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