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.predicate; 015 016import org.gbif.api.model.occurrence.search.OccurrenceSearchParameter; 017import org.gbif.api.util.SearchTypeValidator; 018 019import java.util.Objects; 020import java.util.StringJoiner; 021 022import javax.validation.constraints.NotNull; 023 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027import com.fasterxml.jackson.annotation.JsonCreator; 028import com.fasterxml.jackson.annotation.JsonProperty; 029 030import io.swagger.v3.oas.annotations.media.Schema; 031 032/** 033 * This predicate checks if an occurrence location falls within the given WKT geometry {@code value}. 034 */ 035@Schema( 036 description = "This predicate checks if an occurrence falls within the given WKT geometry." 037) 038public class WithinPredicate implements Predicate { 039 040 private static final Logger LOG = LoggerFactory.getLogger(WithinPredicate.class); 041 042 @Schema( 043 description = "The WKT geometry to test for. Occurrences whose location is within this geometry are returned." 044 ) 045 @NotNull 046 private final String geometry; 047 048 /** 049 * Builds a new within predicate for a single, simple geometry as 050 * <a href="http://en.wikipedia.org/wiki/Well-known_text">Well Known Text</a> (WKT). 051 * Multi geometries like MULTIPOLYGON are not supported and multiple predicates should be used instead. 052 * <br/> 053 * The validation implemented does a basic syntax check for the following simple geometries, but does not 054 * verify that the resulting geometries are topologically valid (see the OGC SFS specification). 055 * <ul> 056 * <li>POINT</li> 057 * <li>LINESTRING</li> 058 * <li>POLYGON</li> 059 * <li>LINEARRING</li> 060 * </ul> 061 * <strong>Unlike other predicates, this validation only logs in case of an invalid string.</strong> 062 * This is because the WKT parser has been changed over time, and some old strings are not valid according 063 * to the current parser. 064 * 065 * @param geometry 066 */ 067 @JsonCreator 068 public WithinPredicate(@JsonProperty("geometry") String geometry) { 069 Objects.requireNonNull(geometry, "<geometry> may not be null"); 070 try { 071 // test if it is a valid WKT 072 SearchTypeValidator.validate(OccurrenceSearchParameter.GEOMETRY, geometry); 073 } catch (IllegalArgumentException e) { 074 // Log invalid strings, but continue - the geometry parser has changed over time, and some once-valid strings 075 // are no longer considered valid. See https://github.com/gbif/gbif-api/issues/48. 076 LOG.warn("Invalid geometry string {}: {}", geometry, e.getMessage()); 077 } 078 this.geometry = geometry; 079 } 080 081 public String getGeometry() { 082 return geometry; 083 } 084 085 @Override 086 public boolean equals(Object o) { 087 if (this == o) { 088 return true; 089 } 090 if (o == null || getClass() != o.getClass()) { 091 return false; 092 } 093 WithinPredicate that = (WithinPredicate) o; 094 return Objects.equals(geometry, that.geometry); 095 } 096 097 @Override 098 public int hashCode() { 099 return Objects.hash(geometry); 100 } 101 102 @Override 103 public String toString() { 104 return new StringJoiner(", ", WithinPredicate.class.getSimpleName() + "[", "]") 105 .add("geometry='" + geometry + "'") 106 .toString(); 107 } 108}