001package org.gbif.api.vocabulary;
002
003import org.gbif.dwc.terms.DcTerm;
004import org.gbif.dwc.terms.DwcTerm;
005import org.gbif.dwc.terms.GbifTerm;
006import org.gbif.dwc.terms.Term;
007import org.gbif.utils.AnnotationUtils;
008
009import java.util.Collections;
010import java.util.List;
011import java.util.Set;
012
013import com.google.common.collect.ImmutableList;
014import com.google.common.collect.ImmutableSet;
015
016import static org.gbif.api.vocabulary.InterpretationRemarkSeverity.ERROR;
017import static org.gbif.api.vocabulary.InterpretationRemarkSeverity.INFO;
018import static org.gbif.api.vocabulary.InterpretationRemarkSeverity.WARNING;
019
020/**
021 * An enumeration of validation rules for single occurrence records.
022 */
023public enum OccurrenceIssue implements InterpretationRemark {
024
025  /**
026   * Coordinate is the exact 0/0 coordinate, often indicating a bad null coordinate.
027   */
028  ZERO_COORDINATE(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
029
030  /**
031   * Coordinate has invalid lat/lon values out of their decimal max range.
032   */
033  COORDINATE_OUT_OF_RANGE(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
034
035  /**
036   * Coordinate value given in some form but GBIF is unable to interpret it.
037   */
038  COORDINATE_INVALID(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
039
040  /**
041   * Original coordinate modified by rounding to 5 decimals.
042   */
043  COORDINATE_ROUNDED(INFO, TermsGroup.COORDINATES_TERMS_NO_DATUM),
044
045  /**
046   * The geodetic datum given could not be interpreted.
047   */
048  GEODETIC_DATUM_INVALID(WARNING, DwcTerm.geodeticDatum),
049
050  /**
051   * Indicating that the interpreted coordinates assume they are based on WGS84 datum as the datum was either not
052   * indicated or interpretable. See GEODETIC_DATUM_INVALID.
053   */
054  GEODETIC_DATUM_ASSUMED_WGS84(INFO, DwcTerm.geodeticDatum),
055
056  /**
057   * The original coordinate was successfully reprojected from a different geodetic datum to WGS84.
058   */
059  COORDINATE_REPROJECTED(INFO, TermsGroup.COORDINATES_TERMS),
060
061  /**
062   * The given decimal latitude and longitude could not be reprojected to WGS84 based on the provided datum.
063   */
064  COORDINATE_REPROJECTION_FAILED(WARNING, TermsGroup.COORDINATES_TERMS),
065
066  /**
067   * Indicates successful coordinate reprojection according to provided datum, but which results in a datum shift
068   * larger than 0.1 decimal degrees.
069   */
070  COORDINATE_REPROJECTION_SUSPICIOUS(WARNING, TermsGroup.COORDINATES_TERMS),
071
072  /**
073   * Indicates an invalid or very unlikely coordinate accuracy derived from precision or uncertainty in meters.
074   */
075  @Deprecated //see POR-3061
076  COORDINATE_ACCURACY_INVALID(WARNING),
077
078  /**
079   * Indicates an invalid or very unlikely coordinatePrecision
080   */
081  COORDINATE_PRECISION_INVALID(WARNING, DwcTerm.coordinatePrecision),
082
083  /**
084   * Indicates an invalid or very unlikely dwc:uncertaintyInMeters.
085   */
086  COORDINATE_UNCERTAINTY_METERS_INVALID(WARNING, DwcTerm.coordinateUncertaintyInMeters),
087
088  /**
089   * There is a mismatch between coordinate uncertainty in meters and coordinate precision.
090   */
091  @Deprecated //see POR-1804
092  COORDINATE_PRECISION_UNCERTAINTY_MISMATCH(WARNING),
093
094  /**
095   * The interpreted occurrence coordinates fall outside of the indicated country.
096   */
097  COUNTRY_COORDINATE_MISMATCH(WARNING, TermsGroup.COORDINATES_COUNTRY_TERMS),
098
099  /**
100   * Interpreted country for dwc:country and dwc:countryCode contradict each other.
101   */
102  COUNTRY_MISMATCH(WARNING, TermsGroup.COUNTRY_TERMS),
103
104  /**
105   * Uninterpretable country values found.
106   */
107  COUNTRY_INVALID(WARNING, TermsGroup.COUNTRY_TERMS),
108
109  /**
110   * The interpreted country is based on the coordinates, not the verbatim string information.
111   */
112  COUNTRY_DERIVED_FROM_COORDINATES(WARNING, TermsGroup.COORDINATES_COUNTRY_TERMS),
113
114  /**
115   * The interpreted continent and country do not match up.
116   */
117  CONTINENT_COUNTRY_MISMATCH(WARNING),
118
119  /**
120   * Uninterpretable continent values found.
121   */
122  CONTINENT_INVALID(WARNING),
123
124  /**
125   * The interpreted continent is based on the coordinates, not the verbatim string information.
126   */
127  CONTINENT_DERIVED_FROM_COORDINATES(WARNING),
128
129  /**
130   * Latitude and longitude appear to be swapped.
131   */
132  PRESUMED_SWAPPED_COORDINATE(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
133
134  /**
135   * Longitude appears to be negated, e.g. 32.3 instead of -32.3
136   */
137  PRESUMED_NEGATED_LONGITUDE(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
138
139  /**
140   * Latitude appears to be negated, e.g. 32.3 instead of -32.3
141   */
142  PRESUMED_NEGATED_LATITUDE(WARNING, TermsGroup.COORDINATES_TERMS_NO_DATUM),
143
144  /**
145   * The recording date specified as the eventDate string and the individual year, month, day are contradicting.
146   */
147  RECORDED_DATE_MISMATCH(WARNING, TermsGroup.RECORDED_DATE_TERMS),
148
149  /**
150   * A (partial) invalid date is given, such as a non existing date, invalid zero month, etc.
151   */
152  RECORDED_DATE_INVALID(WARNING, TermsGroup.RECORDED_DATE_TERMS),
153
154  /**
155   * The recording date is highly unlikely, falling either into the future
156   * or represents a very old date before 1600 that predates modern taxonomy.
157   */
158  RECORDED_DATE_UNLIKELY(WARNING, TermsGroup.RECORDED_DATE_TERMS),
159
160  /**
161   * Matching to the taxonomic backbone can only be done using a fuzzy, non exact match.
162   */
163  TAXON_MATCH_FUZZY(WARNING, TermsGroup.TAXONOMY_TERMS),
164
165  /**
166   * Matching to the taxonomic backbone can only be done on a higher rank and not the scientific name.
167   */
168  TAXON_MATCH_HIGHERRANK(WARNING, TermsGroup.TAXONOMY_TERMS),
169
170  /**
171   * Matching to the taxonomic backbone cannot be done cause there was no match at all
172   * or several matches with too little information to keep them apart (homonyms).
173   */
174  TAXON_MATCH_NONE(WARNING, TermsGroup.TAXONOMY_TERMS),
175
176  /**
177   * Set if supplied depth is not given in the metric system, for example using feet instead of meters
178   */
179  DEPTH_NOT_METRIC(WARNING, DwcTerm.minimumDepthInMeters, DwcTerm.maximumDepthInMeters),
180
181  /**
182   * Set if depth is larger than 11.000m or negative.
183   */
184  DEPTH_UNLIKELY(WARNING, DwcTerm.minimumDepthInMeters, DwcTerm.maximumDepthInMeters),
185
186  /**
187   * Set if supplied min>max
188   */
189  DEPTH_MIN_MAX_SWAPPED(WARNING, DwcTerm.minimumDepthInMeters, DwcTerm.maximumDepthInMeters),
190
191  /**
192   * Set if depth is a non numeric value
193   */
194  DEPTH_NON_NUMERIC(WARNING, DwcTerm.minimumDepthInMeters, DwcTerm.maximumDepthInMeters),
195
196  /**
197   * Set if elevation is above the troposphere (17km) or below 11km (Mariana Trench).
198   */
199  ELEVATION_UNLIKELY(WARNING, DwcTerm.minimumElevationInMeters, DwcTerm.maximumElevationInMeters),
200
201  /**
202   * Set if supplied min > max elevation
203   */
204  ELEVATION_MIN_MAX_SWAPPED(WARNING, DwcTerm.minimumElevationInMeters, DwcTerm.maximumElevationInMeters),
205
206  /**
207   * Set if supplied elevation is not given in the metric system, for example using feet instead of meters
208   */
209  ELEVATION_NOT_METRIC(WARNING, DwcTerm.minimumElevationInMeters, DwcTerm.maximumElevationInMeters),
210
211  /**
212   * Set if elevation is a non numeric value
213   */
214  ELEVATION_NON_NUMERIC(WARNING, DwcTerm.minimumElevationInMeters, DwcTerm.maximumElevationInMeters),
215
216  /**
217   * A (partial) invalid date is given for dc:modified, such as a non existing date, invalid zero month, etc.
218   */
219  MODIFIED_DATE_INVALID(WARNING, DcTerm.modified),
220
221  /**
222   * The date given for dc:modified is in the future or predates unix time (1970).
223   */
224  MODIFIED_DATE_UNLIKELY(WARNING, DcTerm.modified),
225
226  /**
227   * The date given for dwc:dateIdentified is in the future or before Linnean times (1700).
228   */
229  IDENTIFIED_DATE_UNLIKELY(WARNING, DwcTerm.dateIdentified),
230
231  /**
232   * The date given for dwc:dateIdentified is invalid and cant be interpreted at all.
233   */
234  IDENTIFIED_DATE_INVALID(WARNING, DwcTerm.dateIdentified),
235
236  /**
237   * The given basis of record is impossible to interpret or seriously different from the recommended vocabulary.
238   */
239  BASIS_OF_RECORD_INVALID(WARNING, DwcTerm.basisOfRecord),
240
241  /**
242   * The given type status is impossible to interpret or seriously different from the recommended vocabulary.
243   */
244  TYPE_STATUS_INVALID(WARNING, DwcTerm.typeStatus),
245
246  /**
247   * An invalid date is given for dc:created of a multimedia object.
248   */
249  MULTIMEDIA_DATE_INVALID(WARNING) ,
250
251  /**
252   * An invalid uri is given for a multimedia object.
253   */
254  MULTIMEDIA_URI_INVALID(WARNING),
255
256  /**
257   * An invalid uri is given for dc:references.
258   */
259  REFERENCES_URI_INVALID(WARNING, DcTerm.references),
260
261  /**
262   * An error occurred during interpretation, leaving the record interpretation incomplete.
263   */
264  INTERPRETATION_ERROR(ERROR),
265
266  /**
267   * Individual count value not parsable into an integer.
268   */
269  INDIVIDUAL_COUNT_INVALID(WARNING, DwcTerm.individualCount);
270
271
272  /**
273   * Simple helper nested class to allow grouping of Term mostly to increase readability of this class.
274   */
275  private static class TermsGroup {
276
277    static final Term[] COORDINATES_TERMS_NO_DATUM = {
278            DwcTerm.decimalLatitude,
279            DwcTerm.decimalLongitude,
280            DwcTerm.verbatimLatitude,
281            DwcTerm.verbatimLongitude,
282            DwcTerm.verbatimCoordinates
283    };
284
285    static final Term[] COORDINATES_TERMS = {
286            DwcTerm.decimalLatitude,
287            DwcTerm.decimalLongitude,
288            DwcTerm.verbatimLatitude,
289            DwcTerm.verbatimLongitude,
290            DwcTerm.verbatimCoordinates,
291            DwcTerm.geodeticDatum
292    };
293
294    static final  Term[] COUNTRY_TERMS = {
295            DwcTerm.country,
296            DwcTerm.countryCode
297    };
298
299    static final  Term[] COORDINATES_COUNTRY_TERMS = {
300            DwcTerm.decimalLatitude,
301            DwcTerm.decimalLongitude,
302            DwcTerm.verbatimLatitude,
303            DwcTerm.verbatimLongitude,
304            DwcTerm.verbatimCoordinates,
305            DwcTerm.geodeticDatum,
306            DwcTerm.country,
307            DwcTerm.countryCode
308    };
309
310    static final  Term[] RECORDED_DATE_TERMS = {
311            DwcTerm.eventDate,
312            DwcTerm.year, DwcTerm.month, DwcTerm.day
313    };
314
315    static final  Term[] TAXONOMY_TERMS = {
316            DwcTerm.kingdom,
317            DwcTerm.phylum,
318            DwcTerm.class_,
319            DwcTerm.order,
320            DwcTerm.family,
321            DwcTerm.genus,
322            DwcTerm.scientificName,
323            DwcTerm.scientificNameAuthorship,
324            GbifTerm.genericName,
325            DwcTerm.specificEpithet,
326            DwcTerm.infraspecificEpithet
327    };
328  }
329
330  private final Set<Term> relatedTerms;
331  private final InterpretationRemarkSeverity severity;
332  private final boolean isDeprecated;
333
334  /**
335   * {@link OccurrenceIssue} not linked to any specific {@link Term}.
336   */
337  OccurrenceIssue(InterpretationRemarkSeverity severity) {
338    this.severity = severity;
339    this.relatedTerms = Collections.emptySet();
340    this.isDeprecated = AnnotationUtils.isFieldDeprecated(OccurrenceIssue.class, this.name());
341  }
342
343  /**
344   * {@link OccurrenceIssue} linked to the provided {@link Term}.
345   *
346   * @param relatedTerms
347   */
348  OccurrenceIssue(InterpretationRemarkSeverity severity, Term... relatedTerms) {
349    this.severity = severity;
350    this.relatedTerms = ImmutableSet.<Term>builder().add(relatedTerms).build();
351    this.isDeprecated = AnnotationUtils.isFieldDeprecated(OccurrenceIssue.class, this.name());
352  }
353
354  @Override
355  public String getId() {
356    return name();
357  }
358
359  @Override
360  public Set<Term> getRelatedTerms(){
361    return relatedTerms;
362  }
363
364  @Override
365  public InterpretationRemarkSeverity getSeverity(){
366    return severity;
367  }
368
369  @Override
370  public boolean isDeprecated(){
371    return isDeprecated;
372  }
373
374  /**
375   * All issues that indicate problems with the coordinates and thus should not be shown on maps.
376   */
377  public static final List<OccurrenceIssue> GEOSPATIAL_RULES = ImmutableList.of(ZERO_COORDINATE,
378                                                                                COORDINATE_INVALID,
379                                                                                COORDINATE_OUT_OF_RANGE,
380                                                                                COUNTRY_COORDINATE_MISMATCH);
381  public static final List<OccurrenceIssue> TAXONOMIC_RULES = ImmutableList.of(TAXON_MATCH_FUZZY,
382                                                                               TAXON_MATCH_HIGHERRANK,
383                                                                               TAXON_MATCH_NONE);
384
385}