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