Skip to content

Commit

Permalink
Adding support for geometry/relationship spatial filters
Browse files Browse the repository at this point in the history
  • Loading branch information
aaime committed Mar 23, 2017
1 parent c56cef4 commit 0a87916
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 36 deletions.
Expand Up @@ -14,12 +14,12 @@
import org.geotools.feature.NameImpl;
import org.geotools.referencing.CRS;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.PropertyName;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;

/**
* Container/provider for common OpenSearch parameters. Parameter keys are used as the Kvp keys in URLs,
Expand All @@ -33,6 +33,10 @@ public class OpenSearchParameters {

public static final CoordinateReferenceSystem OUTPUT_CRS;

public static enum GeometryRelation {
intersects, disjoint, contains
}

/**
* Possible relationships between data time validity and query one
*
Expand All @@ -59,6 +63,12 @@ public static enum DateRelation {

public static final Parameter<?> GEO_RADIUS = new ParameterBuilder("radius", Double.class)
.prefix(GEO_PREFIX).minimumInclusive(0).build();

public static final Parameter<?> GEO_RELATION = new ParameterBuilder("geoRelation",
DateRelation.class).prefix(GEO_PREFIX).name("relation").build();

public static final Parameter<?> GEO_GEOMETRY = new ParameterBuilder("geometry", Geometry.class)
.prefix(GEO_PREFIX).build();

public static final Parameter<?> GEO_LON = new ParameterBuilder("lon", Double.class)
.prefix(GEO_PREFIX).minimumInclusive(-180).maximumInclusive(180).build();
Expand Down Expand Up @@ -115,7 +125,7 @@ private static List<Parameter<?>> basicOpenSearchParameters() {

private static List<Parameter<?>> geoTimeOpenSearchParameters() {
return Arrays.asList( //
GEO_UID, GEO_BOX, GEO_NAME, GEO_LAT, GEO_LON, GEO_RADIUS, TIME_START, TIME_END, TIME_RELATION);
GEO_UID, GEO_BOX, GEO_NAME, GEO_LAT, GEO_LON, GEO_RADIUS, GEO_GEOMETRY, GEO_RELATION, TIME_START, TIME_END, TIME_RELATION);
}

/**
Expand Down
Expand Up @@ -4,17 +4,7 @@
*/
package org.geoserver.opensearch.eo.kvp;

import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_BOX;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_LAT;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_LON;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_NAME;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_RADIUS;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_UID;
import static org.geoserver.opensearch.eo.OpenSearchParameters.SEARCH_TERMS;
import static org.geoserver.opensearch.eo.OpenSearchParameters.START_INDEX;
import static org.geoserver.opensearch.eo.OpenSearchParameters.TIME_END;
import static org.geoserver.opensearch.eo.OpenSearchParameters.TIME_RELATION;
import static org.geoserver.opensearch.eo.OpenSearchParameters.TIME_START;
import static org.geoserver.opensearch.eo.OpenSearchParameters.*;

import java.io.IOException;
import java.util.ArrayList;
Expand All @@ -36,6 +26,7 @@
import org.geoserver.opensearch.eo.OpenSearchEoService;
import org.geoserver.opensearch.eo.OpenSearchParameters;
import org.geoserver.opensearch.eo.OpenSearchParameters.DateRelation;
import org.geoserver.opensearch.eo.OpenSearchParameters.GeometryRelation;
import org.geoserver.opensearch.eo.SearchRequest;
import org.geoserver.opensearch.eo.store.OpenSearchAccess;
import org.geoserver.ows.KvpRequestReader;
Expand Down Expand Up @@ -63,8 +54,10 @@
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.io.WKTReader;

/**
* Reads a "description" request
Expand All @@ -89,6 +82,8 @@ public class SearchRequestKvpReader extends KvpRequestReader {

static final FilterFactory2 FF = CommonFactoryFinder.getFilterFactory2();

private static final PropertyName DEFAULT_GEOMETRY = FF.property("");

public static final String COUNT_KEY = "count";

public static final String PARENT_ID_KEY = "parentId";
Expand All @@ -99,8 +94,6 @@ public class SearchRequestKvpReader extends KvpRequestReader {

private GeoServer gs;

private TimeParser timeParser = new TimeParser();

public SearchRequestKvpReader(GeoServer gs, OpenSearchEoService service) {
super(SearchRequest.class);
this.oseo = service;
Expand Down Expand Up @@ -215,6 +208,12 @@ private Filter readFilter(Map rawKvp, Collection<Parameter<?>> parameters) throw
if (timeFilter != null) {
filters.add(timeFilter);
}

// handle geometry filter (2 params)
Filter geoFilter = buildGeometryFilter(rawKvp);
if(geoFilter != null) {
filters.add(geoFilter);
}

Filter filter = Predicates.and(filters);
return filter;
Expand All @@ -234,7 +233,7 @@ private Filter buildTimeFilter(Map rawKvp) {
.map(k -> k.name()).collect(Collectors.toList());
throw new OWS20Exception(
"Invalid value for relation, possible values are " + dateRelationNames,
OWS20Exception.OWSExceptionCode.InvalidParameterValue, "relation");
OWS20Exception.OWSExceptionCode.InvalidParameterValue, TIME_RELATION.key);
}
if (start == null && rawStart != null) {
throw new OWS20Exception(
Expand All @@ -254,7 +253,7 @@ private Filter buildTimeFilter(Map rawKvp) {
} else {
throw new OWS20Exception(
"Time relation specified, but start and end time values are missing",
OWS20Exception.OWSExceptionCode.InvalidParameterValue, "relation");
OWS20Exception.OWSExceptionCode.InvalidParameterValue, TIME_RELATION.key);
}
}

Expand Down Expand Up @@ -374,7 +373,7 @@ private Filter buildDistanceWithin(double lon, double lat, double radius) {
OWS20Exception.OWSExceptionCode.InvalidParameterValue, "radius");
}
final Point point = GF.createPoint(new Coordinate(lon, lat));
DWithin dwithin = FF.dwithin(FF.property(""), FF.literal(point), radius, "m");
DWithin dwithin = FF.dwithin(DEFAULT_GEOMETRY, FF.literal(point), radius, "m");
return dwithin;
}

Expand All @@ -387,9 +386,53 @@ private Filter buildBoundingBoxFilter(Object value) throws Exception {
"OpenSearch for EO requests only support boundig boxes in WGS84",
OWS20Exception.OWSExceptionCode.InvalidParameterValue, "box");
}
filter = FF.bbox(FF.property(""), box, MatchAction.ANY);
filter = FF.bbox(DEFAULT_GEOMETRY, box, MatchAction.ANY);
return filter;
}

private Filter buildGeometryFilter(Map rawKvp) {
String rawGeometry = (String) rawKvp.get(GEO_GEOMETRY.key);
String rawRelation = Converters.convert(rawKvp.get(GEO_RELATION.key), String.class);

if(rawGeometry == null && rawRelation == null) {
return null;
}

Geometry geometry;
try {
geometry = new WKTReader().read(rawGeometry);
} catch(Exception e) {
throw new OWS20Exception(
"Could not parse geometry parameter, expecting valid WKT syntax: " + e.getMessage(), e,
OWS20Exception.OWSExceptionCode.InvalidParameterValue, "geometry");
}

// handle relation
GeometryRelation relation = Converters.convert(rawRelation, GeometryRelation.class);
if (relation == null && rawRelation != null) {
final List<String> geoRelationNames = Arrays.stream(GeometryRelation.values())
.map(k -> k.name()).collect(Collectors.toList());
throw new OWS20Exception(
"Invalid value for relation, possible values are " + geoRelationNames,
OWS20Exception.OWSExceptionCode.InvalidParameterValue, GEO_RELATION.key);
}
if(relation == null) {
relation = GeometryRelation.intersects;
}

// build the filter
switch(relation) {
case intersects:
return FF.intersects(DEFAULT_GEOMETRY, FF.literal(geometry));
case contains:
return FF.contains(FF.literal(geometry), DEFAULT_GEOMETRY);
case disjoint:
return FF.disjoint(DEFAULT_GEOMETRY, FF.literal(geometry));
default:
throw new RuntimeException("Geometry relation of type " + relation + " not covered yet");
}

}

private PropertyIsEqualTo buildUidFilter(Object value) {
return FF.equals(FF.property(new NameImpl(OpenSearchAccess.EO_NAMESPACE, "identifier")),
Expand Down
Expand Up @@ -4,25 +4,12 @@
*/
package org.geoserver.opensearch.eo.kvp;

import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_BOX;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_LAT;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_LON;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_RADIUS;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_UID;
import static org.geoserver.opensearch.eo.OpenSearchParameters.SEARCH_TERMS;
import static org.geoserver.opensearch.eo.OpenSearchParameters.START_INDEX;
import static org.geoserver.opensearch.eo.OpenSearchParameters.TIME_END;
import static org.geoserver.opensearch.eo.OpenSearchParameters.TIME_RELATION;
import static org.geoserver.opensearch.eo.OpenSearchParameters.TIME_START;
import static org.geoserver.opensearch.eo.OpenSearchParameters.*;
import static org.geoserver.opensearch.eo.kvp.SearchRequestKvpReader.COUNT_KEY;
import static org.geoserver.opensearch.eo.kvp.SearchRequestKvpReader.PARENT_ID_KEY;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;

import java.util.Collections;
import java.util.Date;
Expand Down Expand Up @@ -56,6 +43,13 @@
import org.opengis.filter.PropertyIsLessThanOrEqualTo;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.spatial.BinarySpatialOperator;
import org.opengis.filter.spatial.Contains;
import org.opengis.filter.spatial.Disjoint;
import org.opengis.filter.spatial.Intersects;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.WKTReader;

public class SearchRequestKvpReaderTest extends OSEOTestSupport {

Expand Down Expand Up @@ -276,7 +270,7 @@ public void testTimeRelationInvalid() throws Exception {
fail("Should have failed");
} catch (OWS20Exception e) {
assertEquals("InvalidParameterValue", e.getCode());
assertEquals("relation", e.getLocator());
assertEquals("timeRelation", e.getLocator());
}
}

Expand All @@ -288,7 +282,7 @@ public void testTimeRelationAlone() throws Exception {
fail("Should have failed");
} catch (OWS20Exception e) {
assertEquals("InvalidParameterValue", e.getCode());
assertEquals("relation", e.getLocator());
assertEquals("timeRelation", e.getLocator());
}
}

Expand Down Expand Up @@ -472,6 +466,44 @@ public void testCloudCoverOpenRange() throws Exception {
assertBinaryFilter(op2, OpenSearchAccess.ProductClass.OPTICAL.getNamespace(), "cloudCover", 40);
}

@Test
public void testGeometryFilter() throws Exception {
String wkt = "POINT(0 0)";
Geometry point = new WKTReader().read(wkt);
// implicit relation
Filter filter = parseAndGetFilter(toMap("geometry", wkt));
assertThat(filter, instanceOf(Intersects.class));
assertBinarySpatialFilter(filter, "", point);
// explicit intersects
filter = parseAndGetFilter(toMap("geometry", wkt, "geoRelation", "intersects"));
assertThat(filter, instanceOf(Intersects.class));
assertBinarySpatialFilter(filter, "", point);
// explicit contains
filter = parseAndGetFilter(toMap("geometry", wkt, "geoRelation", "contains"));
assertThat(filter, instanceOf(Contains.class));
// ... expressions are inverted here, the attribute is contained in the search area
Contains bso = (Contains) filter;
assertThat(bso.getExpression2(), instanceOf(PropertyName.class));
PropertyName pn = (PropertyName) bso.getExpression2();
assertEquals("", pn.getPropertyName());
assertThat(bso.getExpression1(), instanceOf(Literal.class));
assertEquals(point, bso.getExpression1().evaluate(null));
// explict disjoint
filter = parseAndGetFilter(toMap("geometry", wkt, "geoRelation", "disjoint"));
assertThat(filter, instanceOf(Disjoint.class));
assertBinarySpatialFilter(filter, "", point);
}

private void assertBinarySpatialFilter(Filter filter, String expectedName, Object expectedValue) {
BinarySpatialOperator bso = (BinarySpatialOperator) filter;
assertThat(bso.getExpression1(), instanceOf(PropertyName.class));
PropertyName pn = (PropertyName) bso.getExpression1();
assertEquals(expectedName, pn.getPropertyName());
assertThat(bso.getExpression2(), instanceOf(Literal.class));
assertEquals(expectedValue, bso.getExpression2().evaluate(null));
}


@Test
public void testEopCreationDate() throws Exception {
Map<String, String> map = toMap("parentId", "SENTINEL2", "creationDate", "]2016-01-01");
Expand Down

0 comments on commit 0a87916

Please sign in to comment.