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}