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