001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 *     http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014package org.gbif.api.model.occurrence;
015
016import lombok.Getter;
017
018import lombok.Setter;
019
020import org.gbif.api.annotation.Experimental;
021import org.gbif.api.model.common.Classification;
022import org.gbif.api.model.common.Identifier;
023import org.gbif.api.model.common.LinneanClassification;
024import org.gbif.api.model.common.LinneanClassificationKeys;
025import org.gbif.api.model.common.MediaObject;
026import org.gbif.api.util.ClassificationUtils;
027import org.gbif.api.util.IsoDateInterval;
028import org.gbif.api.vocabulary.BasisOfRecord;
029import org.gbif.api.vocabulary.Continent;
030import org.gbif.api.vocabulary.Country;
031import org.gbif.api.vocabulary.GbifRegion;
032import org.gbif.api.vocabulary.License;
033import org.gbif.api.vocabulary.OccurrenceIssue;
034import org.gbif.api.vocabulary.OccurrenceStatus;
035import org.gbif.api.vocabulary.Rank;
036import org.gbif.api.vocabulary.Sex;
037import org.gbif.api.vocabulary.TaxonomicStatus;
038import org.gbif.dwc.terms.DwcTerm;
039import org.gbif.dwc.terms.GbifTerm;
040import org.gbif.dwc.terms.Term;
041import org.gbif.dwc.terms.UnknownTerm;
042
043import java.lang.reflect.Field;
044import java.lang.reflect.Modifier;
045import java.net.URI;
046import java.util.ArrayList;
047import java.util.Arrays;
048import java.util.Collections;
049import java.util.Date;
050import java.util.EnumSet;
051import java.util.HashMap;
052import java.util.List;
053import java.util.Map;
054import java.util.Objects;
055import java.util.Optional;
056import java.util.Set;
057import java.util.stream.Collectors;
058import java.util.stream.Stream;
059
060import jakarta.annotation.Nullable;
061import jakarta.validation.constraints.Max;
062import jakarta.validation.constraints.Min;
063import jakarta.validation.constraints.NotNull;
064
065import com.fasterxml.jackson.annotation.JsonAnyGetter;
066import com.fasterxml.jackson.annotation.JsonIgnore;
067import com.fasterxml.jackson.annotation.JsonProperty;
068import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
069import com.fasterxml.jackson.databind.annotation.JsonSerialize;
070
071import io.swagger.v3.oas.annotations.ExternalDocumentation;
072import io.swagger.v3.oas.annotations.media.Schema;
073import lombok.EqualsAndHashCode;
074import lombok.ToString;
075
076/**
077 * Represents an Occurrence as interpreted by GBIF, adding typed properties on top of the verbatim ones.
078 */
079@EqualsAndHashCode
080@ToString
081@SuppressWarnings("unused")
082public class Occurrence extends VerbatimOccurrence implements LinneanClassification, LinneanClassificationKeys {
083
084  public static final String GEO_DATUM = "WGS84";
085  // keep names of ALL properties of this class in a set for jackson serialization, see #properties()
086  private static final Set<String> PROPERTIES = Collections.unmodifiableSet(
087    Stream.concat(
088      // we need to these JSON properties manually because we have a fixed getter but no field for it
089      Stream.of(DwcTerm.geodeticDatum.simpleName(), "class", DwcTerm.countryCode.simpleName(), GbifTerm.gbifRegion.simpleName(), GbifTerm.publishedByGbifRegion.simpleName()),
090      Stream.concat(Arrays.stream(Occurrence.class.getDeclaredFields()),
091        Arrays.stream(VerbatimOccurrence.class.getDeclaredFields()))
092        .filter(field -> !Modifier.isStatic(field.getModifiers()))
093        .map(Field::getName)).collect(Collectors.toSet()));
094
095  // occurrence fields
096
097  // OpenAPI documentation comes from the enumeration.
098  private BasisOfRecord basisOfRecord;
099
100  @Schema(
101    description = "The number of individuals present at the time of the Occurrence.",
102    externalDocs = @ExternalDocumentation(
103      description = "Darwin Core definition",
104      url = "https://rs.tdwg.org/dwc/terms/individualCount"
105    )
106  )
107  private Integer individualCount;
108
109  // OpenAPI documentation comes from the enumeration.
110  private OccurrenceStatus occurrenceStatus;
111
112  // OpenAPI documentation comes from the enumeration.
113  private String sex;
114
115  @Schema(
116    description = "The age class or life stage of the Organism(s) at the time the Occurrence was recorded.\n\n" +
117      "Values are aligned to the [GBIF LifeStage vocabulary](https://registry.gbif.org/vocabulary/LifeStage/concepts)",
118    externalDocs = @ExternalDocumentation(
119      description = "Darwin Core definition",
120      url = "https://rs.tdwg.org/dwc/terms/lifeStage"
121    )
122  )
123  private String lifeStage;
124
125  @Schema(
126    description = "Statement about whether an organism or organisms have been introduced to a given place and time " +
127      "through the direct or indirect activity of modern humans.\n\n" +
128      "Values are aligned to the [GBIF EstablishmentMeans vocabulary](https://registry.gbif.org/vocabulary/EstablishmentMeans/concepts), " +
129      "which is derived from the [Darwin Core EstablishmentMeans vocabulary](https://dwc.tdwg.org/em/).",
130    externalDocs = @ExternalDocumentation(
131      description = "Darwin Core definition",
132      url = "https://rs.tdwg.org/dwc/terms/establishmentMeans"
133    )
134  )
135  private String establishmentMeans;
136
137  @Schema(
138    description = "The degree to which an Organism survives, reproduces, and expands its range at the given " +
139      "place and time.\n\n" +
140      "Values are aligned to the [GBIF DegreeOfEstablishment vocabulary](https://registry.gbif.org/vocabulary/DegreeOfEstablishment/concepts), " +
141      "which is derived from the [Darwin Core DegreeOfEstablishment vocabulary](https://dwc.tdwg.org/doe/).",
142    externalDocs = @ExternalDocumentation(
143      description = "Darwin Core definition",
144      url = "https://rs.tdwg.org/dwc/terms/degreeOfEstablishment"
145    )
146  )
147  private String degreeOfEstablishment;
148
149  @Schema(
150    description = "The process by which an Organism came to be in a given place at a given time.\n\n" +
151    "Values are aligned to the [GBIF Pathway vocabulary](https://registry.gbif.org/vocabulary/Pathway/concepts), " +
152    "which is derived from the [Darwin Core Pathway vocabulary](https://dwc.tdwg.org/pw/).",
153    externalDocs = @ExternalDocumentation(
154      description = "Darwin Core definition",
155      url = "https://rs.tdwg.org/dwc/terms/pathway"
156    )
157  )
158  private String pathway;
159
160
161  @Schema(
162    description = "The map of classifications associated with this occurrence keyed by checklistKey.",
163    externalDocs = @ExternalDocumentation(
164      description = "Darwin Core definition",
165      url = "https://rs.tdwg.org/dwc/terms/"
166    )
167  )
168  private Map<String, Classification> classifications;
169
170  @Schema(
171    description = "A taxon key from the selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) for the most specific " +
172      "(lowest rank) taxon for this occurrence.  This could be a synonym, see `acceptedTaxonKey`.",
173    externalDocs = @ExternalDocumentation(
174      description = "Darwin Core definition",
175      url = "https://rs.tdwg.org/dwc/terms/"
176    )
177  )
178  @Deprecated
179  private Integer taxonKey;
180
181  @Schema(
182    description = "A taxon key from the selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) for the kingdom of this occurrence.",
183    externalDocs = @ExternalDocumentation(
184      description = "Darwin Core definition",
185      url = "https://rs.tdwg.org/dwc/terms/"
186    )
187  )
188  @Deprecated
189  private Integer kingdomKey;
190
191  @Schema(
192    description = "A taxon key from the selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) for the phylum of this occurrence.",
193    externalDocs = @ExternalDocumentation(
194      description = "Darwin Core definition",
195      url = "https://rs.tdwg.org/dwc/terms/"
196    )
197  )
198  @Deprecated
199  private Integer phylumKey;
200
201  @Schema(
202    description = "A taxon key from the selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) for the class of this" +
203      "occurrence.",
204    externalDocs = @ExternalDocumentation(
205      description = "Darwin Core definition",
206      url = "https://rs.tdwg.org/dwc/terms/"
207    )
208  )
209  @Deprecated
210  private Integer classKey;
211
212  @Schema(
213    description = "A taxon key from the selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) for the order of this" +
214      "occurrence.",
215    externalDocs = @ExternalDocumentation(
216      description = "Darwin Core definition",
217      url = "https://rs.tdwg.org/dwc/terms/"
218    )
219  )
220  @Deprecated
221  private Integer orderKey;
222
223  @Schema(
224    description = "A taxon key from the selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) for the family of this" +
225      "occurrence.",
226    externalDocs = @ExternalDocumentation(
227      description = "Darwin Core definition",
228      url = "https://rs.tdwg.org/dwc/terms/"
229    )
230  )
231  @Deprecated
232  private Integer familyKey;
233
234  @Schema(
235    description = "A taxon key from the selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) for the genus of this" +
236      "occurrence.",
237    externalDocs = @ExternalDocumentation(
238      description = "Darwin Core definition",
239      url = "https://rs.tdwg.org/dwc/terms/"
240    )
241  )
242  @Deprecated
243  private Integer genusKey;
244
245  @Schema(
246    description = "A taxon key from the selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) for the subgenus of this" +
247      "occurrence.",
248    externalDocs = @ExternalDocumentation(
249      description = "Darwin Core definition",
250      url = "https://rs.tdwg.org/dwc/terms/"
251    )
252  )
253  @Deprecated
254  private Integer subgenusKey;
255
256  @Schema(
257    description = "A taxon key from the selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) for the species of this" +
258      "occurrence.",
259    externalDocs = @ExternalDocumentation(
260      description = "Darwin Core definition",
261      url = "https://rs.tdwg.org/dwc/terms/"
262    )
263  )
264  @Deprecated
265  private Integer speciesKey;
266
267  @Schema(
268    description = "A taxon key from the selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) for the accepted taxon of " +
269      "this occurrence.",
270    externalDocs = @ExternalDocumentation(
271      description = "Darwin Core definition",
272      url = "https://rs.tdwg.org/dwc/terms/"
273    )
274  )
275  @Deprecated
276  private Integer acceptedTaxonKey;
277
278  // taxonomy as name strings → LinneanClassification
279
280  @Schema(
281    description = "The scientific name (including authorship) for the taxon from the " +
282      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.  This could be a synonym, see " +
283      "also `acceptedScientificName`.",
284    externalDocs = @ExternalDocumentation(
285      description = "Darwin Core definition",
286      url = "https://rs.tdwg.org/dwc/terms/"
287    )
288  )
289  private String scientificName;  // the interpreted name matching taxonKey
290
291  @Schema(
292    description = "The scientific name authorship for the taxon from the " +
293      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.  This could be a synonym, see " +
294      "also `acceptedScientificName`.",
295    externalDocs = @ExternalDocumentation(
296      description = "Darwin Core definition",
297      url = "https://rs.tdwg.org/dwc/terms/"
298    )
299  )
300  private String scientificNameAuthorship;
301
302  @Schema(
303    description = "The accepted scientific name (including authorship) for the taxon from the " +
304      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
305    externalDocs = @ExternalDocumentation(
306      description = "Darwin Core definition",
307      url = "https://rs.tdwg.org/dwc/terms/"
308    )
309  )
310  private String acceptedScientificName;
311
312  @Schema(
313    description = "The kingdom name (excluding authorship) for the kingdom from the " +
314      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
315    externalDocs = @ExternalDocumentation(
316      description = "Darwin Core definition",
317      url = "https://rs.tdwg.org/dwc/terms/"
318    )
319  )
320  private String kingdom;
321
322  @Schema(
323    description = "The phylum name (excluding authorship) for the phylum from the " +
324      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
325    externalDocs = @ExternalDocumentation(
326      description = "Darwin Core definition",
327      url = "https://rs.tdwg.org/dwc/terms/"
328    )
329  )
330  private String phylum;
331
332  @Schema(
333    description = "The class name (excluding authorship) for the class from the " +
334      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
335    externalDocs = @ExternalDocumentation(
336      description = "Darwin Core definition",
337      url = "https://rs.tdwg.org/dwc/terms/"
338    )
339  )
340  @JsonProperty("class")
341  private String clazz;
342
343  @Schema(
344    description = "The order name (excluding authorship) for the order from the " +
345      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
346    externalDocs = @ExternalDocumentation(
347      description = "Darwin Core definition",
348      url = "https://rs.tdwg.org/dwc/terms/"
349    )
350  )
351  private String order;
352
353  @Schema(
354    description = "The family name (excluding authorship) for the family from the " +
355      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
356    externalDocs = @ExternalDocumentation(
357      description = "Darwin Core definition",
358      url = "https://rs.tdwg.org/dwc/terms/"
359    )
360  )
361  private String family;
362
363  @Schema(
364    description = "The genus name (excluding authorship) for the genus from the " +
365      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
366    externalDocs = @ExternalDocumentation(
367      description = "Darwin Core definition",
368      url = "https://rs.tdwg.org/dwc/terms/"
369    )
370  )
371  private String genus;
372
373  @Schema(
374    description = "The subgenus name (excluding authorship) for the subgenus from the " +
375      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
376    externalDocs = @ExternalDocumentation(
377      description = "Darwin Core definition",
378      url = "https://rs.tdwg.org/dwc/terms/"
379    )
380  )
381  private String subgenus;
382
383  @Schema(
384    description = "The species name (excluding authorship) for the species from the " +
385      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
386    externalDocs = @ExternalDocumentation(
387      description = "Darwin Core definition",
388      url = "https://rs.tdwg.org/dwc/terms/"
389    )
390  )
391  private String species;
392
393  // atomised scientific name
394
395  @Schema(
396    description = "The genus name part of the species name from the " +
397      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
398    externalDocs = @ExternalDocumentation(
399      description = "Darwin Core definition",
400      url = "https://rs.tdwg.org/dwc/terms/genericName"
401    )
402  )
403  private String genericName;
404
405  @Schema(
406    description = "The specific name part of the species name from the " +
407      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
408    externalDocs = @ExternalDocumentation(
409      description = "Darwin Core definition",
410      url = "https://rs.tdwg.org/dwc/terms/specificEpithet"
411    )
412  )
413  private String specificEpithet;
414
415  @Schema(
416    description = "The infraspecific name part of the species name from the " +
417      "selected taxonomy (e.g. Catalogue of Life, GBIF Backbone) matched to this occurrence.",
418    externalDocs = @ExternalDocumentation(
419      description = "Darwin Core definition",
420      url = "https://rs.tdwg.org/dwc/terms/infraspecificEpithet"
421    )
422  )
423  private String infraspecificEpithet;
424
425  @Schema(
426    description = "The taxonomic rank of the most specific name in the scientificName.",
427    externalDocs = @ExternalDocumentation(
428      description = "Darwin Core definition",
429      url = "https://rs.tdwg.org/dwc/terms/taxonRank"
430    )
431  )
432  private Rank taxonRank;
433
434  @Schema(
435    description = "The status of the use of the scientificName as a label for a taxon.",
436    externalDocs = @ExternalDocumentation(
437      description = "Darwin Core definition",
438      url = "https://rs.tdwg.org/dwc/terms/taxonomicStatus"
439    )
440  )
441  private TaxonomicStatus taxonomicStatus;
442
443  @Schema(
444    description = "The IUCN Red List Category of the taxon of this occurrence.\n\n" +
445      "See the [GBIF vocabulary](https://rs.gbif.org/vocabulary/iucn/threat_status/) for the values and their " +
446      "definitions, and the [IUCN Red List of Threatened Species dataset in GBIF](https://doi.org/10.15468/0qnb58) " +
447      "for the version of the Red List GBIF's interpretation procedures are using.",
448    externalDocs = @ExternalDocumentation(
449      description = "GBIF vocabulary",
450      url = "https://rs.gbif.org/vocabulary/iucn/threat_status/"
451    )
452  )
453  private String iucnRedListCategory;
454
455  // identification
456
457  @Schema(
458    description = "The date on which the subject was determined as representing the Taxon.",
459    externalDocs = @ExternalDocumentation(
460      description = "Darwin Core definition",
461      url = "https://rs.tdwg.org/dwc/terms/dateIdentified"
462    )
463  )
464  private Date dateIdentified;
465
466  // location
467
468  @Schema(
469    description = "The geographic latitude (in decimal degrees, using the WGS84 datum) of the geographic centre " +
470      "of the location of the occurrence.",
471    externalDocs = @ExternalDocumentation(
472      description = "Darwin Core definition",
473      url = "https://rs.tdwg.org/dwc/terms/decimalLatitude"
474    )
475  )
476  private Double decimalLatitude;
477
478  @Schema(
479    description = "The geographic longitude (in decimal degrees, using the WGS84 datum) of the geographic centre " +
480      "of the location of the occurrence.",
481    externalDocs = @ExternalDocumentation(
482      description = "Darwin Core definition",
483      url = "https://rs.tdwg.org/dwc/terms/decimalLongitude"
484    )
485  )
486  private Double decimalLongitude;
487
488  //coordinatePrecision and coordinateUncertaintyInMeters should be BigDecimal see POR-2795
489
490  @Schema(
491    description = "A decimal representation of the precision of the coordinates given in the decimalLatitude and decimalLongitude.",
492    externalDocs = @ExternalDocumentation(
493      description = "Darwin Core definition",
494      url = "https://rs.tdwg.org/dwc/terms/coordinatePrecision"
495    )
496  )
497  private Double coordinatePrecision;
498
499  @Schema(
500    description = "The horizontal distance (in metres) from the given decimalLatitude and decimalLongitude " +
501      "describing the smallest circle containing the whole of the Location.",
502    externalDocs = @ExternalDocumentation(
503      description = "Darwin Core definition",
504      url = "https://rs.tdwg.org/dwc/terms/coordinateUncertaintyInMeters"
505    )
506  )
507  private Double coordinateUncertaintyInMeters;
508
509  @Schema(
510    description = "**Deprecated.**  This value is always null.  It is an obsolete Darwin Core term.",
511    externalDocs = @ExternalDocumentation(
512      description = "Darwin Core definition",
513      url = "https://rs.tdwg.org/dwc/terms/"
514    )
515  )
516  @Deprecated //see getter
517  private Double coordinateAccuracy;
518
519  @Schema(
520    description = "Elevation (altitude) in metres above sea level.  This is not a current Darwin Core term."
521  )
522  private Double elevation;
523
524  @Schema(
525    description = "The value of the potential error associated with the elevation.  This is not a current Darwin Core term."
526  )
527  private Double elevationAccuracy;
528
529  @Schema(
530    description = "Depth in metres below sea level.  This is not a current Darwin Core term."
531  )
532  private Double depth;
533
534  @Schema(
535    description = "The value of the potential error associated with the depth.  This is not a current Darwin Core term."
536  )
537  private Double depthAccuracy;
538
539  // OpenAPI documentation from enumeration
540  private Continent continent;
541
542  @JsonSerialize(using = Country.IsoSerializer.class)
543  @JsonDeserialize(using = Country.IsoDeserializer.class)
544  private Country country;
545
546  @Schema(
547    description = "The name of the next-smaller administrative region than country (state, province, canton, " +
548      "department, region, etc.) in which the occurrence occurs.\n\n" +
549      "This value is unaltered by GBIF's processing; see also the GADM fields.",
550    externalDocs = @ExternalDocumentation(
551      description = "Darwin Core definition",
552      url = "https://rs.tdwg.org/dwc/terms/stateProvince"
553    )
554  )
555  private String stateProvince;
556
557  @Schema(
558    description = "The administrative divisions according to the [GADM database](https://gadm.org/).\n\n" +
559      "This value is applied by GBIF's processing without consideration of the `stateProvince`, `county` or `locality` fields."
560  )
561  private Gadm gadm = new Gadm();
562
563  @Schema(
564    description = "The name of the water body in which the Location occurs.",
565    externalDocs = @ExternalDocumentation(
566      description = "Darwin Core definition",
567      url = "https://rs.tdwg.org/dwc/terms/waterBody"
568    )
569  )
570  private String waterBody;
571
572  @Schema(
573    description = "The distance in metres of the occurrence from a centroid known to be applied to occurrences " +
574      "during georeferencing.  This can potentially indicate low-precision georeferencing, check the values of " +
575      "`coordinateUncertaintyInMeters` and `georeferenceRemarks`."
576  )
577  private Double distanceFromCentroidInMeters;
578
579  @Schema(
580    description = "A list (concatenated and separated) of geographic names less specific than the information captured in the dwc:locality term.",
581    externalDocs = @ExternalDocumentation(
582      description = "Darwin Core definition",
583      url = "https://rs.tdwg.org/dwc/terms/higherGeography"
584    )
585  )
586  private String higherGeography;
587
588  @Schema(
589    description = "A list (concatenated and separated) of names of people, groups, or organizations who determined the georeference (spatial representation) for the dcterms:Location.",
590    externalDocs = @ExternalDocumentation(
591      description = "Darwin Core definition",
592      url = "https://rs.tdwg.org/dwc/terms/georeferencedBy"
593    )
594  )
595  private String georeferencedBy;
596
597  // recording event
598
599  @Schema(
600    description = "The four-digit year in which the event occurred, according to the Common Era calendar.",
601    externalDocs = @ExternalDocumentation(
602      description = "Darwin Core definition",
603      url = "https://rs.tdwg.org/dwc/terms/year"
604    )
605  )
606  private Integer year;
607
608  @Schema(
609    description = "The integer month in which the Event occurred.",
610    externalDocs = @ExternalDocumentation(
611      description = "Darwin Core definition",
612      url = "https://rs.tdwg.org/dwc/terms/month"
613    )
614  )
615  private Integer month;
616
617  @Schema(
618    description = "The integer day of the month on which the Event occurred.",
619    externalDocs = @ExternalDocumentation(
620      description = "Darwin Core definition",
621      url = "https://rs.tdwg.org/dwc/terms/day"
622    )
623  )
624  private Integer day;
625
626  @Schema(
627    description = "The date-time during which an Event occurred. For occurrences, this is the date-time when the " +
628      "event was recorded. Not suitable for a time in a geological context.",
629    externalDocs = @ExternalDocumentation(
630      description = "Darwin Core definition",
631      url = "https://rs.tdwg.org/dwc/terms/eventDate"
632    )
633  )
634  private IsoDateInterval eventDate;
635
636  @Schema(
637    description = "The latest integer day of the year on which the Event occurred (1 for 1 January, 365 for " +
638      "31 December, except in a leap year, in which case it is 366).",
639    externalDocs = @ExternalDocumentation(
640      description = "Darwin Core definition",
641      url = "https://rs.tdwg.org/dwc/terms/endDayOfYear"
642    )
643  )
644  private Integer startDayOfYear;
645
646  @Schema(
647    description = "The earliest integer day of the year on which the Event occurred (1 for 1 January, 365 for " +
648      "31 December, except in a leap year, in which case it is 366).",
649    externalDocs = @ExternalDocumentation(
650      description = "Darwin Core definition",
651      url = "https://rs.tdwg.org/dwc/terms/startDayOfYear"
652    )
653  )
654  private Integer endDayOfYear;
655
656  @Schema(
657    description = "A list (concatenated and separated) of nomenclatural types (type status, typified scientific name, " +
658      "publication) applied to the occurrence.",
659    externalDocs = @ExternalDocumentation(
660      description = "Darwin Core definition",
661      url = "https://rs.tdwg.org/dwc/terms/typeStatus"
662    )
663  )
664  private String typeStatus;
665
666  // extracted from type status, but we should propose a new dwc term for this!
667  // for example: "Paratype of Taeniopteryx metequi Ricker & Ross" is status=Paratype, typifiedName=Taeniopteryx metequi Ricker & Ross
668  @Schema(
669    description = "The scientific name that is based on the type specimen.\n\n" +
670      "This is not yet a Darwin Core term, see the [proposal to add it](https://github.com/tdwg/dwc/issues/28)."
671  )
672  private String typifiedName; // missing from DwC
673
674  @Schema(
675    description = "A specific interpretation issue found during processing and interpretation of the record.\n\n" +
676      "See the https://links.gbif.org/occurrence-issues[list of occurrence issues] and the " +
677      "https://gbif.github.io/gbif-api/apidocs/org/gbif/api/vocabulary/OccurrenceIssue.html[OccurrenceIssue enumeration] " +
678      "for possible values and definitions."
679  )
680  private Set<OccurrenceIssue> issues = EnumSet.noneOf(OccurrenceIssue.class);
681
682  // record level
683
684  @Schema(
685    description = "The most recent date-time on which the occurrence was changed, according to the publisher.",
686    externalDocs = @ExternalDocumentation(
687      description = "Dublin Core definition",
688      url = "https://purl.org/dc/terms/modified"
689    )
690  )
691  private Date modified;  // interpreted dc:modified, i.e. date changed in source
692
693  @Schema(
694    description = "The time this occurrence was last processed by GBIF's interpretation system “Pipelines”.\n\n" +
695      "This is the time the record was last changed in GBIF, **not** the time the record was last changed by the " +
696      "publisher.  Data is also reprocessed when we changed the taxonomic backbone, geographic data sources or " +
697      "other interpretation procedures.\n\n" +
698      "An earlier interpretation system distinguished between “parsing” and “interpretation”, but in the current " +
699      "system there is only one process — the two dates will always be the same."
700  )
701  private Date lastInterpreted;
702
703  @Schema(
704    description = "A related resource that is referenced, cited, or otherwise pointed to by the described resource.",
705    externalDocs = @ExternalDocumentation(
706      description = "Dublin Core definition",
707      url = "https://purl.org/dc/terms/references"
708    )
709  )
710  private URI references;
711
712  @Schema(
713    description = "A legal document giving official permission to do something with the occurrence.",
714    externalDocs = @ExternalDocumentation(
715      description = "Dublin Core definition",
716      url = "https://purl.org/dc/terms/license"
717    )
718  )
719  private License license;
720
721  @Schema(
722    description = "A number or enumeration value for the quantity of organisms.",
723    externalDocs = @ExternalDocumentation(
724      description = "Darwin Core definition",
725      url = "https://rs.tdwg.org/dwc/terms/organismQuantity"
726    )
727  )
728  private Double organismQuantity;
729
730  @Schema(
731    description = "The type of quantification system used for the quantity of organisms.",
732    externalDocs = @ExternalDocumentation(
733      description = "Darwin Core definition",
734      url = "https://rs.tdwg.org/dwc/terms/organismQuantityType"
735    )
736  )
737  private String organismQuantityType;
738
739  @Schema(
740    description = "The unit of measurement of the size (time duration, length, area, or volume) of a sample in a sampling event.",
741    externalDocs = @ExternalDocumentation(
742      description = "Darwin Core definition",
743      url = "https://rs.tdwg.org/dwc/terms/sampleSizeUnit"
744    )
745  )
746  private String sampleSizeUnit;
747
748  @Schema(
749    description = "A numeric value for a measurement of the size (time duration, length, area, or volume) of a sample in a sampling event.",
750    externalDocs = @ExternalDocumentation(
751      description = "Darwin Core definition",
752      url = "https://rs.tdwg.org/dwc/terms/sampleSizeValue"
753    )
754  )
755  private Double sampleSizeValue;
756
757  @Schema(
758    description = "The relative measurement of the quantity of the organism (i.e. without absolute units)."
759  )
760  private Double relativeOrganismQuantity;
761
762  @Schema(
763    description = "Flag occurrence when associatedSequences/extension exists"
764  )
765  private boolean isSequenced;
766
767  @Schema(
768      description = "A list (concatenated and separated) of identifiers (publication, global unique identifier, URI) "
769        + "of genetic sequence information associated with the material entity.",
770      externalDocs =
771          @ExternalDocumentation(
772              description = "Darwin Core definition",
773              url = "https://rs.tdwg.org/dwc/terms/associatedSequences"))
774  private String associatedSequences;
775
776  // interpreted extension data
777
778  @Schema(
779    description = "Alternative identifiers for the occurrence.",
780    externalDocs = @ExternalDocumentation(
781      description = "GBIF Alternative Identifiers extension",
782      url = "https://rs.gbif.org/terms/1.0/Identifier"
783    )
784  )
785  private List<Identifier> identifiers = new ArrayList<>();
786
787  @Schema(
788    description = "Multimedia related to te occurrence.",
789    externalDocs = @ExternalDocumentation(
790      description = "GBIF Multimedia extension",
791      url = "https://rs.gbif.org/terms/1.0/Multimedia"
792    )
793  )
794  private List<MediaObject> media = new ArrayList<>();
795
796  @Schema(
797    description = "Measurements or facts about the the occurrence.",
798    externalDocs = @ExternalDocumentation(
799      description = "Darwin Core definition",
800      url = "https://rs.tdwg.org/dwc/terms/MeasurementOrFact"
801    )
802  )
803  private List<MeasurementOrFact> facts = new ArrayList<>();
804
805  @Schema(
806    description = "Relationships between occurrences.",
807    externalDocs = @ExternalDocumentation(
808      description = "Darwin Core definition",
809      url = "https://rs.tdwg.org/dwc/terms/ResourceRelationship"
810    )
811  )
812  private List<OccurrenceRelation> relations = new ArrayList<>();
813
814  @Schema(
815    description = "A list of the globally unique identifiers for the person, people, groups, or organizations " +
816      "responsible for recording the original Occurrence.",
817    externalDocs = @ExternalDocumentation(
818      description = "Darwin Core definition",
819      url = "https://rs.tdwg.org/dwc/terms/recordedByID"
820    )
821  )
822  @JsonProperty("recordedByIDs")
823  private List<AgentIdentifier> recordedByIds = new ArrayList<>();
824
825  @Schema(
826    description = "A list of the globally unique identifiers for the person, people, groups, or organizations " +
827      "responsible for assigning the Taxon to the occurrence.",
828    externalDocs = @ExternalDocumentation(
829      description = "Darwin Core definition",
830      url = "https://rs.tdwg.org/dwc/terms/identifiedByID"
831    )
832  )
833  @JsonProperty("identifiedByIDs")
834  private List<AgentIdentifier> identifiedByIds = new ArrayList<>();
835
836  @Schema(
837    description = "**Experimental.**  The UUID of the institution holding the specimen occurrence, from GRSciColl."
838  )
839  @Experimental
840  private String institutionKey;
841
842  @Schema(
843    description = "**Experimental.**  The UUID of the collection containing the specimen occurrence, from GRSciColl."
844  )
845  @Experimental
846  private String collectionKey;
847
848  @Schema(
849    description = "**Experimental.**  Whether the occurrence belongs to a machine-calculated cluster of probable " +
850      "duplicate occurrences.",
851    externalDocs = @ExternalDocumentation(
852      description = "GBIF Data Blog",
853      url = "https://data-blog.gbif.org/post/clustering-occurrences/"
854    )
855  )
856  @Experimental
857  private boolean isInCluster;
858
859  @Schema(
860    description = "An identifier for the set of data. May be a global unique identifier or an identifier specific to " +
861      "a collection or institution.",
862    externalDocs = @ExternalDocumentation(
863      description = "Darwin Core definition",
864      url = "https://rs.tdwg.org/dwc/terms/datasetID"
865    )
866  )
867  private String datasetID;
868
869  @Schema(
870    description = "The name identifying the data set from which the record was derived.",
871    externalDocs = @ExternalDocumentation(
872      description = "Darwin Core definition",
873      url = "https://rs.tdwg.org/dwc/terms/"
874    )
875  )
876  private String datasetName;
877
878  @Schema(
879    description = "A list (concatenated and separated) of previous or alternate fully qualified catalogue numbers " +
880      "or other human-used identifiers for the same occurrence, whether in the current or any other data set or collection.",
881    externalDocs = @ExternalDocumentation(
882      description = "Darwin Core definition",
883      url = "https://rs.tdwg.org/dwc/terms/otherCatalogNumbers"
884    )
885  )
886  private String otherCatalogNumbers;
887
888  @Schema(
889    description = "The full name of the earliest possible geochronologic eon or lowest chrono-stratigraphic eonothem or the informal name (\"Precambrian\") attributable to the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
890    externalDocs = @ExternalDocumentation(
891      description = "Darwin Core definition",
892      url = "https://rs.tdwg.org/dwc/terms/earliestEonOrLowestEonothem"
893    )
894  )
895  private String earliestEonOrLowestEonothem;
896
897  @Schema(
898    description = "The full name of the latest possible geochronologic eon or highest chrono-stratigraphic eonothem or the informal name (\"Precambrian\") attributable to the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
899    externalDocs = @ExternalDocumentation(
900      description = "Darwin Core definition",
901      url = "https://rs.tdwg.org/dwc/terms/latestEonOrHighestEonothem"
902    )
903  )
904  private String latestEonOrHighestEonothem;
905
906  @Schema(
907    description = "The full name of the earliest possible geochronologic era or lowest chronostratigraphic erathem attributable to the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
908    externalDocs = @ExternalDocumentation(
909      description = "Darwin Core definition",
910      url = "https://rs.tdwg.org/dwc/terms/earliestEraOrLowestErathem"
911    )
912  )
913  private String earliestEraOrLowestErathem;
914
915  @Schema(
916    description = "The full name of the latest possible geochronologic era or highest chronostratigraphic erathem attributable to the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
917    externalDocs = @ExternalDocumentation(
918      description = "Darwin Core definition",
919      url = "https://rs.tdwg.org/dwc/terms/latestEraOrHighestErathem"
920    )
921  )
922  private String latestEraOrHighestErathem;
923
924  @Schema(
925    description = "The full name of the earliest possible geochronologic period or lowest chronostratigraphic system attributable to the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
926    externalDocs = @ExternalDocumentation(
927      description = "Darwin Core definition",
928      url = "https://rs.tdwg.org/dwc/terms/earliestPeriodOrLowestSystem"
929    )
930  )
931  private String earliestPeriodOrLowestSystem;
932
933  @Schema(
934    description = "The full name of the latest possible geochronologic period or highest chronostratigraphic system attributable to the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
935    externalDocs = @ExternalDocumentation(
936      description = "Darwin Core definition",
937      url = "https://rs.tdwg.org/dwc/terms/latestPeriodOrHighestSystem"
938    )
939  )
940  private String latestPeriodOrHighestSystem;
941
942  @Schema(
943    description = "The full name of the earliest possible geochronologic epoch or lowest chronostratigraphic series attributable to the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
944    externalDocs = @ExternalDocumentation(
945      description = "Darwin Core definition",
946      url = "https://rs.tdwg.org/dwc/terms/earliestEpochOrLowestSeries"
947    )
948  )
949  private String earliestEpochOrLowestSeries;
950
951  @Schema(
952    description = "The full name of the latest possible geochronologic epoch or highest chronostratigraphic series attributable to the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
953    externalDocs = @ExternalDocumentation(
954      description = "Darwin Core definition",
955      url = "https://rs.tdwg.org/dwc/terms/latestEpochOrHighestSeries"
956    )
957  )
958  private String latestEpochOrHighestSeries;
959
960  @Schema(
961    description = "The full name of the earliest possible geochronologic age or lowest chronostratigraphic stage attributable to the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
962    externalDocs = @ExternalDocumentation(
963      description = "Darwin Core definition",
964      url = "https://rs.tdwg.org/dwc/terms/earliestAgeOrLowestStage"
965    )
966  )
967  private String earliestAgeOrLowestStage;
968
969  @Schema(
970    description = "The full name of the latest possible geochronologic age or highest chronostratigraphic stage attributable to the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
971    externalDocs = @ExternalDocumentation(
972      description = "Darwin Core definition",
973      url = "https://rs.tdwg.org/dwc/terms/latestAgeOrHighestStage"
974    )
975  )
976  private String latestAgeOrHighestStage;
977
978  @Schema(
979    description = "The full name of the lowest possible geological biostratigraphic zone of the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
980    externalDocs = @ExternalDocumentation(
981      description = "Darwin Core definition",
982      url = "https://rs.tdwg.org/dwc/terms/lowestBiostratigraphicZone"
983    )
984  )
985  private String lowestBiostratigraphicZone;
986
987  @Schema(
988    description = "The full name of the highest possible geological biostratigraphic zone of the stratigraphic horizon from which the dwc:MaterialEntity was collected.",
989    externalDocs = @ExternalDocumentation(
990      description = "Darwin Core definition",
991      url = "https://rs.tdwg.org/dwc/terms/highestBiostratigraphicZone"
992    )
993  )
994  private String highestBiostratigraphicZone;
995
996  @Schema(
997    description = "The full name of the lithostratigraphic group from which the dwc:MaterialEntity was collected.",
998    externalDocs = @ExternalDocumentation(
999      description = "Darwin Core definition",
1000      url = "https://rs.tdwg.org/dwc/terms/group"
1001    )
1002  )
1003  private String group;
1004
1005  @Schema(
1006    description = "The full name of the lithostratigraphic formation from which the dwc:MaterialEntity was collected.",
1007    externalDocs = @ExternalDocumentation(
1008      description = "Darwin Core definition",
1009      url = "https://rs.tdwg.org/dwc/terms/formation"
1010    )
1011  )
1012  private String formation;
1013
1014  @Schema(
1015    description = "The full name of the lithostratigraphic member from which the dwc:MaterialEntity was collected.",
1016    externalDocs = @ExternalDocumentation(
1017      description = "Darwin Core definition",
1018      url = "https://rs.tdwg.org/dwc/terms/member"
1019    )
1020  )
1021  private String member;
1022
1023  @Schema(
1024    description = "The full name of the lithostratigraphic bed from which the dwc:MaterialEntity was collected.",
1025    externalDocs = @ExternalDocumentation(
1026      description = "Darwin Core definition",
1027      url = "https://rs.tdwg.org/dwc/terms/bed"
1028    )
1029  )
1030  private String bed;
1031
1032
1033  @Schema(
1034    description = "A person, group, or organization responsible for recording the original occurrence.",
1035    externalDocs = @ExternalDocumentation(
1036      description = "Darwin Core definition",
1037      url = "https://rs.tdwg.org/dwc/terms/recordedBy"
1038    )
1039  )
1040  private String recordedBy;
1041
1042  @Schema(
1043    description = "A list (concatenated and separated) of names of people, groups, or organizations who assigned the " +
1044      "Taxon to the occurrence.",
1045    externalDocs = @ExternalDocumentation(
1046      description = "Darwin Core definition",
1047      url = "https://rs.tdwg.org/dwc/terms/identifiedBy"
1048    )
1049  )
1050  private String identifiedBy;
1051
1052  @Schema(
1053    description = "A preparation or preservation method for a specimen.",
1054    externalDocs = @ExternalDocumentation(
1055      description = "Darwin Core definition",
1056      url = "https://rs.tdwg.org/dwc/terms/preparations"
1057    )
1058  )
1059  private String preparations;
1060
1061  @Schema(
1062    description = "The methods or protocols used during an Event, denoted by an IRI.",
1063    externalDocs = @ExternalDocumentation(
1064      description = "Darwin Core definition",
1065      url = "https://rs.tdwg.org/dwc/terms/samplingProtocol"
1066    )
1067  )
1068  private String samplingProtocol;
1069
1070  @Schema(
1071    description = "**Deprecated: use the nucleotideSequence instead.**  The DNA sequence ID of an occurrence."
1072  )
1073  @Deprecated
1074  private List<String> dnaSequenceID = new ArrayList<>();
1075
1076  @Schema(description = "Nucleotide Sequence objects of an occurrence.")
1077  @Getter
1078  @Setter
1079  private List<NucleotideSequence> nucleotideSequence = new ArrayList<>();
1080
1081  public Occurrence() {
1082
1083  }
1084
1085  /**
1086   * Create occurrence instance from existing verbatim one, copying over all data.
1087   */
1088  public Occurrence(@Nullable VerbatimOccurrence verbatim) {
1089    if (verbatim != null) {
1090      setKey(verbatim.getKey());
1091      setDatasetKey(verbatim.getDatasetKey());
1092      setPublishingOrgKey(verbatim.getPublishingOrgKey());
1093      setPublishingCountry(verbatim.getPublishingCountry());
1094      setProtocol(verbatim.getProtocol());
1095      setCrawlId(verbatim.getCrawlId());
1096      if (verbatim.getLastCrawled() != null) {
1097        setLastCrawled(new Date(verbatim.getLastCrawled().getTime()));
1098      }
1099      if (verbatim.getVerbatimFields() != null) {
1100        getVerbatimFields().putAll(verbatim.getVerbatimFields());
1101      }
1102      if (verbatim.getLastParsed() != null) {
1103        setLastParsed(verbatim.getLastParsed());
1104      }
1105      setExtensions(verbatim.getExtensions());
1106    }
1107  }
1108
1109  @Nullable
1110  public BasisOfRecord getBasisOfRecord() {
1111    return basisOfRecord;
1112  }
1113
1114  public void setBasisOfRecord(BasisOfRecord basisOfRecord) {
1115    this.basisOfRecord = basisOfRecord;
1116  }
1117
1118  @Nullable
1119  public Integer getIndividualCount() {
1120    return individualCount;
1121  }
1122
1123  public void setIndividualCount(Integer individualCount) {
1124    this.individualCount = individualCount;
1125  }
1126
1127  @Nullable
1128  public OccurrenceStatus getOccurrenceStatus() {
1129    return occurrenceStatus;
1130  }
1131
1132  public void setOccurrenceStatus(OccurrenceStatus occurrenceStatus) {
1133    this.occurrenceStatus = occurrenceStatus;
1134  }
1135
1136  @Nullable
1137  public String getSex() {
1138    return sex;
1139  }
1140
1141  public void setSex(String sex) {
1142    this.sex = sex;
1143  }
1144
1145  @Nullable
1146  public String getLifeStage() {
1147    return lifeStage;
1148  }
1149
1150  public void setLifeStage(String lifeStage) {
1151    this.lifeStage = lifeStage;
1152  }
1153
1154  @Nullable
1155  public String getEstablishmentMeans() {
1156    return establishmentMeans;
1157  }
1158
1159  public void setEstablishmentMeans(String establishmentMeans) {
1160    this.establishmentMeans = establishmentMeans;
1161  }
1162
1163  /**
1164   * The best matching, accepted name usage representing this occurrence.
1165   * In case the verbatim scientific name and its classification can only be matched to a higher rank this will
1166   * represent the lowest matching rank. In the worst case this could just be for example Animalia.
1167   */
1168  @Nullable
1169  public Integer getTaxonKey() {
1170    return taxonKey;
1171  }
1172
1173  public void setTaxonKey(Integer taxonKey) {
1174    this.taxonKey = taxonKey;
1175  }
1176
1177  @Nullable
1178  @Override
1179  public Integer getKingdomKey() {
1180    return kingdomKey;
1181  }
1182
1183  @Override
1184  public void setKingdomKey(@Nullable Integer kingdomKey) {
1185    this.kingdomKey = kingdomKey;
1186  }
1187
1188  @Nullable
1189  @Override
1190  public Integer getPhylumKey() {
1191    return phylumKey;
1192  }
1193
1194  @Override
1195  public void setPhylumKey(@Nullable Integer phylumKey) {
1196    this.phylumKey = phylumKey;
1197  }
1198
1199  @Nullable
1200  @Override
1201  public Integer getClassKey() {
1202    return classKey;
1203  }
1204
1205  @Override
1206  public void setClassKey(@Nullable Integer classKey) {
1207    this.classKey = classKey;
1208  }
1209
1210  @Nullable
1211  @Override
1212  public Integer getOrderKey() {
1213    return orderKey;
1214  }
1215
1216  @Override
1217  public void setOrderKey(@Nullable Integer orderKey) {
1218    this.orderKey = orderKey;
1219  }
1220
1221  @Nullable
1222  @Override
1223  public Integer getFamilyKey() {
1224    return familyKey;
1225  }
1226
1227  @Override
1228  public void setFamilyKey(@Nullable Integer familyKey) {
1229    this.familyKey = familyKey;
1230  }
1231
1232  @Nullable
1233  @Override
1234  public Integer getGenusKey() {
1235    return genusKey;
1236  }
1237
1238  @Override
1239  public void setGenusKey(@Nullable Integer genusKey) {
1240    this.genusKey = genusKey;
1241  }
1242
1243  @Nullable
1244  @Override
1245  public Integer getSubgenusKey() {
1246    return subgenusKey;
1247  }
1248
1249  @Override
1250  public void setSubgenusKey(@Nullable Integer subgenusKey) {
1251    this.subgenusKey = subgenusKey;
1252  }
1253
1254  @Nullable
1255  @Override
1256  public Integer getHigherRankKey(Rank rank) {
1257    return ClassificationUtils.getHigherRankKey(this, rank);
1258  }
1259
1260  /**
1261   * An ordered map with entries for all higher Linnean ranks excluding the taxonKey itself.
1262   * The map starts with the highest rank, e.g. the kingdom and maps the name usage key to its canonical name.
1263   *
1264   * @return map of higher ranks
1265   */
1266  @NotNull
1267  @JsonIgnore
1268  public Map<Integer, String> getHigherClassificationMap() {
1269    return taxonKey == null ? ClassificationUtils.getHigherClassificationMap(this)
1270      : ClassificationUtils.getHigherClassificationMap(this, taxonKey, null, null);
1271  }
1272
1273  /**
1274   * The accepted species for this occurrence. In case the taxonKey is of a higher rank than species (e.g. genus)
1275   * speciesKey is null. In case taxonKey represents an infraspecific taxon the speciesKey points to the species
1276   * the infraspecies is classified as. In case of taxonKey being a species the speciesKey is the same.
1277   */
1278  @Nullable
1279  @Override
1280  public Integer getSpeciesKey() {
1281    return speciesKey;
1282  }
1283
1284  @Override
1285  public void setSpeciesKey(@Nullable Integer speciesKey) {
1286    this.speciesKey = speciesKey;
1287  }
1288
1289  /**
1290   * The accepted taxon key from the selected taxonomy.
1291   */
1292  @Nullable
1293  public Integer getAcceptedTaxonKey() {
1294    return acceptedTaxonKey;
1295  }
1296
1297  public void setAcceptedTaxonKey(Integer acceptedTaxonKey) {
1298    this.acceptedTaxonKey = acceptedTaxonKey;
1299  }
1300
1301  @Nullable
1302  public String getSpecificEpithet() {
1303    return specificEpithet;
1304  }
1305
1306  public void setSpecificEpithet(String specificEpithet) {
1307    this.specificEpithet = specificEpithet;
1308  }
1309
1310  @Nullable
1311  public String getInfraspecificEpithet() {
1312    return infraspecificEpithet;
1313  }
1314
1315  public void setInfraspecificEpithet(String infraspecificEpithet) {
1316    this.infraspecificEpithet = infraspecificEpithet;
1317  }
1318
1319  @Nullable
1320  public Rank getTaxonRank() {
1321    return taxonRank;
1322  }
1323
1324  public void setTaxonRank(Rank taxonRank) {
1325    this.taxonRank = taxonRank;
1326  }
1327
1328  /**
1329   * The status of the use of the scientificName as a label for a taxon.
1330   * The GBIF recommended controlled value vocabulary can be found at <a href="http://rs.gbif.org/vocabulary/gbif/taxonomic_status.xml">http://rs.gbif.org/vocabulary/gbif/taxonomic_status.xm</a>.
1331   */
1332  @Nullable
1333  public TaxonomicStatus getTaxonomicStatus() {
1334    return taxonomicStatus;
1335  }
1336
1337  public void setTaxonomicStatus(TaxonomicStatus taxonomicStatus) {
1338    this.taxonomicStatus = taxonomicStatus;
1339  }
1340
1341  /**
1342   * The IUCN Red List Category.
1343   */
1344  @Nullable
1345  public String getIucnRedListCategory() {
1346    return iucnRedListCategory;
1347  }
1348
1349  public void setIucnRedListCategory(String iucnRedListCategory) {
1350    this.iucnRedListCategory = iucnRedListCategory;
1351  }
1352
1353  /**
1354   * The scientific name for taxonKey from the selected taxonomy.
1355   */
1356  @Nullable
1357  public String getScientificName() {
1358    return scientificName;
1359  }
1360
1361  public void setScientificName(@Nullable String scientificName) {
1362    this.scientificName = scientificName;
1363  }
1364
1365  /**
1366   * The scientific name authorship for taxonKey from the selected taxonomy.
1367   */
1368  @Nullable
1369  public String getScientificNameAuthorship() {
1370    return scientificNameAuthorship;
1371  }
1372
1373  public void setScientificNameAuthorship(String scientificNameAuthorship) {
1374    this.scientificNameAuthorship = scientificNameAuthorship;
1375  }
1376
1377  /**
1378   * The verbatim scientific name as provided by the source.
1379   */
1380  @Nullable
1381  @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
1382  public String getVerbatimScientificName() {
1383    return getVerbatimField(DwcTerm.scientificName);
1384  }
1385
1386  public void setVerbatimScientificName(String scientificName) {
1387    //DO NOTHING
1388  }
1389
1390  /**
1391   * The accepted scientific name for the acceptedTaxonKey from the selected taxonomy.
1392   */
1393  @Nullable
1394  public String getAcceptedScientificName() {
1395    return acceptedScientificName;
1396  }
1397
1398  public void setAcceptedScientificName(String acceptedScientificName) {
1399    this.acceptedScientificName = acceptedScientificName;
1400  }
1401
1402  @Nullable
1403  @Override
1404  public String getKingdom() {
1405    return kingdom;
1406  }
1407
1408  @Override
1409  public void setKingdom(@Nullable String kingdom) {
1410    this.kingdom = kingdom;
1411  }
1412
1413  @Nullable
1414  @Override
1415  public String getPhylum() {
1416    return phylum;
1417  }
1418
1419  @Override
1420  public void setPhylum(@Nullable String phylum) {
1421    this.phylum = phylum;
1422  }
1423
1424  @Nullable
1425  @Override
1426  public String getClazz() {
1427    return clazz;
1428  }
1429
1430  @Override
1431  public void setClazz(@Nullable String clazz) {
1432    this.clazz = clazz;
1433  }
1434
1435  @Nullable
1436  @Override
1437  public String getOrder() {
1438    return order;
1439  }
1440
1441  @Override
1442  public void setOrder(@Nullable String order) {
1443    this.order = order;
1444  }
1445
1446  @Nullable
1447  @Override
1448  public String getFamily() {
1449    return family;
1450  }
1451
1452  @Override
1453  public void setFamily(@Nullable String family) {
1454    this.family = family;
1455  }
1456
1457  @Nullable
1458  @Override
1459  public String getGenus() {
1460    return genus;
1461  }
1462
1463  @Override
1464  public void setGenus(@Nullable String genus) {
1465    this.genus = genus;
1466  }
1467
1468  @Nullable
1469  public String getGenericName() {
1470    return genericName;
1471  }
1472
1473  public void setGenericName(String genericName) {
1474    this.genericName = genericName;
1475  }
1476
1477  @Nullable
1478  @Override
1479  public String getSubgenus() {
1480    return subgenus;
1481  }
1482
1483  @Override
1484  public void setSubgenus(@Nullable String subgenus) {
1485    this.subgenus = subgenus;
1486  }
1487
1488  @Nullable
1489  @Override
1490  public String getHigherRank(Rank rank) {
1491    return ClassificationUtils.getHigherRank(this, rank);
1492  }
1493
1494  /**
1495   * The corresponding scientific name of the speciesKey from the selected taxonomy.
1496   */
1497  @Nullable
1498  @Override
1499  public String getSpecies() {
1500    return species;
1501  }
1502
1503  @Override
1504  public void setSpecies(@Nullable String species) {
1505    this.species = species;
1506  }
1507
1508  @Nullable
1509  public Date getDateIdentified() {
1510    return dateIdentified == null ? null : new Date(dateIdentified.getTime());
1511  }
1512
1513  public void setDateIdentified(@Nullable Date dateIdentified) {
1514    this.dateIdentified = dateIdentified == null ? null : new Date(dateIdentified.getTime());
1515  }
1516
1517  /**
1518   * The decimalLongitude in decimal degrees always for the WGS84 datum. If a different geodetic datum was given the verbatim
1519   * coordinates are transformed into WGS84 values.
1520   */
1521  @Nullable
1522  public Double getDecimalLongitude() {
1523    return decimalLongitude;
1524  }
1525
1526  public void setDecimalLongitude(@Nullable Double decimalLongitude) {
1527    this.decimalLongitude = decimalLongitude;
1528  }
1529
1530  @Nullable
1531  public Double getDecimalLatitude() {
1532    return decimalLatitude;
1533  }
1534
1535  public void setDecimalLatitude(@Nullable Double decimalLatitude) {
1536    this.decimalLatitude = decimalLatitude;
1537  }
1538
1539  /**
1540   * The uncertainty radius for lat/lon in meters.
1541   */
1542  @Nullable
1543  public Double getCoordinateUncertaintyInMeters() {
1544    return coordinateUncertaintyInMeters;
1545  }
1546
1547  public void setCoordinateUncertaintyInMeters(@Nullable Double coordinateUncertaintyInMeters) {
1548    this.coordinateUncertaintyInMeters = coordinateUncertaintyInMeters;
1549  }
1550
1551  @Nullable
1552  public Double getCoordinatePrecision() {
1553    return coordinatePrecision;
1554  }
1555
1556  public void setCoordinatePrecision(Double coordinatePrecision) {
1557    this.coordinatePrecision = coordinatePrecision;
1558  }
1559
1560  /**
1561   * @deprecated to be removed in the public v2 of the API (see POR-3061)
1562   * The uncertainty for latitude in decimal degrees.
1563   * Note that the longitude degrees have a different accuracy in degrees which changes with latitude and is largest at the poles.
1564   */
1565  @Nullable
1566  @Deprecated
1567  public Double getCoordinateAccuracy() {
1568    return coordinateAccuracy;
1569  }
1570
1571  public void setCoordinateAccuracy(@Nullable Double coordinateAccuracy) {
1572    this.coordinateAccuracy = coordinateAccuracy;
1573  }
1574
1575  /**
1576   * The geodetic datum for the interpreted decimal coordinates.
1577   * This is always WGS84 if a coordinate exists as we reproject other datums into WGS84.
1578   */
1579  @Schema(
1580    description = "The geodetic datum for the interpreted decimal coordinates.\n\n" +
1581      "Coordinates are reprojected to WGS84 if they exist, so this field is either null or `WGS84`."
1582  )
1583  @Nullable
1584  public String getGeodeticDatum() {
1585    if (decimalLatitude != null) {
1586      return GEO_DATUM;
1587    }
1588    return null;
1589  }
1590
1591  /**
1592   * This private method is needed for jackson deserialization only.
1593   */
1594  private void setGeodeticDatum(String datum) {
1595    // ignore, we have a static WGS84 value
1596  }
1597
1598  /**
1599   * Elevation in meters usually above sea level (altitude).
1600   * </br>
1601   * The elevation is calculated using the equation: (minimumElevationInMeters + maximumElevationInMeters) / 2.
1602   */
1603  @Nullable
1604  public Double getElevation() {
1605    return elevation;
1606  }
1607
1608  public void setElevation(@Nullable Double elevation) {
1609    this.elevation = elevation;
1610  }
1611
1612  /**
1613   * Elevation accuracy is the uncertainty for the elevation in meters.
1614   * </br>
1615   * The elevation accuracy is calculated using the equation: (maximumElevationInMeters - minimumElevationInMeters) / 2
1616   */
1617  @Nullable
1618  public Double getElevationAccuracy() {
1619    return elevationAccuracy;
1620  }
1621
1622  public void setElevationAccuracy(@Nullable Double elevationAccuracy) {
1623    this.elevationAccuracy = elevationAccuracy;
1624  }
1625
1626  /**
1627   * Depth in meters below the surface. Complimentary to elevation, the depth can be 10 meters below the surface of a
1628   * lake in 1100m (=elevation).
1629   * </br>
1630   * The depth is calculated using the equation: (minimumDepthInMeters + maximumDepthInMeters) / 2.
1631   */
1632  @Nullable
1633  public Double getDepth() {
1634    return depth;
1635  }
1636
1637  public void setDepth(@Nullable Double depth) {
1638    this.depth = depth;
1639  }
1640
1641  /**
1642   * Depth accuracy is the uncertainty for the depth in meters.
1643   * </br>
1644   * The depth accuracy is calculated using the equation: (maximumDepthInMeters - minimumDepthInMeters) / 2
1645   */
1646  @Nullable
1647  public Double getDepthAccuracy() {
1648    return depthAccuracy;
1649  }
1650
1651  public void setDepthAccuracy(@Nullable Double depthAccuracy) {
1652    this.depthAccuracy = depthAccuracy;
1653  }
1654
1655  @Nullable
1656  public Continent getContinent() {
1657    return continent;
1658  }
1659
1660  public void setContinent(@Nullable Continent continent) {
1661    this.continent = continent;
1662  }
1663
1664  @Schema(
1665    description = "The 2-letter country code (as per ISO-3166-1) of the country, territory or area in which the " +
1666      "occurrence was recorded.",
1667    externalDocs = @ExternalDocumentation(
1668      description = "Darwin Core definition",
1669      url = "https://rs.tdwg.org/dwc/terms/countryCode"
1670    )
1671  )
1672  @Nullable
1673  @JsonProperty("countryCode")
1674  public Country getCountry() {
1675    return country;
1676  }
1677
1678  public void setCountry(@Nullable Country country) {
1679    this.country = country;
1680  }
1681
1682  @Nullable
1683  @JsonProperty("gbifRegion")
1684  public GbifRegion getGbifRegion() {
1685    return Optional.ofNullable(country).map(Country::getGbifRegion).orElse(null);
1686  }
1687
1688  private void setGbifRegion(String gbifRegion) {
1689    // ignore, setter only to avoid JSON being written into the fields map
1690  }
1691
1692  /**
1693   * Renders the country title as a JSON property country in addition to the ISO 3166 2 letter countryCode being
1694   * serialized by the regular country Java property.
1695   * Made private to use it only for JSON serialization and not within Java code.
1696   */
1697  @Schema(
1698    description = "The title (as per ISO-3166-1) of the country, territory or area in which the " +
1699      "occurrence was recorded.",
1700    externalDocs = @ExternalDocumentation(
1701      description = "Darwin Core definition",
1702      url = "https://rs.tdwg.org/dwc/terms/country"
1703    )
1704  )
1705  @Nullable
1706  @JsonProperty("country")
1707  private String getCountryTitle() {
1708    return country == null ? null : country.getTitle();
1709  }
1710
1711  private void setCountryTitle(String country) {
1712    // ignore, setter only to avoid JSON being written into the fields map
1713  }
1714
1715  @Nullable
1716  public String getStateProvince() {
1717    return stateProvince;
1718  }
1719
1720  public void setStateProvince(@Nullable String stateProvince) {
1721    this.stateProvince = stateProvince;
1722  }
1723
1724  @Nullable
1725  public String getWaterBody() {
1726    return waterBody;
1727  }
1728
1729  public void setWaterBody(@Nullable String waterBody) {
1730    this.waterBody = waterBody;
1731  }
1732
1733  /**
1734   * The distance in metres from a known centroid, e.g. a country centroid.
1735   */
1736  public Double getDistanceFromCentroidInMeters() {
1737    return distanceFromCentroidInMeters;
1738  }
1739
1740  public void setDistanceFromCentroidInMeters(Double distanceFromCentroidInMeters) {
1741    this.distanceFromCentroidInMeters = distanceFromCentroidInMeters;
1742  }
1743
1744  @Nullable
1745  public String getHigherGeography() {
1746    return higherGeography;
1747  }
1748
1749  public void setHigherGeography(String higherGeography) {
1750    this.higherGeography = higherGeography;
1751  }
1752
1753  @Nullable
1754  public String getGeoreferencedBy() {
1755    return georeferencedBy;
1756  }
1757
1758  public void setGeoreferencedBy(String georeferencedBy) {
1759    this.georeferencedBy = georeferencedBy;
1760  }
1761
1762  /**
1763   * The full year of the event date.
1764   *
1765   * @return the year of the event date
1766   */
1767  @Min(1500)
1768  @Max(2030)
1769  @Nullable
1770  public Integer getYear() {
1771    return year;
1772  }
1773
1774  public void setYear(@Nullable Integer year) {
1775    this.year = year;
1776  }
1777
1778  /**
1779   * The month of the year of the event date starting with zero for january following {@link Date}.
1780   *
1781   * @return the month of the event date
1782   */
1783  @Min(1)
1784  @Max(12)
1785  @Nullable
1786  public Integer getMonth() {
1787    return month;
1788  }
1789
1790  public void setMonth(@Nullable Integer month) {
1791    this.month = month;
1792  }
1793
1794  /**
1795   * The day of the month of the event date.
1796   *
1797   * @return the day of the event date
1798   */
1799  @Min(1)
1800  @Max(31)
1801  @Nullable
1802  public Integer getDay() {
1803    return day;
1804  }
1805
1806  public void setDay(@Nullable Integer day) {
1807    this.day = day;
1808  }
1809
1810  /**
1811   * The date the occurrence was recorded or collected.
1812   */
1813  @Nullable
1814  public IsoDateInterval getEventDate() {
1815    return eventDate == null ? null : new IsoDateInterval(eventDate.getFrom(), eventDate.getTo());
1816  }
1817
1818  public void setEventDate(@Nullable IsoDateInterval eventDate) {
1819    this.eventDate = eventDate == null ? null : new IsoDateInterval(eventDate.getFrom(), eventDate.getTo());
1820  }
1821
1822  /**
1823   * The earliest integer day of the year of the event.
1824   *
1825   * @return the earliest integer day of the event date
1826   */
1827  @Min(1)
1828  @Max(366)
1829  @Nullable
1830  public Integer getStartDayOfYear() {
1831    return startDayOfYear;
1832  }
1833
1834  public void setStartDayOfYear(@Nullable Integer startDayOfYear) {
1835    this.startDayOfYear = startDayOfYear;
1836  }
1837
1838  /**
1839   * The latest integer day of the year of the event.
1840   *
1841   * @return the latest integer day of the event date
1842   */
1843  @Min(1)
1844  @Max(366)
1845  @Nullable
1846  public Integer getEndDayOfYear() {
1847    return endDayOfYear;
1848  }
1849
1850  public void setEndDayOfYear(@Nullable Integer endDayOfYear) {
1851    this.endDayOfYear = endDayOfYear;
1852  }
1853
1854  @Nullable
1855  public String getTypeStatus() {
1856    return typeStatus;
1857  }
1858
1859  public void setTypeStatus(@Nullable String typeStatus) {
1860    this.typeStatus = typeStatus;
1861  }
1862
1863  /**
1864   * The scientific name the type status of this specimen applies to.
1865   */
1866  @Nullable
1867  public String getTypifiedName() {
1868    return typifiedName;
1869  }
1870
1871  public void setTypifiedName(@Nullable String typifiedName) {
1872    this.typifiedName = typifiedName;
1873  }
1874
1875  /**
1876   * A set of issues found for this occurrence.
1877   */
1878  @NotNull
1879  public Set<OccurrenceIssue> getIssues() {
1880    return issues;
1881  }
1882
1883  public void setIssues(Set<OccurrenceIssue> issues) {
1884    Objects.requireNonNull(issues, "Issues cannot be null");
1885    EnumSet<OccurrenceIssue> set = EnumSet.noneOf(OccurrenceIssue.class);
1886    set.addAll(issues);
1887    this.issues = set;
1888  }
1889
1890  public void addIssue(OccurrenceIssue issue) {
1891    Objects.requireNonNull(issue, "Issue needs to be specified");
1892    issues.add(issue);
1893  }
1894
1895  /**
1896   * The interpreted dc:modified from the verbatim source data.
1897   * Ideally indicating when a record was last modified in the source.
1898   */
1899  @Nullable
1900  public Date getModified() {
1901    return modified == null ? null : new Date(modified.getTime());
1902  }
1903
1904  public void setModified(@Nullable Date modified) {
1905    this.modified = modified == null ? null : new Date(modified.getTime());
1906  }
1907
1908  /**
1909   * The date this occurrence last went through the interpretation phase of the GBIF indexing.
1910   */
1911  @Nullable
1912  public Date getLastInterpreted() {
1913    return lastInterpreted == null ? null : new Date(lastInterpreted.getTime());
1914  }
1915
1916  public void setLastInterpreted(@Nullable Date lastInterpreted) {
1917    this.lastInterpreted = lastInterpreted == null ? null : new Date(lastInterpreted.getTime());
1918  }
1919
1920  /**
1921   * An external link to more details, the records "homepage".
1922   */
1923  @Nullable
1924  public URI getReferences() {
1925    return references;
1926  }
1927
1928  public void setReferences(URI references) {
1929    this.references = references;
1930  }
1931
1932  /**
1933   * A number or enumeration value for the quantity of organisms.
1934   */
1935  @Nullable
1936  public Double getOrganismQuantity() {
1937    return organismQuantity;
1938  }
1939
1940  public void setOrganismQuantity(@Nullable Double organismQuantity) {
1941    this.organismQuantity = organismQuantity;
1942  }
1943
1944  /**
1945   * The type of quantification system used for the quantity of organisms.
1946   */
1947  @Nullable
1948  public String getOrganismQuantityType() {
1949    return organismQuantityType;
1950  }
1951
1952  public void setOrganismQuantityType(@Nullable String organismQuantityType) {
1953    this.organismQuantityType = organismQuantityType;
1954  }
1955
1956  /**
1957   * The unit of measurement of the size (time duration, length, area, or volume) of a sample in a sampling event.
1958   */
1959  @Nullable
1960  public String getSampleSizeUnit() {
1961    return sampleSizeUnit;
1962  }
1963
1964  public void setSampleSizeUnit(@Nullable String sampleSizeUnit) {
1965    this.sampleSizeUnit = sampleSizeUnit;
1966  }
1967
1968  /**
1969   * A numeric value for a measurement of the size (time duration, length, area, or volume) of a sample in a sampling event.
1970   */
1971  @Nullable
1972  public Double getSampleSizeValue() {
1973    return sampleSizeValue;
1974  }
1975
1976  public void setSampleSizeValue(@Nullable Double sampleSizeValue) {
1977    this.sampleSizeValue = sampleSizeValue;
1978  }
1979
1980  /**
1981   * Calculated filed organismQuantity / sampleSizeValue, if the type is identical
1982   */
1983  @Nullable
1984  public Double getRelativeOrganismQuantity() {
1985    return relativeOrganismQuantity;
1986  }
1987
1988  public void setRelativeOrganismQuantity(@Nullable Double relativeOrganismQuantity) {
1989    this.relativeOrganismQuantity = relativeOrganismQuantity;
1990  }
1991
1992  /**
1993   * Flag occurrence when associatedSequences/extension exists
1994   */
1995  public boolean getIsSequenced() {
1996    return isSequenced;
1997  }
1998
1999  public void setIsSequenced(boolean isSequenced) {
2000    this.isSequenced = isSequenced;
2001  }
2002
2003  /**
2004   * A list (concatenated and separated) of identifiers (publication, global unique identifier, URI) of genetic
2005   * sequence information associated with the material entity.
2006   */
2007  @Nullable
2008  public String getAssociatedSequences() {
2009    return associatedSequences;
2010  }
2011
2012  public void setAssociatedSequences(String associatedSequences) {
2013    this.associatedSequences = associatedSequences;
2014  }
2015
2016  /**
2017   * Applied license to the occurrence record or dataset to which this record belongs to.
2018   */
2019  @NotNull
2020  public License getLicense() {
2021    return license;
2022  }
2023
2024  public void setLicense(License license) {
2025    this.license = license;
2026  }
2027
2028  @NotNull
2029  public List<Identifier> getIdentifiers() {
2030    return identifiers;
2031  }
2032
2033  public void setIdentifiers(List<Identifier> identifiers) {
2034    this.identifiers = identifiers;
2035  }
2036
2037  @NotNull
2038  public List<MediaObject> getMedia() {
2039    return media;
2040  }
2041
2042  public void setMedia(List<MediaObject> media) {
2043    this.media = media;
2044  }
2045
2046  @NotNull
2047  public List<MeasurementOrFact> getFacts() {
2048    return facts;
2049  }
2050
2051  public void setFacts(List<MeasurementOrFact> facts) {
2052    this.facts = facts;
2053  }
2054
2055  @NotNull
2056  public List<OccurrenceRelation> getRelations() {
2057    return relations;
2058  }
2059
2060  public void setRelations(List<OccurrenceRelation> relations) {
2061    this.relations = relations;
2062  }
2063
2064  @NotNull
2065  public List<AgentIdentifier> getRecordedByIds() {
2066    return recordedByIds;
2067  }
2068
2069  public void setRecordedByIds(List<AgentIdentifier> recordedByIds) {
2070    this.recordedByIds = recordedByIds;
2071  }
2072
2073  @NotNull
2074  public List<AgentIdentifier> getIdentifiedByIds() {
2075    return identifiedByIds;
2076  }
2077
2078  public void setIdentifiedByIds(List<AgentIdentifier> identifiedByIds) {
2079    this.identifiedByIds = identifiedByIds;
2080  }
2081
2082  @NotNull
2083  public Gadm getGadm() {
2084    return gadm;
2085  }
2086
2087  public void setGadm(Gadm gadm) {
2088    this.gadm = gadm;
2089  }
2090
2091  @Nullable
2092  @Experimental
2093  public String getInstitutionKey() {
2094    return institutionKey;
2095  }
2096
2097  public void setInstitutionKey(String institutionKey) {
2098    this.institutionKey = institutionKey;
2099  }
2100
2101  @Nullable
2102  @Experimental
2103  public String getCollectionKey() {
2104    return collectionKey;
2105  }
2106
2107  public void setCollectionKey(String collectionKey) {
2108    this.collectionKey = collectionKey;
2109  }
2110
2111  public boolean getIsInCluster() {
2112    return isInCluster;
2113  }
2114
2115  public void setIsInCluster(boolean isInCluster) {
2116    this.isInCluster = isInCluster;
2117  }
2118
2119  @Nullable
2120  public String getDegreeOfEstablishment() {
2121    return degreeOfEstablishment;
2122  }
2123
2124  public void setDegreeOfEstablishment(String degreeOfEstablishment) {
2125    this.degreeOfEstablishment = degreeOfEstablishment;
2126  }
2127
2128  @Nullable
2129  public String getPathway() {
2130    return pathway;
2131  }
2132
2133  public void setPathway(String pathway) {
2134    this.pathway = pathway;
2135  }
2136
2137  public String getDatasetID() {
2138    return datasetID;
2139  }
2140
2141  public void setDatasetID(String datasetID) {
2142    this.datasetID = datasetID;
2143  }
2144
2145  public String getDatasetName() {
2146    return datasetName;
2147  }
2148
2149  public void setDatasetName(String datasetName) {
2150    this.datasetName = datasetName;
2151  }
2152
2153  public String getOtherCatalogNumbers() {
2154    return otherCatalogNumbers;
2155  }
2156
2157  public void setOtherCatalogNumbers(String otherCatalogNumbers) {
2158    this.otherCatalogNumbers = otherCatalogNumbers;
2159  }
2160
2161  @Nullable
2162  public String getEarliestEonOrLowestEonothem() {
2163    return earliestEonOrLowestEonothem;
2164  }
2165
2166  public void setEarliestEonOrLowestEonothem(String earliestEonOrLowestEonothem) {
2167    this.earliestEonOrLowestEonothem = earliestEonOrLowestEonothem;
2168  }
2169
2170  @Nullable
2171  public String getLatestEonOrHighestEonothem() {
2172    return latestEonOrHighestEonothem;
2173  }
2174
2175  public void setLatestEonOrHighestEonothem(String latestEonOrHighestEonothem) {
2176    this.latestEonOrHighestEonothem = latestEonOrHighestEonothem;
2177  }
2178
2179  @Nullable
2180  public String getEarliestEraOrLowestErathem() {
2181    return earliestEraOrLowestErathem;
2182  }
2183
2184  public void setEarliestEraOrLowestErathem(String earliestEraOrLowestErathem) {
2185    this.earliestEraOrLowestErathem = earliestEraOrLowestErathem;
2186  }
2187
2188  @Nullable
2189  public String getLatestEraOrHighestErathem() {
2190    return latestEraOrHighestErathem;
2191  }
2192
2193  public void setLatestEraOrHighestErathem(String latestEraOrHighestErathem) {
2194    this.latestEraOrHighestErathem = latestEraOrHighestErathem;
2195  }
2196
2197  @Nullable
2198  public String getEarliestPeriodOrLowestSystem() {
2199    return earliestPeriodOrLowestSystem;
2200  }
2201
2202  public void setEarliestPeriodOrLowestSystem(String earliestPeriodOrLowestSystem) {
2203    this.earliestPeriodOrLowestSystem = earliestPeriodOrLowestSystem;
2204  }
2205
2206  @Nullable
2207  public String getLatestPeriodOrHighestSystem() {
2208    return latestPeriodOrHighestSystem;
2209  }
2210
2211  public void setLatestPeriodOrHighestSystem(String latestPeriodOrHighestSystem) {
2212    this.latestPeriodOrHighestSystem = latestPeriodOrHighestSystem;
2213  }
2214
2215  @Nullable
2216  public String getEarliestEpochOrLowestSeries() {
2217    return earliestEpochOrLowestSeries;
2218  }
2219
2220  public void setEarliestEpochOrLowestSeries(String earliestEpochOrLowestSeries) {
2221    this.earliestEpochOrLowestSeries = earliestEpochOrLowestSeries;
2222  }
2223
2224  @Nullable
2225  public String getLatestEpochOrHighestSeries() {
2226    return latestEpochOrHighestSeries;
2227  }
2228
2229  public void setLatestEpochOrHighestSeries(String latestEpochOrHighestSeries) {
2230    this.latestEpochOrHighestSeries = latestEpochOrHighestSeries;
2231  }
2232
2233  @Nullable
2234  public String getEarliestAgeOrLowestStage() {
2235    return earliestAgeOrLowestStage;
2236  }
2237
2238  public void setEarliestAgeOrLowestStage(String earliestAgeOrLowestStage) {
2239    this.earliestAgeOrLowestStage = earliestAgeOrLowestStage;
2240  }
2241
2242  @Nullable
2243  public String getLatestAgeOrHighestStage() {
2244    return latestAgeOrHighestStage;
2245  }
2246
2247  public void setLatestAgeOrHighestStage(String latestAgeOrHighestStage) {
2248    this.latestAgeOrHighestStage = latestAgeOrHighestStage;
2249  }
2250
2251  @Nullable
2252  public String getLowestBiostratigraphicZone() {
2253    return lowestBiostratigraphicZone;
2254  }
2255
2256  public void setLowestBiostratigraphicZone(String lowestBiostratigraphicZone) {
2257    this.lowestBiostratigraphicZone = lowestBiostratigraphicZone;
2258  }
2259
2260  @Nullable
2261  public String getHighestBiostratigraphicZone() {
2262    return highestBiostratigraphicZone;
2263  }
2264
2265  public void setHighestBiostratigraphicZone(String highestBiostratigraphicZone) {
2266    this.highestBiostratigraphicZone = highestBiostratigraphicZone;
2267  }
2268
2269  @Nullable
2270  public String getGroup() {
2271    return group;
2272  }
2273
2274  public void setGroup(String group) {
2275    this.group = group;
2276  }
2277
2278  @Nullable
2279  public String getFormation() {
2280    return formation;
2281  }
2282
2283  public void setFormation(String formation) {
2284    this.formation = formation;
2285  }
2286
2287  @Nullable
2288  public String getMember() {
2289    return member;
2290  }
2291
2292  public void setMember(String member) {
2293    this.member = member;
2294  }
2295
2296  @Nullable
2297  public String getBed() {
2298    return bed;
2299  }
2300
2301  public void setBed(String bed) {
2302    this.bed = bed;
2303  }
2304
2305  public String getRecordedBy() {
2306    return recordedBy;
2307  }
2308
2309  public void setRecordedBy(String recordedBy) {
2310    this.recordedBy = recordedBy;
2311  }
2312
2313  public String getIdentifiedBy() {
2314    return identifiedBy;
2315  }
2316
2317  public void setIdentifiedBy(String identifiedBy) {
2318    this.identifiedBy = identifiedBy;
2319  }
2320
2321  public String getPreparations() {
2322    return preparations;
2323  }
2324
2325  public void setPreparations(String preparations) {
2326    this.preparations = preparations;
2327  }
2328
2329  public String getSamplingProtocol() {
2330    return samplingProtocol;
2331  }
2332
2333  public void setSamplingProtocol(String samplingProtocol) {
2334    this.samplingProtocol = samplingProtocol;
2335  }
2336
2337  @Deprecated
2338  public List<String> getDnaSequenceID() {
2339    return dnaSequenceID;
2340  }
2341
2342  @Deprecated
2343  public void setDnaSequenceID(List<String> dnaSequenceID) {
2344    this.dnaSequenceID = dnaSequenceID;
2345  }
2346
2347  public Map<String, Classification> getClassifications() {
2348    return classifications;
2349  }
2350
2351  public void setClassifications(Map<String, Classification> classifications) {
2352    this.classifications = classifications;
2353  }
2354
2355  /**
2356   * Convenience method checking if any spatial validation rule has not passed.
2357   * Primarily used to indicate that the record should not be displayed on a map.
2358   */
2359  @JsonIgnore
2360  public boolean hasSpatialIssue() {
2361    for (OccurrenceIssue rule : OccurrenceIssue.GEOSPATIAL_RULES) {
2362      if (issues.contains(rule)) {
2363        return true;
2364      }
2365    }
2366    return false;
2367  }
2368
2369  /**
2370   * This private method is only for serialization via jackson and not exposed anywhere else!
2371   * It maps the verbatimField terms into properties with their simple name or qualified names for UnknownTerms.
2372   */
2373  @JsonAnyGetter
2374  private Map<String, String> jsonVerbatimFields() {
2375    Map<String, String> extendedProps = new HashMap<>();
2376    for (Map.Entry<Term, String> prop : getVerbatimFields().entrySet()) {
2377      Term t = prop.getKey();
2378      if (t instanceof UnknownTerm || PROPERTIES.contains(t.simpleName())) {
2379        extendedProps.put(t.qualifiedName(), prop.getValue());
2380      } else {
2381        // render all terms in controlled enumerations as simple names only - unless we have a property of that name already!
2382        extendedProps.put(t.simpleName(), prop.getValue());
2383      }
2384    }
2385    return extendedProps;
2386  }
2387}