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