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.model.event; 015 016import org.gbif.api.annotation.Experimental; 017import org.gbif.api.model.common.Identifier; 018import org.gbif.api.model.common.MediaObject; 019import org.gbif.api.model.occurrence.AgentIdentifier; 020import org.gbif.api.model.occurrence.Gadm; 021import org.gbif.api.model.occurrence.MeasurementOrFact; 022import org.gbif.api.model.occurrence.Occurrence; 023import org.gbif.api.model.occurrence.OccurrenceRelation; 024import org.gbif.api.model.occurrence.VerbatimOccurrence; 025import org.gbif.api.util.IsoDateInterval; 026import org.gbif.api.vocabulary.BasisOfRecord; 027import org.gbif.api.vocabulary.Continent; 028import org.gbif.api.vocabulary.Country; 029import org.gbif.api.vocabulary.License; 030import org.gbif.api.vocabulary.OccurrenceIssue; 031import org.gbif.api.vocabulary.OccurrenceStatus; 032import org.gbif.api.vocabulary.Sex; 033import org.gbif.dwc.terms.DwcTerm; 034import org.gbif.dwc.terms.Term; 035import org.gbif.dwc.terms.UnknownTerm; 036 037import java.lang.reflect.Field; 038import java.lang.reflect.Modifier; 039import java.net.URI; 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.Collections; 043import java.util.Date; 044import java.util.EnumSet; 045import java.util.HashMap; 046import java.util.List; 047import java.util.Map; 048import java.util.Set; 049import java.util.stream.Collectors; 050import java.util.stream.Stream; 051 052import javax.annotation.Nullable; 053 054import com.fasterxml.jackson.annotation.JsonAnyGetter; 055import com.fasterxml.jackson.annotation.JsonIgnore; 056import com.fasterxml.jackson.annotation.JsonProperty; 057import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 058import com.fasterxml.jackson.databind.annotation.JsonSerialize; 059 060import lombok.AllArgsConstructor; 061import lombok.Data; 062 063/** 064 * Event class based on https://dwc.tdwg.org/terms/#event. 065 */ 066@Data 067public class Event extends VerbatimOccurrence { 068 069 public static final String GEO_DATUM = "WGS84"; 070 071 // keep names of ALL properties of this class in a set for jackson serialization, see #properties() 072 private static final Set<String> PROPERTIES = Collections.unmodifiableSet( 073 Stream.concat( 074 // we need to these json properties manually because we have a fixed getter but no field for it 075 Stream.of(DwcTerm.geodeticDatum.simpleName(), "class", "countryCode"), 076 Stream.concat(Arrays.stream(Event.class.getDeclaredFields()), 077 Arrays.stream(VerbatimOccurrence.class.getDeclaredFields())) 078 .filter(field -> !Modifier.isStatic(field.getModifiers())) 079 .map(Field::getName)).collect(Collectors.toSet())); 080 081 public static Event fromOccurrence(Occurrence occurrence) { 082 Event event =new Event(); 083 event.setKey(occurrence.getKey()); 084 event.setDatasetKey(occurrence.getDatasetKey()); 085 event.setPublishingOrgKey(occurrence.getPublishingOrgKey()); 086 event.setNetworkKeys(occurrence.getNetworkKeys()); 087 event.setInstallationKey(occurrence.getInstallationKey()); 088 event.setPublishingCountry(occurrence.getPublishingCountry()); 089 event.setProtocol(occurrence.getProtocol()); 090 event.setLastCrawled(occurrence.getLastCrawled()); 091 event.setLastParsed(occurrence.getLastParsed()); 092 event.setCrawlId(occurrence.getCrawlId()); 093 event.setProjectId(occurrence.getProjectId()); 094 event.setProgrammeAcronym(occurrence.getProgrammeAcronym()); 095 event.setHostingOrganizationKey(occurrence.getHostingOrganizationKey()); 096 event.setVerbatimFields(occurrence.getVerbatimFields()); 097 event.setExtensions(occurrence.getExtensions()); 098 event.setBasisOfRecord(occurrence.getBasisOfRecord()); 099 event.setIndividualCount(occurrence.getIndividualCount()); 100 event.setOccurrenceStatus(occurrence.getOccurrenceStatus()); 101 event.setSex(occurrence.getSex()); 102 event.setLifeStage(occurrence.getLifeStage()); 103 event.setEstablishmentMeans(occurrence.getEstablishmentMeans()); 104 event.setDegreeOfEstablishment(occurrence.getDegreeOfEstablishment()); 105 event.setPathway(occurrence.getPathway()); 106 event.setDateIdentified(occurrence.getDateIdentified()); 107 event.setDecimalLongitude(occurrence.getDecimalLongitude()); 108 event.setDecimalLatitude(occurrence.getDecimalLatitude()); 109 event.setCoordinatePrecision(occurrence.getCoordinatePrecision()); 110 event.setCoordinateUncertaintyInMeters(occurrence.getCoordinateUncertaintyInMeters()); 111 event.setCoordinateAccuracy(occurrence.getCoordinateAccuracy()); 112 event.setElevation(occurrence.getElevation()); 113 event.setElevationAccuracy(occurrence.getElevationAccuracy()); 114 event.setDepth(occurrence.getDepth()); 115 event.setDepthAccuracy(occurrence.getDepthAccuracy()); 116 event.setContinent(occurrence.getContinent()); 117 event.setCountry(occurrence.getCountry()); 118 event.setStateProvince(occurrence.getStateProvince()); 119 event.setWaterBody(occurrence.getWaterBody()); 120 event.setDistanceFromCentroidInMeters(occurrence.getDistanceFromCentroidInMeters()); 121 event.setYear(occurrence.getYear()); 122 event.setMonth(occurrence.getMonth()); 123 event.setDay(occurrence.getDay()); 124 event.setEventDate(occurrence.getEventDate()); 125 event.setIssues(occurrence.getIssues()); 126 event.setModified(occurrence.getModified()); 127 event.setLastInterpreted(occurrence.getLastInterpreted()); 128 event.setReferences(occurrence.getReferences()); 129 event.setLicense(occurrence.getLicense()); 130 event.setOrganismQuantity(occurrence.getOrganismQuantity()); 131 event.setOrganismQuantityType(occurrence.getOrganismQuantityType()); 132 event.setSampleSizeUnit(occurrence.getSampleSizeUnit()); 133 event.setSampleSizeValue(occurrence.getSampleSizeValue()); 134 event.setRelativeOrganismQuantity(occurrence.getRelativeOrganismQuantity()); 135 event.setIdentifiers(occurrence.getIdentifiers()); 136 event.setMedia(occurrence.getMedia()); 137 event.setFacts(occurrence.getFacts()); 138 event.setRelations(occurrence.getRelations()); 139 event.setRecordedByIds(occurrence.getRecordedByIds()); 140 event.setIdentifiedByIds(occurrence.getIdentifiedByIds()); 141 event.setGadm(occurrence.getGadm()); 142 event.setInstitutionKey(occurrence.getInstitutionKey()); 143 event.setCollectionKey(occurrence.getCollectionKey()); 144 event.setInCluster(occurrence.getIsInCluster()); 145 event.setDatasetID(occurrence.getDatasetID()); 146 event.setDatasetName(occurrence.getDatasetName()); 147 event.setOtherCatalogNumbers(occurrence.getOtherCatalogNumbers()); 148 event.setRecordedBy(occurrence.getRecordedBy()); 149 event.setIdentifiedBy(occurrence.getIdentifiedBy()); 150 event.setPreparations(occurrence.getPreparations()); 151 event.setSamplingProtocol(occurrence.getSamplingProtocol()); 152 return event; 153 } 154 155 @Data 156 @AllArgsConstructor 157 public static class ParentLineage { 158 private String id; 159 private String eventType; 160 } 161 162 @Data 163 @AllArgsConstructor 164 public static class VocabularyConcept { 165 private String concept; 166 private Set<String> lineage; 167 } 168 169 private String id; 170 private Set<String> samplingProtocols; 171 private String eventID; 172 private String parentEventID; 173 private Integer startDayOfYear; 174 private Integer endDayOfYear; 175 private String locationID; 176 private VocabularyConcept eventType; 177 private List<ParentLineage> parentsLineage; 178 179 // occurrence fields 180 private BasisOfRecord basisOfRecord; 181 private Integer individualCount; 182 private OccurrenceStatus occurrenceStatus; 183 private Sex sex; 184 private String lifeStage; 185 private String establishmentMeans; 186 private String degreeOfEstablishment; 187 private String pathway; 188 189 // identification 190 private Date dateIdentified; 191 // location 192 private Double decimalLongitude; 193 private Double decimalLatitude; 194 195 //coordinatePrecision and coordinateUncertaintyInMeters should be BigDecimal see POR-2795 196 private Double coordinatePrecision; 197 private Double coordinateUncertaintyInMeters; 198 @Deprecated //see getter 199 private Double coordinateAccuracy; 200 201 private Double elevation; 202 private Double elevationAccuracy; 203 private Double depth; 204 private Double depthAccuracy; 205 private Continent continent; 206 @JsonSerialize(using = Country.IsoSerializer.class) 207 @JsonDeserialize(using = Country.IsoDeserializer.class) 208 private Country country; 209 private String stateProvince; 210 private String waterBody; 211 private Double distanceFromCentroidInMeters; 212 213 // recording event 214 private Integer year; 215 private Integer month; 216 private Integer day; 217 private IsoDateInterval eventDate; 218 219 private Set<OccurrenceIssue> issues = EnumSet.noneOf(OccurrenceIssue.class); 220 221 // record level 222 private Date modified; // interpreted dc:modified, i.e. date changed in source 223 private Date lastInterpreted; 224 private URI references; 225 private License license; 226 private Double organismQuantity; 227 private String organismQuantityType; 228 private String sampleSizeUnit; 229 private Double sampleSizeValue; 230 private Double relativeOrganismQuantity; 231 232 // interpreted extension data 233 private List<Identifier> identifiers = new ArrayList<>(); 234 private List<MediaObject> media = new ArrayList<>(); 235 private List<MeasurementOrFact> facts = new ArrayList<>(); 236 private List<OccurrenceRelation> relations = new ArrayList<>(); 237 @JsonProperty("recordedByIDs") 238 private List<AgentIdentifier> recordedByIds = new ArrayList<>(); 239 @JsonProperty("identifiedByIDs") 240 private List<AgentIdentifier> identifiedByIds = new ArrayList<>(); 241 private Gadm gadm = new Gadm(); 242 @Experimental 243 private String institutionKey; 244 @Experimental 245 private String collectionKey; 246 private boolean isInCluster; 247 private String datasetID; 248 private String datasetName; 249 private String otherCatalogNumbers; 250 private String recordedBy; 251 private String identifiedBy; 252 private String preparations; 253 private String samplingProtocol; 254 255 /** 256 * Convenience method checking if any spatial validation rule has not passed. 257 * Primarily used to indicate that the record should not be displayed on a map. 258 */ 259 @JsonIgnore 260 public boolean hasSpatialIssue() { 261 for (OccurrenceIssue rule : OccurrenceIssue.GEOSPATIAL_RULES) { 262 if (issues.contains(rule)) { 263 return true; 264 } 265 } 266 return false; 267 } 268 269 /** 270 * The geodetic datum for the interpreted decimal coordinates. 271 * This is always WGS84 if there a coordinate exists as we reproject other datums into WGS84. 272 */ 273 @Nullable 274 public String getGeodeticDatum() { 275 if (decimalLatitude != null) { 276 return GEO_DATUM; 277 } 278 return null; 279 } 280 281 /** 282 * This private method is only for serialization via jackson and not exposed anywhere else! 283 * It maps the verbatimField terms into properties with their simple name or qualified names for UnknownTerms. 284 */ 285 @JsonAnyGetter 286 private Map<String, String> jsonVerbatimFields() { 287 Map<String, String> extendedProps = new HashMap<>(); 288 for (Map.Entry<Term, String> prop : getVerbatimFields().entrySet()) { 289 Term t = prop.getKey(); 290 if (t instanceof UnknownTerm || PROPERTIES.contains(t.simpleName())) { 291 extendedProps.put(t.qualifiedName(), prop.getValue()); 292 } else { 293 // render all terms in controlled enumerations as simple names only - unless we have a property of that name already! 294 extendedProps.put(t.simpleName(), prop.getValue()); 295 } 296 } 297 return extendedProps; 298 } 299}