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.vocabulary;
015
016import org.gbif.api.util.AnnotationUtils;
017import org.gbif.dwc.terms.DcTerm;
018import org.gbif.dwc.terms.DwcTerm;
019import org.gbif.dwc.terms.Term;
020
021import java.util.Arrays;
022import java.util.Collections;
023import java.util.HashSet;
024import java.util.List;
025import java.util.Set;
026
027import org.apache.commons.lang3.ArrayUtils;
028
029import static org.gbif.api.vocabulary.InterpretationRemarkSeverity.ERROR;
030import static org.gbif.api.vocabulary.InterpretationRemarkSeverity.INFO;
031import static org.gbif.api.vocabulary.InterpretationRemarkSeverity.WARNING;
032
033/**
034 * An enumeration of validation rules for single occurrence records.
035 */
036public enum OccurrenceIssue implements InterpretationRemark {
037
038  /**
039   * Coordinate is the exact 0°, 0° coordinate, often indicating a bad null coordinate.
040   */
041  ZERO_COORDINATE(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
042
043  /**
044   * Coordinate has a latitude and/or longitude value beyond the maximum (or minimum) decimal value.
045   */
046  COORDINATE_OUT_OF_RANGE(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
047
048  /**
049   * Coordinate value is given in some form but GBIF is unable to interpret it.
050   */
051  COORDINATE_INVALID(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
052
053  /**
054   * Original coordinate modified by rounding to 5 decimals.
055   */
056  COORDINATE_ROUNDED(INFO, TermsGroup.COORDINATES_TERMS_NO_DATUM),
057
058  /**
059   * The geodetic datum given could not be interpreted.
060   */
061  GEODETIC_DATUM_INVALID(WARNING, DwcTerm.geodeticDatum),
062
063  /**
064   * Indicating that the interpreted coordinates assume they are based on WGS84 datum as the datum
065   * was either not indicated or interpretable. See GEODETIC_DATUM_INVALID.
066   */
067  GEODETIC_DATUM_ASSUMED_WGS84(INFO, DwcTerm.geodeticDatum),
068
069  /**
070   * The original coordinate was successfully reprojected from a different geodetic datum to WGS84.
071   */
072  COORDINATE_REPROJECTED(INFO, TermsGroup.COORDINATES_TERMS),
073
074  /**
075   * The given decimal latitude and longitude could not be reprojected to WGS84 based on the
076   * provided datum.
077   */
078  COORDINATE_REPROJECTION_FAILED(WARNING, TermsGroup.COORDINATES_TERMS),
079
080  /**
081   * Indicates successful coordinate reprojection according to provided datum, but which results in
082   * a datum shift larger than 0.1 decimal degrees.
083   */
084  COORDINATE_REPROJECTION_SUSPICIOUS(WARNING, TermsGroup.COORDINATES_TERMS),
085
086  /**
087   * Indicates an invalid or very unlikely coordinate accuracy derived from precision or uncertainty
088   * in meters.
089   */
090  @Deprecated //see POR-3061
091  COORDINATE_ACCURACY_INVALID(WARNING),
092
093  /**
094   * Indicates an invalid or very unlikely coordinatePrecision
095   */
096  COORDINATE_PRECISION_INVALID(WARNING, DwcTerm.coordinatePrecision),
097
098  /**
099   * Indicates an invalid or very unlikely dwc:uncertaintyInMeters.
100   */
101  COORDINATE_UNCERTAINTY_METERS_INVALID(WARNING, DwcTerm.coordinateUncertaintyInMeters),
102
103  /**
104   * There is a mismatch between coordinate uncertainty in meters and coordinate precision.
105   */
106  @Deprecated //see POR-1804
107  COORDINATE_PRECISION_UNCERTAINTY_MISMATCH(WARNING),
108
109  /**
110   * The Footprint Spatial Reference System given could not be interpreted.
111   */
112  FOOTPRINT_SRS_INVALID(WARNING, DwcTerm.footprintSRS),
113
114  /**
115   * The Footprint Well-Known-Text conflicts with the interpreted coordinates
116   * (Decimal Latitude, Decimal Longitude etc).
117   */
118  FOOTPRINT_WKT_MISMATCH(WARNING, DwcTerm.footprintWKT),
119
120  /**
121   * The Footprint Well-Known-Text given could not be interpreted.
122   */
123  FOOTPRINT_WKT_INVALID(WARNING, DwcTerm.footprintWKT),
124
125  /**
126   * The interpreted occurrence coordinates fall outside of the indicated country.
127   */
128  COUNTRY_COORDINATE_MISMATCH(WARNING, TermsGroup.COORDINATES_COUNTRY_TERMS),
129
130  /**
131   * Interpreted country for dwc:country and dwc:countryCode contradict each other.
132   */
133  COUNTRY_MISMATCH(WARNING, TermsGroup.COUNTRY_TERMS),
134
135  /**
136   * Uninterpretable country values found.
137   */
138  COUNTRY_INVALID(WARNING, TermsGroup.COUNTRY_TERMS),
139
140  /**
141   * The interpreted country is based on the coordinates, not the verbatim string information.
142   */
143  COUNTRY_DERIVED_FROM_COORDINATES(WARNING, TermsGroup.COORDINATES_COUNTRY_TERMS),
144
145  /**
146   * The interpreted occurrence coordinates fall outside of the indicated continent.
147   */
148  CONTINENT_COORDINATE_MISMATCH(WARNING),
149
150  /**
151   * The interpreted continent and country do not match.
152   */
153  CONTINENT_COUNTRY_MISMATCH(WARNING),
154
155  /**
156   * Uninterpretable continent values found.
157   */
158  CONTINENT_INVALID(WARNING),
159
160  /**
161   * The interpreted continent is based on the country, not the verbatim string information.
162   */
163  CONTINENT_DERIVED_FROM_COUNTRY(WARNING),
164
165  /**
166   * The interpreted continent is based on the coordinates, not the verbatim string information.
167   */
168  CONTINENT_DERIVED_FROM_COORDINATES(WARNING),
169
170  /**
171   * Latitude and longitude appear to be swapped.
172   */
173  PRESUMED_SWAPPED_COORDINATE(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
174
175  /**
176   * Longitude appears to be negated, e.g. 32.3 instead of -32.3
177   */
178  PRESUMED_NEGATED_LONGITUDE(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
179
180  /**
181   * Latitude appears to be negated, e.g. 32.3 instead of -32.3
182   */
183  PRESUMED_NEGATED_LATITUDE(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
184
185  /**
186   * The recorded date specified as the eventDate string and the individual year, month, day and/or
187   * startDayOfYear, endDayOfYear are contradictory.
188   */
189  RECORDED_DATE_MISMATCH(WARNING, TermsGroup.RECORDED_DATE_TERMS),
190
191  /**
192   * A (partial) invalid date is given, such as a non-existent date, zero month, etc.
193   */
194  RECORDED_DATE_INVALID(WARNING, TermsGroup.RECORDED_DATE_TERMS),
195
196  /**
197   * The recorded date is highly unlikely, falling either into the future or representing a very old
198   * date before 1600 thus predating modern taxonomy.
199   */
200  RECORDED_DATE_UNLIKELY(WARNING, TermsGroup.RECORDED_DATE_TERMS),
201
202  /**
203   * Matching to the taxonomic backbone can only be done using a fuzzy, non-exact match.
204   */
205  TAXON_MATCH_FUZZY(WARNING, TermsGroup.TAXONOMY_TERMS),
206
207  /**
208   * Matching to the taxonomic backbone can only be done on a higher rank and not the scientific
209   * name.
210   */
211  TAXON_MATCH_HIGHERRANK(WARNING, TermsGroup.TAXONOMY_TERMS),
212
213  /**
214   * Matching to the taxonomic backbone can only be done on a species level,
215   * but the occurrence was in fact considered a broader species aggregate/complex.
216   * @see <a href="https://github.com/gbif/portal-feedback/issues/2935">gbif/portal-feedback#2935</a>
217   */
218  TAXON_MATCH_AGGREGATE(WARNING, TermsGroup.TAXONOMY_TERMS),
219
220  /**
221   * The scientificNameID was not used when mapping the record to the GBIF backbone. This may indicate one of
222   * <ul>
223   *   <li>The ID uses a pattern not configured for use by GBIF</li>
224   *   <li>The ID did not uniquely(!) identify a concept in the checklist</li>
225   *   <li>The ID found a concept in the checklist which did not map to the backbone</li>
226   *   <li>A different ID was used, or the record names were used as no ID lookup successfully linked to the backbone</li>
227   * </ul>
228   * @see <a href="https://github.com/gbif/pipelines/issues/217">gbif/pipelines#217</a>
229   */
230  TAXON_MATCH_SCIENTIFIC_NAME_ID_IGNORED(INFO, DwcTerm.scientificNameID),
231
232  /**
233   * The taxonConceptID was not used when mapping the record to the GBIF backbone. This may indicate one of
234   * <ul>
235   *   <li>The ID uses a pattern not configured for use by GBIF</li>
236   *   <li>The ID did not uniquely(!) identify a concept in the checklist</li>
237   *   <li>The ID found a concept in the checklist which did not map to the backbone</li>
238   *   <li>A different ID was used, or the record names were used as no ID lookup successfully linked to the backbone</li>
239   * </ul>
240   * @see <a href="https://github.com/gbif/pipelines/issues/217">gbif/pipelines#217</a>
241   */
242  TAXON_MATCH_TAXON_CONCEPT_ID_IGNORED(INFO, DwcTerm.taxonConceptID),
243
244  /**
245   * The taxonID was not used when mapping the record to the GBIF backbone. This may indicate one of
246   * <ul>
247   *   <li>The ID uses a pattern not configured for use by GBIF</li>
248   *   <li>The ID did not uniquely(!) identify a concept in the checklist</li>
249   *   <li>The ID found a concept in the checklist which did not map to the backbone</li>
250   *   <li>A different ID was used, or the record names were used as no ID lookup successfully linked to the backbone</li>
251   * </ul>
252   * @see <a href="https://github.com/gbif/pipelines/issues/217">gbif/pipelines#217</a>
253   */
254  TAXON_MATCH_TAXON_ID_IGNORED(INFO, DwcTerm.taxonID),
255
256  /**
257   * The scientificNameID matched a known pattern, but it was not found in the associated checklist.
258   * The backbone lookup was performed using either the names or a different ID on the record.
259   * This may indicate a poorly formatted identifier or may be caused by a newly created ID that
260   * isn't yet known in the version of the published checklist.
261   * @see <a href="https://github.com/gbif/pipelines/issues/217">gbif/pipelines#217</a>
262   */
263  SCIENTIFIC_NAME_ID_NOT_FOUND(WARNING, DwcTerm.scientificNameID),
264
265  /**
266   * The taxonConceptID matched a known pattern, but it was not found in the associated checklist.
267   * The backbone lookup was performed using either the names or a different ID on the record.
268   * This may indicate a poorly formatted identifier or may be caused by a newly created ID that isn't yet
269   * known in the version of the published checklist.
270   * @see <a href="https://github.com/gbif/pipelines/issues/217">gbif/pipelines#217</a>
271   */
272  TAXON_CONCEPT_ID_NOT_FOUND(WARNING, DwcTerm.taxonConceptID),
273
274  /**
275   * The taxonID matched a known pattern, but it was not found in the associated checklist.
276   * The backbone lookup was performed using either the names or a different ID on the record.
277   * This may indicate a poorly formatted identifier or may be caused by a newly created ID that isn't yet
278   * known in the version of the published checklist.
279   * @see <a href="https://github.com/gbif/pipelines/issues/217">gbif/pipelines#217</a>
280   */
281  TAXON_ID_NOT_FOUND(WARNING, DwcTerm.taxonID),
282
283  /**
284   * The scientificName provided in the occurrence record does not precisely match the name in the registered checklist
285   * when using the scientificNameID, taxonID or taxonConceptID to look it up. Publishers are advised to check the IDs
286   * are correct, or update the formatting of the names on their records.
287   * @see <a href="https://github.com/gbif/pipelines/issues/217">gbif/pipelines#217</a>
288   */
289  SCIENTIFIC_NAME_AND_ID_INCONSISTENT(WARNING, DwcTerm.scientificNameID, DwcTerm.taxonID, DwcTerm.taxonConceptID, DwcTerm.scientificName),
290
291  /**
292   * Matching to the taxonomic backbone cannot be done because there was no match at all, or several
293   * matches with too little information to keep them apart (potentially homonyms).
294   */
295  TAXON_MATCH_NONE(WARNING, TermsGroup.TAXONOMY_TERMS),
296
297  /**
298   * The GBIF Backbone concept was found using the scientificNameID, taxonID or taxonConceptID, but it differs from what would
299   * have been found if the classification names on the record were used. This may indicate a gap in the GBIF backbone,
300   * a poor mapping between the checklist and the backbone, or a mismatch between the classification names and the
301   * declared IDs (scientificNameID or taxonConceptID) on the occurrence record itself.
302   * @see <a href="https://github.com/gbif/pipelines/issues/217">gbif/pipelines#217</a>
303   */
304  TAXON_MATCH_NAME_AND_ID_AMBIGUOUS(WARNING, TermsGroup.TAXONOMY_TERMS),
305
306  /**
307   * Set if supplied depth is not given in the metric system, for example using feet instead of
308   * meters
309   */
310  DEPTH_NOT_METRIC(WARNING, DwcTerm.minimumDepthInMeters, DwcTerm.maximumDepthInMeters),
311
312  /**
313   * Set if depth is larger than 11,000m or negative.
314   */
315  DEPTH_UNLIKELY(WARNING, DwcTerm.minimumDepthInMeters, DwcTerm.maximumDepthInMeters),
316
317  /**
318   * Set if supplied minimum depth > maximum depth
319   */
320  DEPTH_MIN_MAX_SWAPPED(WARNING, DwcTerm.minimumDepthInMeters, DwcTerm.maximumDepthInMeters),
321
322  /**
323   * Set if depth is a non-numeric value
324   */
325  DEPTH_NON_NUMERIC(WARNING, DwcTerm.minimumDepthInMeters, DwcTerm.maximumDepthInMeters),
326
327  /**
328   * Set if elevation is above the troposphere (17km) or below 11km (Mariana Trench).
329   */
330  ELEVATION_UNLIKELY(WARNING, DwcTerm.minimumElevationInMeters, DwcTerm.maximumElevationInMeters),
331
332  /**
333   * Set if supplied minimum elevation > maximum elevation
334   */
335  ELEVATION_MIN_MAX_SWAPPED(WARNING, DwcTerm.minimumElevationInMeters,
336    DwcTerm.maximumElevationInMeters),
337
338  /**
339   * Set if supplied elevation is not given in the metric system, for example using feet instead of
340   * meters
341   */
342  ELEVATION_NOT_METRIC(WARNING, DwcTerm.minimumElevationInMeters, DwcTerm.maximumElevationInMeters),
343
344  /**
345   * Set if elevation is a non-numeric value
346   */
347  ELEVATION_NON_NUMERIC(WARNING, DwcTerm.minimumElevationInMeters,
348    DwcTerm.maximumElevationInMeters),
349
350  /**
351   * A (partial) invalid date is given for dc:modified, such as a nonexistent date, zero month, etc.
352   */
353  MODIFIED_DATE_INVALID(WARNING, DcTerm.modified),
354
355  /**
356   * The date given for dc:modified is in the future or predates Unix time (1970).
357   */
358  MODIFIED_DATE_UNLIKELY(WARNING, DcTerm.modified),
359
360  /**
361   * The date given for dwc:dateIdentified is in the future or before Linnean times (1700).
362   */
363  IDENTIFIED_DATE_UNLIKELY(WARNING, DwcTerm.dateIdentified),
364
365  /**
366   * The date given for dwc:dateIdentified is invalid and can't be interpreted at all.
367   */
368  IDENTIFIED_DATE_INVALID(WARNING, DwcTerm.dateIdentified),
369
370  /**
371   * The given basis of record is impossible to interpret or significantly different from the
372   * recommended vocabulary.
373   */
374  BASIS_OF_RECORD_INVALID(WARNING, DwcTerm.basisOfRecord),
375
376  /**
377   * The given type status is impossible to interpret or significantly different from the
378   * recommended vocabulary.
379   */
380  TYPE_STATUS_INVALID(WARNING, DwcTerm.typeStatus),
381
382  /**
383   * The given type status contains some words that express uncertainty.
384   */
385  SUSPECTED_TYPE(WARNING, DwcTerm.typeStatus),
386  /**
387   * An invalid date is given for dc:created of a multimedia object.
388   */
389  MULTIMEDIA_DATE_INVALID(WARNING),
390
391  /**
392   * An invalid URI is given for a multimedia object.
393   */
394  MULTIMEDIA_URI_INVALID(WARNING),
395
396  /**
397   * An invalid URI is given for dc:references.
398   */
399  REFERENCES_URI_INVALID(WARNING, DcTerm.references),
400
401  /**
402   * An error occurred during interpretation, leaving the record interpretation incomplete.
403   */
404  INTERPRETATION_ERROR(ERROR),
405
406  /**
407   * The individual count value is not a positive integer
408   */
409  INDIVIDUAL_COUNT_INVALID(WARNING, DwcTerm.individualCount),
410
411  /**
412   * Example: individual count value > 0, but occurrence status is absent.
413   */
414  INDIVIDUAL_COUNT_CONFLICTS_WITH_OCCURRENCE_STATUS(WARNING, DwcTerm.individualCount),
415
416  /**
417   * Occurrence status value can't be assigned to {@link OccurrenceStatus}
418   */
419  OCCURRENCE_STATUS_UNPARSABLE(WARNING, DwcTerm.occurrenceStatus),
420
421  /**
422   * Occurrence status was inferred from the individual count value
423   */
424  OCCURRENCE_STATUS_INFERRED_FROM_INDIVIDUAL_COUNT(WARNING, DwcTerm.occurrenceStatus),
425
426  /**
427   * Occurrence status was inferred from basis of records
428   */
429  OCCURRENCE_STATUS_INFERRED_FROM_BASIS_OF_RECORD(WARNING, DwcTerm.occurrenceStatus),
430
431  /**
432   * The date given for dwc:georeferencedDate is in the future or before Linnean times (1700).
433   */
434  GEOREFERENCED_DATE_UNLIKELY(WARNING, DwcTerm.georeferencedDate),
435
436  /**
437   * The date given for dwc:georeferencedDate is invalid and can't be interpreted at all.
438   */
439  GEOREFERENCED_DATE_INVALID(WARNING, DwcTerm.georeferencedDate),
440
441  /**
442   * The given institution matches with more than 1 GRSciColl institution.
443   */
444  AMBIGUOUS_INSTITUTION(INFO, TermsGroup.INSTITUTION_TERMS),
445
446  /**
447   * The given collection matches with more than 1 GRSciColl collection.
448   */
449  AMBIGUOUS_COLLECTION(INFO, TermsGroup.COLLECTION_TERMS),
450
451  /**
452   * The given institution couldn't be matched with any GRSciColl institution.
453   */
454  INSTITUTION_MATCH_NONE(INFO, TermsGroup.INSTITUTION_TERMS),
455
456  /**
457   * The given collection couldn't be matched with any GRSciColl collection.
458   */
459  COLLECTION_MATCH_NONE(INFO, TermsGroup.COLLECTION_TERMS),
460
461  /**
462   * The given institution was fuzzily matched to a GRSciColl institution. This can happen when
463   * either the code or the ID don't match or when the institution name is used instead of the code.
464   */
465  INSTITUTION_MATCH_FUZZY(INFO, TermsGroup.INSTITUTION_TERMS),
466
467  /**
468   * The given collection was fuzzily matched to a GRSciColl collection. This can happen when either
469   * the code or the ID don't match or when the collection name is used instead of the code.
470   */
471  COLLECTION_MATCH_FUZZY(INFO, TermsGroup.COLLECTION_TERMS),
472
473  /** The collection matched doesn't belong to the institution matched. */
474  INSTITUTION_COLLECTION_MISMATCH(
475    INFO, ArrayUtils.addAll(TermsGroup.INSTITUTION_TERMS, TermsGroup.INSTITUTION_TERMS)),
476
477  /**
478   * The given owner institution is different than the given institution. Therefore we assume it
479   * could be on loan and we don't link it to the occurrence.
480   *
481   * Deprecated by {@link #DIFFERENT_OWNER_INSTITUTION}.
482   */
483  @Deprecated
484  POSSIBLY_ON_LOAN(INFO, TermsGroup.INSTITUTION_TERMS),
485
486  /**
487   * The given owner institution is different than the given institution. Therefore we assume it
488   * doesn't belong to the institution and we don't link it to the occurrence.
489   */
490  DIFFERENT_OWNER_INSTITUTION(INFO, TermsGroup.INSTITUTION_TERMS);
491
492  /**
493   * Simple helper nested class to allow grouping of Term mostly to increase readability of this
494   * class.
495   */
496  private static class TermsGroup {
497
498    static final Term[] COORDINATES_TERMS_NO_DATUM = {
499      DwcTerm.decimalLatitude,
500      DwcTerm.decimalLongitude,
501      DwcTerm.verbatimLatitude,
502      DwcTerm.verbatimLongitude,
503      DwcTerm.verbatimCoordinates
504    };
505
506    static final Term[] COORDINATES_TERMS = {
507      DwcTerm.decimalLatitude,
508      DwcTerm.decimalLongitude,
509      DwcTerm.verbatimLatitude,
510      DwcTerm.verbatimLongitude,
511      DwcTerm.verbatimCoordinates,
512      DwcTerm.geodeticDatum
513    };
514
515    static final Term[] COUNTRY_TERMS = {
516      DwcTerm.country,
517      DwcTerm.countryCode
518    };
519
520    static final Term[] COORDINATES_COUNTRY_TERMS = {
521      DwcTerm.decimalLatitude,
522      DwcTerm.decimalLongitude,
523      DwcTerm.verbatimLatitude,
524      DwcTerm.verbatimLongitude,
525      DwcTerm.verbatimCoordinates,
526      DwcTerm.geodeticDatum,
527      DwcTerm.country,
528      DwcTerm.countryCode
529    };
530
531    static final Term[] RECORDED_DATE_TERMS = {
532      DwcTerm.eventDate,
533      DwcTerm.year, DwcTerm.month, DwcTerm.day,
534      DwcTerm.startDayOfYear, DwcTerm.endDayOfYear
535    };
536
537    static final Term[] TAXONOMY_TERMS = {
538      DwcTerm.kingdom,
539      DwcTerm.phylum,
540      DwcTerm.class_,
541      DwcTerm.order,
542      DwcTerm.family,
543      DwcTerm.genus,
544      DwcTerm.scientificName,
545      DwcTerm.scientificNameAuthorship,
546      DwcTerm.genericName,
547      DwcTerm.specificEpithet,
548      DwcTerm.infraspecificEpithet,
549      DwcTerm.scientificNameID,
550      DwcTerm.taxonConceptID,
551    };
552
553    static final Term[] INSTITUTION_TERMS = {
554      DwcTerm.institutionCode, DwcTerm.institutionID, DwcTerm.ownerInstitutionCode
555    };
556
557    static final Term[] COLLECTION_TERMS = {DwcTerm.collectionCode, DwcTerm.collectionID};
558  }
559
560  private final Set<Term> relatedTerms;
561  private final InterpretationRemarkSeverity severity;
562  private final boolean isDeprecated;
563
564  /**
565   * {@link OccurrenceIssue} not linked to any specific {@link Term}.
566   */
567  OccurrenceIssue(InterpretationRemarkSeverity severity) {
568    this.severity = severity;
569    this.relatedTerms = Collections.emptySet();
570    this.isDeprecated = AnnotationUtils.isFieldDeprecated(OccurrenceIssue.class, this.name());
571  }
572
573  /**
574   * {@link OccurrenceIssue} linked to the provided {@link Term}.
575   */
576  OccurrenceIssue(InterpretationRemarkSeverity severity, Term... relatedTerms) {
577    this.severity = severity;
578    this.relatedTerms = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(relatedTerms)));
579    this.isDeprecated = AnnotationUtils.isFieldDeprecated(OccurrenceIssue.class, this.name());
580  }
581
582  @Override
583  public String getId() {
584    return name();
585  }
586
587  @Override
588  public Set<Term> getRelatedTerms() {
589    return relatedTerms;
590  }
591
592  @Override
593  public InterpretationRemarkSeverity getSeverity() {
594    return severity;
595  }
596
597  @Override
598  public boolean isDeprecated() {
599    return isDeprecated;
600  }
601
602  /**
603   * All issues that indicate problems with the coordinates and thus should not be shown on maps.
604   */
605  public static final List<OccurrenceIssue> GEOSPATIAL_RULES =
606    Collections.unmodifiableList(
607      Arrays.asList(
608        ZERO_COORDINATE,
609        COORDINATE_OUT_OF_RANGE,
610        COORDINATE_INVALID,
611        COUNTRY_COORDINATE_MISMATCH,
612        PRESUMED_SWAPPED_COORDINATE,
613        PRESUMED_NEGATED_LONGITUDE,
614        PRESUMED_NEGATED_LATITUDE));
615}