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