Skip to content

Commit

Permalink
[GEOS-9247] WMTS multidimensional, support bboxes spanning the dateline
Browse files Browse the repository at this point in the history
  • Loading branch information
aaime committed Jun 12, 2019
1 parent 5a920ea commit eeb48be
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 63 deletions.
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.geoserver.catalog.Catalog; import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.DimensionPresentation; import org.geoserver.catalog.DimensionPresentation;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.MetadataMap; import org.geoserver.catalog.MetadataMap;
import org.geoserver.catalog.PublishedInfo; import org.geoserver.catalog.PublishedInfo;
Expand All @@ -37,13 +35,9 @@
import org.geoserver.platform.ServiceException; import org.geoserver.platform.ServiceException;
import org.geoserver.wms.WMS; import org.geoserver.wms.WMS;
import org.geoserver.wms.dimension.DimensionFilterBuilder; import org.geoserver.wms.dimension.DimensionFilterBuilder;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.coverage.util.FeatureUtilities;
import org.geotools.data.DataSourceException;
import org.geotools.data.Query; import org.geotools.data.Query;
import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.SchemaException; import org.geotools.feature.SchemaException;
import org.geotools.filter.spatial.ReprojectingFilterVisitor;
import org.geotools.filter.visitor.SimplifyingFilterVisitor; import org.geotools.filter.visitor.SimplifyingFilterVisitor;
import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS; import org.geotools.referencing.CRS;
Expand All @@ -58,7 +52,6 @@
import org.geowebcache.service.OWSException; import org.geowebcache.service.OWSException;
import org.geowebcache.service.wmts.WMTSExtensionImpl; import org.geowebcache.service.wmts.WMTSExtensionImpl;
import org.geowebcache.storage.StorageBroker; import org.geowebcache.storage.StorageBroker;
import org.opengis.feature.type.FeatureType;
import org.opengis.filter.Filter; import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory; import org.opengis.filter.FilterFactory;
import org.opengis.filter.FilterFactory2; import org.opengis.filter.FilterFactory2;
Expand Down Expand Up @@ -340,7 +333,7 @@ private Filter getDomainRestrictions(
List<Dimension> dimensions, List<Dimension> dimensions,
ReferencedEnvelope boundingBox, ReferencedEnvelope boundingBox,
ResourceInfo resource) ResourceInfo resource)
throws IOException, TransformException, SchemaException { throws IOException, TransformException, SchemaException, FactoryException {
Filter filter = DimensionsUtils.getBoundingBoxFilter(resource, boundingBox, filterFactory); Filter filter = DimensionsUtils.getBoundingBoxFilter(resource, boundingBox, filterFactory);
for (Dimension dimension : dimensions) { for (Dimension dimension : dimensions) {
Object restriction = conveyor.getParameter(dimension.getDimensionName(), false); Object restriction = conveyor.getParameter(dimension.getDimensionName(), false);
Expand All @@ -353,42 +346,7 @@ private Filter getDomainRestrictions(
} }
} }


// reproject the filter to the native CRS of the resouce return filter;
return reprojectFilter(filter, getSchemaForResource(resource));
}

private FeatureType getSchemaForResource(ResourceInfo resource)
throws IOException, TransformException, SchemaException {
FeatureType schema;
if (resource instanceof FeatureTypeInfo) {
schema = ((FeatureTypeInfo) resource).getFeatureType();
} else if (resource instanceof CoverageInfo) {
GridCoverage2DReader reader =
(GridCoverage2DReader)
((CoverageInfo) resource).getGridCoverageReader(null, null);
schema = FeatureUtilities.wrapGridCoverageReader(reader, null).getSchema();
} else {
throw new IllegalArgumentException(
"Did not expect this resource, only vector and raster layers are supported: "
+ resource);
}

return schema;
}

private static Filter reprojectFilter(Filter filter, FeatureType schema) throws IOException {
if (filter == null || Filter.INCLUDE.equals(filter)) {
return filter;
}
try {
// and then we reproject all geometries so that the datastore receives
// them in the native projection system (or the forced one, in case of force)
ReprojectingFilterVisitor reprojectingVisitor =
new ReprojectingFilterVisitor(FILTER_FACTORY, schema);
return (Filter) filter.accept(reprojectingVisitor, null);
} catch (Exception e) {
throw new DataSourceException("Had troubles handling filter reprojection...", e);
}
} }


private ReferencedEnvelope getRequestedBoundingBox(SimpleConveyor conveyor, TileLayer tileLayer) private ReferencedEnvelope getRequestedBoundingBox(SimpleConveyor conveyor, TileLayer tileLayer)
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -25,24 +25,32 @@
import org.geoserver.gwc.wmts.Tuple; import org.geoserver.gwc.wmts.Tuple;
import org.geoserver.util.ISO8601Formatter; import org.geoserver.util.ISO8601Formatter;
import org.geoserver.wms.WMS; import org.geoserver.wms.WMS;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.coverage.util.FeatureUtilities;
import org.geotools.data.FeatureSource; import org.geotools.data.FeatureSource;
import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator; import org.geotools.feature.FeatureIterator;
import org.geotools.feature.SchemaException;
import org.geotools.feature.visitor.Aggregate; import org.geotools.feature.visitor.Aggregate;
import org.geotools.feature.visitor.FeatureCalc; import org.geotools.feature.visitor.FeatureCalc;
import org.geotools.feature.visitor.UniqueVisitor; import org.geotools.feature.visitor.UniqueVisitor;
import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.gml2.bindings.GML2EncodingUtils; import org.geotools.gml2.bindings.GML2EncodingUtils;
import org.geotools.renderer.crs.ProjectionHandler;
import org.geotools.renderer.crs.ProjectionHandlerFinder;
import org.geotools.util.Converters; import org.geotools.util.Converters;
import org.geotools.util.Range; import org.geotools.util.Range;
import org.geowebcache.service.OWSException; import org.geowebcache.service.OWSException;
import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.FeatureType;
import org.opengis.filter.Filter; import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory; import org.opengis.filter.FilterFactory;
import org.opengis.filter.FilterFactory2; import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.PropertyName; import org.opengis.filter.expression.PropertyName;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.springframework.util.comparator.ComparableComparator; import org.springframework.util.comparator.ComparableComparator;


/** Some utils methods useful to interact with dimensions. */ /** Some utils methods useful to interact with dimensions. */
Expand Down Expand Up @@ -330,26 +338,71 @@ public static ReferencedEnvelope getBounds(ResourceInfo resource, Filter filter)
* Builds a bounding box filter, or returns {@link Filter#INCLUDE} if the bounding box is null * Builds a bounding box filter, or returns {@link Filter#INCLUDE} if the bounding box is null
*/ */
public static Filter getBoundingBoxFilter( public static Filter getBoundingBoxFilter(
ResourceInfo resource, ReferencedEnvelope boundingBox, FilterFactory filterFactory) { ResourceInfo resource, ReferencedEnvelope boundingBox, FilterFactory filterFactory)
throws TransformException, IOException, SchemaException, FactoryException {
String geometryName = getGeometryPropertyName(resource); String geometryName = getGeometryPropertyName(resource);
if (boundingBox == null || geometryName == null) { if (boundingBox == null || geometryName == null) {
return Filter.INCLUDE; return Filter.INCLUDE;
} }
CoordinateReferenceSystem coordinateReferenceSystem =
boundingBox.getCoordinateReferenceSystem(); // do we need to query multiple areas?
String epsgCode = ProjectionHandler handler =
coordinateReferenceSystem == null ProjectionHandlerFinder.getHandler(
? null boundingBox,
: GML2EncodingUtils.toURI(coordinateReferenceSystem); getSchemaForResource(resource).getCoordinateReferenceSystem(),
Filter spatialFilter = true);
filterFactory.bbox( if (handler == null) {
geometryName, return toBoundingBoxFilter(boundingBox, filterFactory, geometryName);
boundingBox.getMinX(), }
boundingBox.getMinY(),
boundingBox.getMaxX(), List<ReferencedEnvelope> boxes = handler.getQueryEnvelopes();
boundingBox.getMaxY(), List<Filter> filters =
epsgCode); boxes.stream()
return spatialFilter; .map(re -> toBoundingBoxFilter(re, filterFactory, geometryName))
.collect(Collectors.toList());
if (filters.size() == 1) {
return filters.get(0);
} else if (filters.size() > 1) {
return filterFactory.or(filters);
} else {
return Filter.INCLUDE;
}
}

protected static double rollLongitude(final double x) {
return x - 360 * Math.floor(x / 360 + 0.5);
}

private static FeatureType getSchemaForResource(ResourceInfo resource)
throws IOException, TransformException, SchemaException {
FeatureType schema;
if (resource instanceof FeatureTypeInfo) {
schema = ((FeatureTypeInfo) resource).getFeatureType();
} else if (resource instanceof CoverageInfo) {
GridCoverage2DReader reader =
(GridCoverage2DReader)
((CoverageInfo) resource).getGridCoverageReader(null, null);
schema = FeatureUtilities.wrapGridCoverageReader(reader, null).getSchema();
} else {
throw new IllegalArgumentException(
"Did not expect this resource, only vector and raster layers are supported: "
+ resource);
}

return schema;
}

private static Filter toBoundingBoxFilter(
ReferencedEnvelope boundingBox, FilterFactory filterFactory, String geometryName) {
CoordinateReferenceSystem crs = boundingBox.getCoordinateReferenceSystem();
String epsgCode = crs == null ? null : GML2EncodingUtils.toURI(crs);
return filterFactory.bbox(
geometryName,
boundingBox.getMinX(),
boundingBox.getMinY(),
boundingBox.getMaxX(),
boundingBox.getMaxY(),
epsgCode);
} }


private static String getGeometryPropertyName(ResourceInfo resource) { private static String getGeometryPropertyName(ResourceInfo resource) {
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -418,6 +418,46 @@ public void testRasterDescribeDomainsReprojectedFilterMosaic() throws Exception
checkXpathCount(result, "/md:Domains/md:DimensionDomain[md:Size='2']", "2"); checkXpathCount(result, "/md:Domains/md:DimensionDomain[md:Size='2']", "2");
} }


@Test
public void testRasterDescribeDomainsAcrossDateline() throws Exception {
// perform the get describe domains operation with a spatial restriction across the
// dateline,
// with the part covering the data fully outside of the dateline
String queryRequest =
String.format(
"request=DescribeDomains&Version=1.0.0&Layer=%s&TileMatrixSet=EPSG:4326",
getLayerId(RASTER_ELEVATION_TIME) + "&bbox=170,40,374,45,EPSG:4326");
MockHttpServletResponse response = getAsServletResponse("gwc/service/wmts?" + queryRequest);
Document result = getResultAsDocument(response);
print(result);
// check that we have two domains
checkXpathCount(result, "/md:Domains/md:DimensionDomain", "2");
// check the space domain is not included
checkXpathCount(result, "/md:Domains/md:SpaceDomain", "1");
// the domain should not contain 2 values
checkXpathCount(result, "/md:Domains/md:DimensionDomain[md:Size='2']", "2");
}

@Test
public void testRasterDescribeDomainsReprojectedOutsideValid() throws Exception {
// perform the get describe domains operation with a spatial restriction in 3857 and with
// values wrapped to a "second copy of the world" past the dateline
String queryRequest =
String.format(
"request=DescribeDomains&Version=1.0.0&Layer=%s&TileMatrixSet=EPSG:4326",
getLayerId(RASTER_ELEVATION_TIME)
+ "&bbox=40000000,5000000,41000000,6000000,EPSG:3857");
MockHttpServletResponse response = getAsServletResponse("gwc/service/wmts?" + queryRequest);
Document result = getResultAsDocument(response);
print(result);
// check that we have two domains
checkXpathCount(result, "/md:Domains/md:DimensionDomain", "2");
// check the space domain is not included
checkXpathCount(result, "/md:Domains/md:SpaceDomain", "1");
// the domain should not contain 2 values
checkXpathCount(result, "/md:Domains/md:DimensionDomain[md:Size='2']", "2");
}

@Test @Test
public void testRasterDescribeDomainsOperationWithBoundingAndWrongTileMatrixSet() public void testRasterDescribeDomainsOperationWithBoundingAndWrongTileMatrixSet()
throws Exception { throws Exception {
Expand Down Expand Up @@ -484,6 +524,72 @@ public void testVectorDescribeDomainsReprojectedFilter() throws Exception {
Document result = getResultAsDocument(response); Document result = getResultAsDocument(response);
print(result); print(result);
// check the space domain // check the space domain
checkVectorElevationFullDomain(result);
}

@Test
public void testVectorDescribeDomainsAcrossDateline() throws Exception {
// spatial restriction across the dateline, the polygons are the 4 quadrants covering the
// world
String queryRequest =
String.format(
"request=DescribeDomains&Version=1.0.0&Layer=%s&TileMatrixSet=EPSG:4326",
getLayerId(VECTOR_ELEVATION_TIME) + "&bbox=170,-90,190,90,EPSG:4326");
MockHttpServletResponse response = getAsServletResponse("gwc/service/wmts?" + queryRequest);
Document result = getResultAsDocument(response);
print(result);
// should hav gotten back everything
checkVectorElevationFullDomain(result);
}

@Test
public void testVectorDescribeDomainsOutsideWorld() throws Exception {
// spatial restriction is whole world but completely outside range, code should re-roll it
String queryRequest =
String.format(
"request=DescribeDomains&Version=1.0.0&Layer=%s&TileMatrixSet=EPSG:4326",
getLayerId(VECTOR_ELEVATION_TIME) + "&bbox=180,-90,540,90,EPSG:4326");
MockHttpServletResponse response = getAsServletResponse("gwc/service/wmts?" + queryRequest);
Document result = getResultAsDocument(response);
print(result);
// should hav gotten back everything
checkVectorElevationFullDomain(result);
}

@Test
public void testVectorDescribeDomainsAcrossDatelineWebMercator() throws Exception {
// spatial restriction across the dateline in 3857, the polygons are the 4 quadrants
// covering the world
String queryRequest =
String.format(
"request=DescribeDomains&Version=1.0.0&Layer=%s&TileMatrixSet=EPSG:4326",
getLayerId(VECTOR_ELEVATION_TIME)
+ "&bbox=19000000,-20000000,21000000,20000000,EPSG:3857");
MockHttpServletResponse response = getAsServletResponse("gwc/service/wmts?" + queryRequest);
Document result = getResultAsDocument(response);
print(result);
// should hav gotten back everything
checkVectorElevationFullDomain(result);
}

@Test
public void testVectorDescribeDomainsOutswideWorldWebMercator() throws Exception {
// spatial restriction outside of the valid 3857 domain, the polygons are the 4 quadrants
// covering the world
String queryRequest =
String.format(
"request=DescribeDomains&Version=1.0.0&Layer=%s&TileMatrixSet=EPSG:4326",
getLayerId(VECTOR_ELEVATION_TIME)
+ "&bbox=21000000,-20000000,59000000,20000000,EPSG:3857");
MockHttpServletResponse response = getAsServletResponse("gwc/service/wmts?" + queryRequest);
Document result = getResultAsDocument(response);
print(result);
// should hav gotten back everything
checkVectorElevationFullDomain(result);
}

public void checkVectorElevationFullDomain(Document result) throws Exception {
// check the space domain, should be regular whole world
assertXpathEvaluatesTo("1.1", "/md:Domains/@version", result); assertXpathEvaluatesTo("1.1", "/md:Domains/@version", result);
checkXpathCount(result, "/md:Domains/md:SpaceDomain/md:BoundingBox[@CRS='EPSG:4326']", "1"); checkXpathCount(result, "/md:Domains/md:SpaceDomain/md:BoundingBox[@CRS='EPSG:4326']", "1");
checkXpathCount(result, "/md:Domains/md:SpaceDomain/md:BoundingBox[@minx='-180.0']", "1"); checkXpathCount(result, "/md:Domains/md:SpaceDomain/md:BoundingBox[@minx='-180.0']", "1");
Expand All @@ -500,7 +606,8 @@ public void testVectorDescribeDomainsReprojectedFilter() throws Exception {
"1"); "1");
checkXpathCount( checkXpathCount(
result, result,
"/md:Domains/md:DimensionDomain[ows:Identifier = 'elevation' and md:Domain='1.0,2.0,3.0,5.0']", "/md:Domains/md:DimensionDomain[ows:Identifier = 'elevation' and md:Domain='1.0,2"
+ ".0,3.0,5.0']",
"1"); "1");
// check the time domain // check the time domain
checkXpathCount(result, "/md:Domains/md:DimensionDomain[ows:Identifier='time']", "1"); checkXpathCount(result, "/md:Domains/md:DimensionDomain[ows:Identifier='time']", "1");
Expand All @@ -510,7 +617,8 @@ public void testVectorDescribeDomainsReprojectedFilter() throws Exception {
"1"); "1");
checkXpathCount( checkXpathCount(
result, result,
"/md:Domains/md:DimensionDomain[ows:Identifier='time' and md:Domain='2012-02-11T00:00:00.000Z,2012-02-12T00:00:00.000Z']", "/md:Domains/md:DimensionDomain[ows:Identifier='time' and "
+ "md:Domain='2012-02-11T00:00:00.000Z,2012-02-12T00:00:00.000Z']",
"1"); "1");
} }


Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ _=geom:Polygon:srid=4326,id:java.lang.Integer,startTime:java.sql.Date,endTime:ja
TimeElevation.0=POLYGON((-180 90, 0 90, 0 0, -180 0, -180 90))|0|2012-02-11|2012-02-12|1|2 TimeElevation.0=POLYGON((-180 90, 0 90, 0 0, -180 0, -180 90))|0|2012-02-11|2012-02-12|1|2
TimeElevation.1=POLYGON((0 90, 180 90, 180 0, 0 0, 0 90))|1|2012-02-12|2012-02-13|2|3 TimeElevation.1=POLYGON((0 90, 180 90, 180 0, 0 0, 0 90))|1|2012-02-12|2012-02-13|2|3
TimeElevation.2=POLYGON((-180 -90, 0 -90, 0 0, -180 0, -180 -90))|2|2012-02-11|2012-02-13|3|4 TimeElevation.2=POLYGON((-180 -90, 0 -90, 0 0, -180 0, -180 -90))|2|2012-02-11|2012-02-13|3|4
TimeElevation.3=POLYGON((-180 -90, 0 -90, 0 0, -180 0, -180 -90))|2|2012-02-11|2012-02-13|5|7 TimeElevation.3=POLYGON((0 -90, 180 -90, 180 0, 0 0, 0 -90))|2|2012-02-11|2012-02-13|5|7

0 comments on commit eeb48be

Please sign in to comment.