/* (c) 2016 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.gwc.wmts; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.PublishedInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.gwc.layer.CatalogConfiguration; import org.geoserver.gwc.layer.GeoServerTileLayer; import org.geoserver.gwc.wmts.dimensions.Dimension; import org.geoserver.gwc.wmts.dimensions.DimensionsUtils; import org.geoserver.ows.LocalWorkspace; import org.geoserver.ows.util.KvpMap; import org.geoserver.ows.util.KvpUtils; import org.geoserver.platform.ServiceException; import org.geoserver.wms.WMS; import org.geotools.factory.CommonFactoryFinder; import org.geotools.filter.visitor.SimplifyingFilterVisitor; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.geotools.resources.CRSUtilities; import org.geotools.util.logging.Logging; import org.geowebcache.GeoWebCacheException; import org.geowebcache.conveyor.Conveyor; import org.geowebcache.grid.GridSubset; import org.geowebcache.grid.SRS; import org.geowebcache.io.XMLBuilder; import org.geowebcache.layer.TileLayer; import org.geowebcache.layer.TileLayerDispatcher; import org.geowebcache.service.OWSException; import org.geowebcache.service.wmts.WMTSExtensionImpl; import org.geowebcache.storage.StorageBroker; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * WMTS extension that provides the necessary metadata and operations * for handling multidimensional requests. */ public final class MultiDimensionalExtension extends WMTSExtensionImpl { private final static Logger LOGGER = Logging.getLogger(MultiDimensionalExtension.class); private final FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(); private TileLayerDispatcher tileLayerDispatcher; private final WMS wms; private final Catalog catalog; public MultiDimensionalExtension(WMS wms, Catalog catalog, TileLayerDispatcher tileLayerDispatcher) { this.wms = wms; this.catalog = catalog; this.tileLayerDispatcher = tileLayerDispatcher; } private final List<OperationMetadata> extraOperations = new ArrayList<>(); { extraOperations.add(new OperationMetadata("DescribeDomains")); extraOperations.add(new OperationMetadata("GetFeature")); extraOperations.add(new OperationMetadata("GetHistogram")); } @Override public List<OperationMetadata> getExtraOperationsMetadata() throws IOException { return extraOperations; } @Override public Conveyor getConveyor(HttpServletRequest request, HttpServletResponse response, StorageBroker storageBroker) throws GeoWebCacheException, OWSException { // parse the request parameters converting string raw values to java objects KvpMap parameters = KvpUtils.normalize(request.getParameterMap()); KvpUtils.parse(parameters); // let's see if we can handle this request String operationName = (String) parameters.get("request"); return Operation.match(operationName, request, response, storageBroker, parameters); } @Override public boolean handleRequest(Conveyor candidateConveyor) throws OWSException { if (!(candidateConveyor instanceof SimpleConveyor)) { return false; } SimpleConveyor conveyor = (SimpleConveyor) candidateConveyor; switch (conveyor.getOperation()) { case DESCRIBE_DOMAINS: try { executeDescribeDomainsOperation(conveyor); } catch (Exception exception) { LOGGER.log(Level.SEVERE, "Error executing describe domains operation.", exception); throw new OWSException(500, "NoApplicableCode", "", "Error executing describe domains operation:" + exception.getMessage()); } break; case GET_HISTOGRAM: try { executeGetHistogramOperation(conveyor); } catch (Exception exception) { LOGGER.log(Level.SEVERE, "Error executing get histogram operation.", exception); throw new OWSException(500, "NoApplicableCode", "", "Error executing get histogram operation:" + exception.getMessage()); } break; case GET_FEATURE: try { executeGetFeatureOperation(conveyor); } catch (Exception exception) { LOGGER.log(Level.SEVERE, "Error executing get feature operation.", exception); throw new OWSException(500, "NoApplicableCode", "", "Error executing get feature operation:" + exception.getMessage()); } break; default: return false; } return true; } @Override public void encodeLayer(XMLBuilder xmlBuilder, TileLayer tileLayer) throws IOException { LayerInfo layerInfo = getLayerInfo(tileLayer, tileLayer.getName()); if (layerInfo == null) { // dimension are not supported for this layer (maybe is a layer group) return; } List<Dimension> dimensions = DimensionsUtils.extractDimensions(wms, layerInfo); encodeLayerDimensions(xmlBuilder, dimensions); } private Domains getDomains(SimpleConveyor conveyor) throws Exception { // getting and parsing the mandatory parameters String layerName = (String) conveyor.getParameter("layer", true); TileLayer tileLayer = tileLayerDispatcher.getTileLayer(layerName); LayerInfo layerInfo = getLayerInfo(tileLayer, layerName); // getting this layer dimensions along with its values List<Dimension> dimensions = DimensionsUtils.extractDimensions(wms, layerInfo); // let's see if we have a spatial limitation ReferencedEnvelope boundingBox = (ReferencedEnvelope) conveyor.getParameter("bbox", false); // if we have a bounding box we need to set the crs based on the tile matrix set if (boundingBox != null) { String providedTileMatrixSet = (String) conveyor.getParameter("tileMatrixSet", true); // getting the layer grid set corresponding to the provided tile matrix set GridSubset gridSubset = tileLayer.getGridSubset(providedTileMatrixSet); if (gridSubset == null) { // the provided tile matrix set is not supported by this layer throw new RuntimeException(String.format("Unknown grid set '%s'.", providedTileMatrixSet)); } // set bounding box crs base on tile matrix tile set srs boundingBox = new ReferencedEnvelope(boundingBox, CRS.decode(gridSubset.getSRS().toString())); } // add any domain provided restriction and set the bounding box Filter filter = Filter.INCLUDE; for (Dimension dimension : dimensions) { Object restriction = conveyor.getParameter(dimension.getDimensionName(), false); dimension.setBoundingBox(boundingBox); dimension.addDomainRestriction(restriction); filter = filterFactory.and(filter, dimension.getFilter()); } // encode the domains return new Domains(dimensions, layerInfo, boundingBox, SimplifyingFilterVisitor.simplify(filter)); } private void executeDescribeDomainsOperation(SimpleConveyor conveyor) throws Exception { Domains domains = getDomains(conveyor); DescribeDomainsTransformer transformer = new DescribeDomainsTransformer(wms); transformer.transform(domains, conveyor.getResponse().getOutputStream()); conveyor.getResponse().setContentType("text/xml"); } private void executeGetHistogramOperation(SimpleConveyor conveyor) throws Exception { Domains domains = getDomains(conveyor); domains.setHistogram((String) conveyor.getParameter("histogram", true)); domains.setResolution((String) conveyor.getParameter("resolution", false)); HistogramTransformer transformer = new HistogramTransformer(wms); transformer.transform(domains, conveyor.getResponse().getOutputStream()); conveyor.getResponse().setContentType("text/xml"); } private void executeGetFeatureOperation(SimpleConveyor conveyor) throws Exception { Domains domains = getDomains(conveyor); FeaturesTransformer transformer = new FeaturesTransformer(wms); transformer.transform(domains, conveyor.getResponse().getOutputStream()); // right now we only support gml in the the future we may need to support other formats conveyor.getResponse().setContentType("text/xml; subtype=gml/3.1.1"); } private LayerInfo getLayerInfo(TileLayer tileLayer, String layerName) { // let's see if we can get the layer info from the tile layer if (tileLayer != null && tileLayer instanceof GeoServerTileLayer) { PublishedInfo publishedInfo = ((GeoServerTileLayer) tileLayer).getPublishedInfo(); if (!(publishedInfo instanceof LayerInfo)) { // dimensions are not supported for layers groups return null; } return (LayerInfo) publishedInfo; } // let's see if we are in the context of a virtual service WorkspaceInfo localWorkspace = LocalWorkspace.get(); if (localWorkspace != null) { // we need to make sure that the layer name is prefixed with the local workspace layerName = CatalogConfiguration.removeWorkspacePrefix(layerName, catalog); layerName = localWorkspace.getName() + ":" + layerName; } LayerInfo layerInfo = catalog.getLayerByName(layerName); if (layerInfo == null) { // the catalog is not aware of this layer, there is nothing we can do throw new ServiceException(String.format("Unknown layer '%s'.", layerName)); } return layerInfo; } /** * Helper method that will encode a layer dimensions, if the layer dimension are NULL or empty nothing will be done. */ private void encodeLayerDimensions(XMLBuilder xml, List<Dimension> dimensions) throws IOException { for (Dimension dimension : dimensions) { // encode each dimension as top element encodeLayerDimension(xml, dimension); } } /** * Helper method that will encode a dimension, if the dimension is NULL nothing will be done. All optional attributes * that are NULL will be ignored. */ private void encodeLayerDimension(XMLBuilder xml, Dimension dimension) throws IOException { xml.indentElement("Dimension"); // identifier is mandatory xml.simpleElement("ows:Identifier", dimension.getDimensionName(), true); // default value is mandatory xml.simpleElement("Default", dimension.getDefaultValueAsString(), true); for (String value : dimension.getDomainValuesAsStrings(Filter.INCLUDE).second.second) { xml.simpleElement("Value", value, true); } xml.endElement("Dimension"); } }