/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wcs2_0.response; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Logger; import net.opengis.wcs20.DescribeCoverageType; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CoverageDimensionInfo; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.DimensionInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.MetadataMap; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.wcs.CoverageCleanerCallback; import org.geoserver.wcs.WCSInfo; import org.geoserver.wcs.responses.CoverageResponseDelegateFinder; import org.geoserver.wcs.responses.GeoTIFFCoverageResponseDelegate; import org.geoserver.wcs2_0.GetCoverage; import org.geoserver.wcs2_0.WCS20Const; import org.geoserver.wcs2_0.exception.WCS20Exception; import org.geoserver.wcs2_0.util.EnvelopeAxesLabelsMapper; import org.geoserver.wcs2_0.util.NCNameResourceCodec; import org.geoserver.wcs2_0.util.RequestUtils; import org.geoserver.wcs2_0.util.WCS20DescribeCoverageExtension; import org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridEnvelope2D; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.geometry.GeneralEnvelope; import org.geotools.referencing.CRS; import org.geotools.referencing.CRS.AxisOrder; import org.geotools.util.logging.Logging; import org.geotools.wcs.v2_0.WCS; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.datum.PixelInCell; import org.vfny.geoserver.wcs.WcsException; import org.xml.sax.ContentHandler; import org.xml.sax.helpers.AttributesImpl; import org.xml.sax.helpers.NamespaceSupport; /** * Based on the <code>org.geotools.xml.transform</code> framework, does the * job of encoding a WCS 2.0.1 DescribeCoverage document. * * @author Emanuele Tajariol (etj) - GeoSolutions * @author Simone Giannecchini, GeoSolutions */ public class WCS20DescribeCoverageTransformer extends GMLTransformer { public static final Logger LOGGER = Logging.getLogger(WCS20DescribeCoverageTransformer.class .getPackage().getName()); private MIMETypeMapper mimemapper; private WCSInfo wcs; private Catalog catalog; private CoverageResponseDelegateFinder responseFactory; /** Available extension points for DescribeCoverage*/ private List<WCS20DescribeCoverageExtension> wcsDescribeCoverageExtensions; /** Boolean indicating that at least an extension point for the DescribeCoverage operation is available */ private boolean availableDescribeCoverageExtensions; /** * Creates a new WFSCapsTransformer object. * @param mimemapper */ public WCS20DescribeCoverageTransformer(WCSInfo wcs, Catalog catalog, CoverageResponseDelegateFinder responseFactory,EnvelopeAxesLabelsMapper envelopeDimensionsMapper, MIMETypeMapper mimemapper) { super(envelopeDimensionsMapper); this.wcs = wcs; this.catalog = catalog; this.responseFactory = responseFactory; this.mimemapper = mimemapper; setNamespaceDeclarationEnabled(false); setIndentation(2); this.wcsDescribeCoverageExtensions = GeoServerExtensions .extensions(WCS20DescribeCoverageExtension.class); this.availableDescribeCoverageExtensions = wcsDescribeCoverageExtensions != null && !wcsDescribeCoverageExtensions.isEmpty(); } public WCS20DescribeCoverageTranslator createTranslator(ContentHandler handler) { return new WCS20DescribeCoverageTranslator(handler); } public class WCS20DescribeCoverageTranslator extends GMLTranslator { private DescribeCoverageType request; private String proxifiedBaseUrl; public WCS20DescribeCoverageTranslator(ContentHandler handler) { super(handler); } /** * Encode the object. */ @Override public void encode(Object o) throws IllegalArgumentException { if (!(o instanceof DescribeCoverageType)) { throw new IllegalArgumentException(new StringBuffer("Not a DescribeCoverageType: ") .append(o).toString()); } this.request = (DescribeCoverageType) o; // collect coverages List<CoverageInfo> coverages = new ArrayList<CoverageInfo>(); List<String> covIds = new ArrayList<String>(); for (String encodedCoverageId : (List<String>)request.getCoverageId()) { String newCoverageID = encodedCoverageId; // Extension point for encoding the coverageId if (availableDescribeCoverageExtensions) { for (WCS20DescribeCoverageExtension ext : wcsDescribeCoverageExtensions) { newCoverageID = ext.handleCoverageId(newCoverageID); } } LayerInfo layer = NCNameResourceCodec.getCoverage(catalog, newCoverageID); if(layer != null) { coverages.add((CoverageInfo) layer.getResource()); covIds.add(encodedCoverageId); } else { // if we get there there is an internal error, the coverage existence is // checked before creating the transformer throw new IllegalArgumentException("Failed to locate coverage " + encodedCoverageId + ", unexpected, the coverage existance has been " + "checked earlier in the request lifecycle"); } } // register namespaces provided by extended capabilities NamespaceSupport namespaces = getNamespaceSupport(); namespaces.declarePrefix("swe", "http://www.opengis.net/swe/2.0"); namespaces.declarePrefix("wcsgs", "http://www.geoserver.org/wcsgs/2.0"); for (WCS20CoverageMetadataProvider cp : extensions) { cp.registerNamespaces(namespaces); } // ok: build the response final AttributesImpl attributes = WCS20Const.getDefaultNamespaces(); helper.registerNamespaces(getNamespaceSupport(), attributes); String location = buildSchemaLocation(request.getBaseUrl(), WCS.NAMESPACE, "http://schemas.opengis.net/wcs/2.0/wcsDescribeCoverage.xsd"); attributes.addAttribute("", "xsi:schemaLocation", "xsi:schemaLocation", "", location); start("wcs:CoverageDescriptions", attributes); int coverageIndex = 0; for (CoverageInfo ci : coverages) { try { String encodedId = NCNameResourceCodec.encode(ci); CoverageInfo ciNew = ci; String newCoverageID = covIds.get(coverageIndex); // Extension point for encoding the coverageId if (availableDescribeCoverageExtensions) { for (WCS20DescribeCoverageExtension ext : wcsDescribeCoverageExtensions) { newCoverageID = ext.handleEncodedId(location, newCoverageID); ciNew = ext.handleCoverageInfo(covIds.get(coverageIndex), ci); } } else { newCoverageID = encodedId; } handleCoverageDescription(newCoverageID, ciNew); coverageIndex++; } catch (Exception e) { throw new RuntimeException("Unexpected error occurred during describe coverage xml encoding", e); } } end("wcs:CoverageDescriptions"); } String buildSchemaLocation(String schemaBaseURL, String... locations) { for (WCS20CoverageMetadataProvider cp : extensions) { locations = helper.append(locations, cp.getSchemaLocations(schemaBaseURL)); } return helper.buildSchemaLocation(locations); } /** * * @param ci */ public void handleCoverageDescription(String encodedId, CoverageInfo ci) { try { // see if we have to handle time, elevation and additional dimensions WCSDimensionsHelper dimensionsHelper = null; MetadataMap metadata = ci.getMetadata(); Map<String, DimensionInfo> dimensionsMap = WCSDimensionsHelper.getDimensionsFromMetadata(metadata); // Setup a dimension helper in case we found some dimensions for that coverage if (!dimensionsMap.isEmpty()) { dimensionsHelper = new WCSDimensionsHelper(dimensionsMap, RequestUtils.getCoverageReader(ci), encodedId); } GridCoverage2DReader reader = (GridCoverage2DReader) ci.getGridCoverageReader(null, null); if (reader== null) { throw new WCS20Exception("Unable to read sample coverage for " + ci.getName()); } // get the crs and look for an EPSG code final CoordinateReferenceSystem crs = reader.getCoordinateReferenceSystem(); List<String> axesNames = envelopeDimensionsMapper.getAxesNames( reader.getOriginalEnvelope(), true); // lookup EPSG code Integer EPSGCode = null; try { EPSGCode = CRS.lookupEpsgCode(crs, false); } catch (FactoryException e) { throw new IllegalStateException("Unable to lookup epsg code for this CRS:" + crs, e); } if (EPSGCode == null) { throw new IllegalStateException("Unable to lookup epsg code for this CRS:" + crs); } final String srsName = GetCoverage.SRS_STARTER + EPSGCode; // handle axes swap for geographic crs final boolean axisSwap = !CRS.getAxisOrder(CRS.decode(srsName)).equals(AxisOrder.EAST_NORTH); // encoding ID of the coverage final AttributesImpl coverageAttributes = new AttributesImpl(); coverageAttributes.addAttribute("", "gml:id", "gml:id", "", encodedId); // starting encoding start("wcs:CoverageDescription", coverageAttributes); // handle domain final StringBuilder builder = new StringBuilder(); for (String axisName : axesNames) { builder.append(axisName).append(" "); } if (dimensionsHelper != null && dimensionsHelper.getElevationDimension() != null) { builder.append("elevation "); } if (dimensionsHelper != null && dimensionsHelper.getTimeDimension() != null) { builder.append("time "); } String axesLabel = builder.substring(0, builder.length() - 1); GeneralEnvelope envelope = reader.getOriginalEnvelope(); handleBoundedBy(envelope, axisSwap, srsName, axesLabel, dimensionsHelper); // coverage id element("wcs:CoverageId", encodedId); // handle coverage function handleCoverageFunction((GridEnvelope2D) reader.getOriginalGridRange(), axisSwap); // metadata handleMetadata(ci, dimensionsHelper); // handle domain builder.setLength(0); axesNames = envelopeDimensionsMapper.getAxesNames(reader.getOriginalEnvelope(), false); for (String axisName : axesNames) { builder.append(axisName).append(" "); } axesLabel = builder.substring(0, builder.length() - 1); GridGeometry2D gg = new GridGeometry2D(reader.getOriginalGridRange(), reader.getOriginalGridToWorld(PixelInCell.CELL_CENTER), reader.getCoordinateReferenceSystem()); handleDomainSet(gg, 2, encodedId, srsName, axisSwap); // handle rangetype handleRangeType(ci.getDimensions()); // service parameters handleServiceParameters(ci); end("wcs:CoverageDescription"); } catch (Exception e) { throw new WcsException(e); } } private GridSampleDimension[] getSampleDimensions(GridCoverage2DReader reader) throws Exception { GridCoverage2D coverage = null; try { coverage = RequestUtils.readSampleGridCoverage(reader); return coverage.getSampleDimensions(); } finally { if(coverage != null) { CoverageCleanerCallback.addCoverages(coverage); } } } private void handleServiceParameters(CoverageInfo ci) throws IOException { start("wcs:ServiceParameters"); element("wcs:CoverageSubtype", "RectifiedGridCoverage"); String mapNativeFormat = mimemapper.mapNativeFormat(ci); element("wcs:nativeFormat",mapNativeFormat); end("wcs:ServiceParameters"); } /** * Encodes the RangeType as per the {@link DescribeCoverageType}WCS spec of the provided {@link GridCoverage2D} * * e.g.: * * <pre> * {@code * <gmlcov:rangeType> * <swe:DataRecord> * <swe:field name="singleBand"> * <swe:Quantity definition="http://www.opengis.net/def/property/OGC/0/Radiance"> * <gml:description>Panchromatic Channel</gml:description> * <gml:name>single band</gml:name> * <swe:uom code="W/cm2"/> * <swe:constraint> * <swe:AllowedValues> * <swe:interval>0 255</swe:interval> * <swe:significantFigures>3</swe:significantFigures> * </swe:AllowedValues> * </swe:constraint> * </swe:Quantity> * </swe:field> * </swe:DataRecord> * </gmlcov:rangeType> * } * </pre> * * @param gc2d the {@link GridCoverage2D} for which to encode the RangeType. */ public void handleRangeType(final List<CoverageDimensionInfo> bands) { start("gmlcov:rangeType"); start("swe:DataRecord"); // handle bands for(CoverageDimensionInfo sd : bands){ final AttributesImpl fieldAttr = new AttributesImpl(); fieldAttr.addAttribute("", "name", "name", "", sd.getName()); start("swe:field",fieldAttr); start("swe:Quantity"); // Description start("swe:description"); chars(sd.getName()); // TODO can we make up something better?? end("swe:description"); // nil values List<Double> nullValues = sd.getNullValues(); if (nullValues != null && !nullValues.isEmpty()) { final int size = nullValues.size(); double[] noDataValues = new double[size]; for (int i = 0; i < size; i++) { noDataValues[i] = nullValues.get(i); } handleSampleDimensionNilValues(null, noDataValues); } //UoM final AttributesImpl uomAttr = new AttributesImpl(); final String unit =sd.getUnit(); uomAttr.addAttribute("", "code", "code", "", unit == null ? "W.m-2.Sr-1" : unit); start("swe:uom",uomAttr); end("swe:uom"); // constraint on values start("swe:constraint"); start("swe:AllowedValues"); handleSampleDimensionRange(sd);// TODO make this generic end("swe:AllowedValues"); end("swe:constraint"); end("swe:Quantity"); end("swe:field"); } end("swe:DataRecord"); end("gmlcov:rangeType"); } } }