Skip to content

Commit

Permalink
OpenSearch support for dateline crossing bbox filters
Browse files Browse the repository at this point in the history
  • Loading branch information
aaime committed May 5, 2017
1 parent fda4190 commit 03e097a
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.opensearch.eo.kvp;

import org.geoserver.platform.OWS20Exception;
import org.geoserver.wfs.kvp.BBoxKvpParser;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

/**
* A parser that allows dateline crossing envelopes
*
* @author Andrea Aime
*/
public class OpenSearchBBoxKvpParser extends BBoxKvpParser {

@Override
protected Object buildEnvelope(int countco, double minx, double miny, double minz, double maxx,
double maxy, double maxz, String srs)
throws NoSuchAuthorityCodeException, FactoryException {
if (countco > 4) {
throw new IllegalArgumentException(
"Too many coordinates, openSearch cannot handle non flat envelopes yet");
}

CoordinateReferenceSystem crs = srs == null ? null : CRS.decode(srs, true);
if (crs != null && !CRS.equalsIgnoreMetadata(DefaultGeographicCRS.WGS84, crs)) {
throw new OWS20Exception(
"OpenSearch for EO requests only support boundig boxes in WGS84",
OWS20Exception.OWSExceptionCode.InvalidParameterValue, "box");
}
minx = rollLongitude(minx);
maxx = rollLongitude(maxx);

if (minx > maxx) {
// dateline crossing case
return new ReferencedEnvelope[] { new ReferencedEnvelope(minx, 180, miny, maxy, crs),
new ReferencedEnvelope(-180, maxx, miny, maxy, crs), };
} else {
return new ReferencedEnvelope(minx, maxx, miny, maxy, crs);
}
}

protected static double rollLongitude(final double x) {
double mod = (x + 180) % 360;
if (mod == 0) {
return x > 0 ? 180 : -180;
} else {
return mod - 180;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@
*/
package org.geoserver.opensearch.eo.kvp;

import static org.geoserver.opensearch.eo.OpenSearchParameters.*;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_BOX;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_GEOMETRY;
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_RELATION;
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 java.io.IOException;
import java.util.ArrayList;
Expand All @@ -30,18 +42,14 @@
import org.geoserver.opensearch.eo.SearchRequest;
import org.geoserver.opensearch.eo.store.OpenSearchAccess;
import org.geoserver.ows.KvpRequestReader;
import org.geoserver.ows.kvp.TimeParser;
import org.geoserver.platform.OWS20Exception;
import org.geoserver.platform.OWS20Exception.OWSExceptionCode;
import org.geoserver.wfs.kvp.BBoxKvpParser;
import org.geotools.data.Parameter;
import org.geotools.data.Query;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.Hints;
import org.geotools.feature.NameImpl;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.util.ConverterFactory;
import org.geotools.util.Converters;
import org.opengis.filter.Filter;
Expand All @@ -50,8 +58,8 @@
import org.opengis.filter.PropertyIsEqualTo;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.spatial.BBOX;
import org.opengis.filter.spatial.DWithin;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.springframework.util.StringUtils;

import com.vividsolutions.jts.geom.Coordinate;
Expand Down Expand Up @@ -94,6 +102,8 @@ public class SearchRequestKvpReader extends KvpRequestReader {
private OpenSearchEoService oseo;

private GeoServer gs;

OpenSearchBBoxKvpParser bboxParser = new OpenSearchBBoxKvpParser();

public SearchRequestKvpReader(GeoServer gs, OpenSearchEoService service) {
super(SearchRequest.class);
Expand Down Expand Up @@ -380,14 +390,17 @@ private Filter buildDistanceWithin(double lon, double lat, double radius) {

private Filter buildBoundingBoxFilter(Object value) throws Exception {
Filter filter;
ReferencedEnvelope box = (ReferencedEnvelope) new BBoxKvpParser().parse((String) value);
final CoordinateReferenceSystem crs = box.getCoordinateReferenceSystem();
if (crs != null && !CRS.equalsIgnoreMetadata(DefaultGeographicCRS.WGS84, crs)) {
throw new OWS20Exception(
"OpenSearch for EO requests only support boundig boxes in WGS84",
OWS20Exception.OWSExceptionCode.InvalidParameterValue, "box");
Object parsed = bboxParser.parse((String) value);
if(parsed instanceof ReferencedEnvelope) {
filter = FF.bbox(DEFAULT_GEOMETRY, (ReferencedEnvelope) parsed, MatchAction.ANY);
} else if(parsed instanceof ReferencedEnvelope[]) {
ReferencedEnvelope[] envelopes = (ReferencedEnvelope[]) parsed;
BBOX bbox1 = FF.bbox(DEFAULT_GEOMETRY, envelopes[0], MatchAction.ANY);
BBOX bbox2 = FF.bbox(DEFAULT_GEOMETRY, envelopes[1], MatchAction.ANY);
return FF.or(bbox1, bbox2);
} else {
throw new IllegalArgumentException("Unexpected bbox parse result: " + parsed);
}
filter = FF.bbox(DEFAULT_GEOMETRY, box, MatchAction.ANY);
return filter;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,8 @@ public void testSpecificProduct() throws Exception {
}

@Test
public void testSearchByBox() throws Exception {
Document dom = getAsDOM("oseo/search?parentId=SENTINEL2&box=-118,33,-117,34");
public void testSearchByBoxDatelineCrossing() throws Exception {
Document dom = getAsDOM("oseo/search?parentId=SENTINEL2&box=170,33,-117,34");
// print(dom);

// only one feature should be matching
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,28 @@ public void testParseTimeBox() throws Exception {
assertEquals(1, searchParameters.size());
assertThat(searchParameters, hasEntry(OpenSearchParameters.GEO_BOX, "10,20,30,40"));
}

@Test
public void testParseBBoxWholeWorld() throws Exception {
Map<String, String> map = toMap(GEO_BOX.key, "-180,-90,180,90");
SearchRequest request = parseSearchRequest(map);
assertEquals(null, request.getParentId());
final Query query = request.getQuery();
assertNotNull(query);
final String expectedCql = "BBOX(, -180.0,-90.0,180.0,90.0)";
assertEquals(expectedCql, ECQL.toCQL(query.getFilter()));
}

@Test
public void testParseBBoxDatelineCrossing() throws Exception {
Map<String, String> map = toMap(GEO_BOX.key, "170,-90,-170,90");
SearchRequest request = parseSearchRequest(map);
assertEquals(null, request.getParentId());
final Query query = request.getQuery();
assertNotNull(query);
final String expectedCql = "BBOX(, 170.0,-90.0,180.0,90.0) OR BBOX(, -180.0,-90.0,-170.0,90.0)";
assertEquals(expectedCql, ECQL.toCQL(query.getFilter()));
}

@Test
public void testPaging() throws Exception {
Expand Down
38 changes: 22 additions & 16 deletions src/wfs/src/main/java/org/geoserver/wfs/kvp/BBoxKvpParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
import org.geoserver.ows.util.KvpUtils;
import org.geoserver.platform.ServiceException;
import org.geoserver.wfs.WFSException;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope3D;
import org.geotools.referencing.CRS;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.SingleCRS;

import com.vividsolutions.jts.geom.Envelope;

Expand Down Expand Up @@ -69,6 +69,26 @@ public Object parse(String value) throws Exception {
maxy = bbox[3];
}

// check for srs
String srs = null;
if (unparsed.size() > countco) {
// merge back the CRS definition, in case it is an AUTO one
StringBuilder sb = new StringBuilder();
for (int i = countco; i < unparsed.size(); i++) {
sb.append(unparsed.get(i));
if(i < (unparsed.size() - 1)) {
sb.append(",");
}
}
srs = sb.toString();
}

return buildEnvelope(countco, minx, miny, minz, maxx, maxy, maxz, srs);
}

protected Object buildEnvelope(int countco, double minx, double miny, double minz, double maxx,
double maxy, double maxz, String srs)
throws NoSuchAuthorityCodeException, FactoryException {
if (minx > maxx) {
throw new ServiceException("illegal bbox, minX: " + minx + " is "
+ "greater than maxX: " + maxx);
Expand All @@ -82,20 +102,6 @@ public Object parse(String value) throws Exception {
if (minz > maxz) {
throw new ServiceException("illegal bbox, minZ: " + minz + " is "
+ "greater than maxZ: " + maxz);
}

// check for srs
String srs = null;
if (unparsed.size() > countco) {
// merge back the CRS definition, in case it is an AUTO one
StringBuilder sb = new StringBuilder();
for (int i = countco; i < unparsed.size(); i++) {
sb.append(unparsed.get(i));
if(i < (unparsed.size() - 1)) {
sb.append(",");
}
}
srs = sb.toString();
}

if (countco == 6) {
Expand Down

0 comments on commit 03e097a

Please sign in to comment.