Package org.geoserver.wcs

Source Code of org.geoserver.wcs.DefaultWebCoverageService111

/* (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.wcs;

import static org.vfny.geoserver.wcs.WcsException.WcsExceptionCode.InvalidParameterValue;

import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.jai.Interpolation;

import net.opengis.ows11.BoundingBoxType;
import net.opengis.ows11.CodeType;
import net.opengis.wcs11.AxisSubsetType;
import net.opengis.wcs11.DescribeCoverageType;
import net.opengis.wcs11.DomainSubsetType;
import net.opengis.wcs11.FieldSubsetType;
import net.opengis.wcs11.GetCapabilitiesType;
import net.opengis.wcs11.GetCoverageType;
import net.opengis.wcs11.GridCrsType;
import net.opengis.wcs11.OutputType;
import net.opengis.wcs11.RangeSubsetType;
import net.opengis.wcs11.TimePeriodType;
import net.opengis.wcs11.TimeSequenceType;

import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CoverageDimensionInfo;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.data.util.CoverageUtils;
import org.geoserver.ows.util.RequestUtils;
import org.geoserver.wcs.kvp.GridCS;
import org.geoserver.wcs.kvp.GridType;
import org.geoserver.wcs.response.DescribeCoverageTransformer;
import org.geoserver.wcs.response.WCSCapsTransformer;
import org.geoserver.wcs.responses.CoverageResponseDelegate;
import org.geoserver.wcs.responses.CoverageResponseDelegateFinder;
import org.geotools.coverage.grid.GeneralGridGeometry;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.gml2.bindings.GML2EncodingUtils;
import org.geotools.parameter.DefaultParameterDescriptor;
import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.matrix.XAffineTransform;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.referencing.operation.transform.IdentityTransform;
import org.geotools.util.logging.Logging;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.filter.Filter;
import org.opengis.geometry.Envelope;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.CoordinateOperationFactory;
import org.opengis.referencing.operation.MathTransform;
import org.vfny.geoserver.util.WCSUtils;
import org.vfny.geoserver.wcs.WcsException;
import org.vfny.geoserver.wcs.WcsException.WcsExceptionCode;

public class DefaultWebCoverageService111 implements WebCoverageService111 {
    Logger LOGGER = Logging.getLogger(DefaultWebCoverageService111.class);

    private Catalog catalog;

    private GeoServer geoServer;

    private CoverageResponseDelegateFinder responseFactory;

    public DefaultWebCoverageService111(GeoServer geoServer,
            CoverageResponseDelegateFinder responseFactory) {
        this.geoServer = geoServer;
        this.catalog = geoServer.getCatalog();
        this.responseFactory = responseFactory;
    }

    public WCSInfo getServiceInfo() {
        return geoServer.getService(WCSInfo.class);
    }

    public WCSCapsTransformer getCapabilities(GetCapabilitiesType request) {
        // do the version negotiation dance
        List<String> provided = new ArrayList<String>();
        // provided.add("1.0.0");
        provided.add("1.1.0");
        provided.add("1.1.1");
        List<String> accepted = null;
        if (request.getAcceptVersions() != null)
            accepted = request.getAcceptVersions().getVersion();
        String version = RequestUtils.getVersionOws11(provided, accepted);

        // TODO: add support for 1.0.0 in here

        if ("1.1.0".equals(version) || "1.1.1".equals(version)) {
            WCSCapsTransformer capsTransformer = new WCSCapsTransformer(geoServer);
            capsTransformer.setEncoding(Charset.forName((getServiceInfo().getGeoServer()
                    .getSettings().getCharset())));
            return capsTransformer;
        }

        throw new WcsException("Could not understand version:" + version);
    }

    public DescribeCoverageTransformer describeCoverage(DescribeCoverageType request) {
        final String version = request.getVersion();
        if ("1.1.0".equals(version) || "1.1.1".equals(version)) {
            WCSInfo wcs = getServiceInfo();
            DescribeCoverageTransformer describeTransformer = new DescribeCoverageTransformer(wcs,
                    catalog, responseFactory);
            describeTransformer.setEncoding(Charset.forName(wcs.getGeoServer().getSettings()
                    .getCharset()));
            return describeTransformer;
        }

        throw new WcsException("Could not understand version:" + version);
    }

    @SuppressWarnings({ "deprecation", "unchecked" })
    public GridCoverage[] getCoverage(GetCoverageType request) {
        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.finest(new StringBuffer("execute CoverageRequest response. Called request is: ")
                    .append(request).toString());
        }

        WCSInfo wcs = getServiceInfo();

        CoverageInfo meta = null;
        GridCoverage2D coverage = null;
        try {
            CodeType identifier = request.getIdentifier();
            if (identifier == null)
                throw new WcsException("Internal error, the coverage identifier must not be null",
                        InvalidParameterValue, "identifier");
            meta = catalog.getCoverageByName(identifier.getValue());
            if (meta == null) {
                throw new WcsException("No such coverage: " + request.getIdentifier().getValue());
            }

            // first let's run some sanity checks on the inputs
            checkDomainSubset(meta, request.getDomainSubset(), wcs);
            checkRangeSubset(meta, request.getRangeSubset());
            checkOutput(meta, request.getOutput());

            // grab the format, the reader using the default params
            final GridCoverage2DReader reader = (GridCoverage2DReader) meta.getGridCoverageReader(
                    null, WCSUtils.getReaderHints(wcs));

            // handle spatial domain subset, if needed
            final GeneralEnvelope originalEnvelope = reader.getOriginalEnvelope();
            final BoundingBoxType bbox = request.getDomainSubset().getBoundingBox();
            final CoordinateReferenceSystem nativeCRS = originalEnvelope
                    .getCoordinateReferenceSystem();
            final GeneralEnvelope requestedEnvelopeInNativeCRS;
            final GeneralEnvelope requestedEnvelope;
            if (bbox != null) {
                // first off, parse the envelope corners
                double[] lowerCorner = new double[bbox.getLowerCorner().size()];
                double[] upperCorner = new double[bbox.getUpperCorner().size()];
                for (int i = 0; i < lowerCorner.length; i++) {
                    lowerCorner[i] = (Double) bbox.getLowerCorner().get(i);
                    upperCorner[i] = (Double) bbox.getUpperCorner().get(i);
                }
                requestedEnvelope = new GeneralEnvelope(lowerCorner, upperCorner);
                // grab the native crs
                // if no crs has beens specified, the native one is assumed
                if (bbox.getCrs() == null) {
                    requestedEnvelope.setCoordinateReferenceSystem(nativeCRS);
                    requestedEnvelopeInNativeCRS = requestedEnvelope;
                } else {
                    // otherwise we need to transform
                    final CoordinateReferenceSystem bboxCRS = CRS.decode(bbox.getCrs());
                    requestedEnvelope.setCoordinateReferenceSystem(bboxCRS);
                    if (!CRS.equalsIgnoreMetadata(bboxCRS, nativeCRS)) {
                        CoordinateOperationFactory of = CRS.getCoordinateOperationFactory(true);
                        CoordinateOperation co = of.createOperation(bboxCRS, nativeCRS);
                        requestedEnvelopeInNativeCRS = CRS.transform(co, requestedEnvelope);
                    } else {
                        requestedEnvelopeInNativeCRS = new GeneralEnvelope(requestedEnvelope);
                    }
                }
            } else {
                requestedEnvelopeInNativeCRS = reader.getOriginalEnvelope();
                requestedEnvelope = requestedEnvelopeInNativeCRS;
            }

            final GridCrsType gridCRS = request.getOutput().getGridCRS();

            // Compute the crs that the final coverage will be served into
            final CoordinateReferenceSystem targetCRS;
            if (gridCRS == null) {
                targetCRS = reader.getOriginalEnvelope().getCoordinateReferenceSystem();
            } else {
                targetCRS = CRS.decode(gridCRS.getGridBaseCRS());
            }

            //
            // Raster destination size
            //
            int elevationLevels = 0;
            double[] elevations = null;

            // grab the grid to world transformation
            MathTransform gridToCRS = reader.getOriginalGridToWorld(PixelInCell.CELL_CENTER);

            //
            // TIME Values
            //
            final List<Date> timeValues = new LinkedList<Date>();

            TimeSequenceType temporalSubset = request.getDomainSubset().getTemporalSubset();

            if (temporalSubset != null && temporalSubset.getTimePosition() != null
                    && temporalSubset.getTimePosition().size() > 0) {
                for (Iterator it = temporalSubset.getTimePosition().iterator(); it.hasNext();) {
                    Date tp = (Date) it.next();
                    timeValues.add(tp);
                }
            } else if (temporalSubset != null && temporalSubset.getTimePeriod() != null
                    && temporalSubset.getTimePeriod().size() > 0) {
                for (Iterator it = temporalSubset.getTimePeriod().iterator(); it.hasNext();) {
                    TimePeriodType tp = (TimePeriodType) it.next();
                    Date beginning = (Date) tp.getBeginPosition();
                    Date ending = (Date) tp.getEndPosition();

                    timeValues.add(beginning);
                    timeValues.add(ending);
                }
            }

            // now we have enough info to read the coverage, grab the parameters
            // and add the grid geometry info
            final GeneralEnvelope intersectionEnvelopeInSourceCRS = new GeneralEnvelope(
                    requestedEnvelopeInNativeCRS);
            intersectionEnvelopeInSourceCRS.intersect(originalEnvelope);

            final GridGeometry2D requestedGridGeometry = new GridGeometry2D(
                    PixelInCell.CELL_CENTER, gridToCRS, intersectionEnvelopeInSourceCRS, null);

            final ParameterValueGroup readParametersDescriptor = reader.getFormat()
                    .getReadParameters();
            GeneralParameterValue[] readParameters = CoverageUtils.getParameters(
                    readParametersDescriptor, meta.getParameters());
            readParameters = (readParameters != null ? readParameters
                    : new GeneralParameterValue[0]);

            //
            // Setting coverage reading params.
            //
            final ParameterValue requestedGridGeometryParam = new DefaultParameterDescriptor(
                    AbstractGridFormat.READ_GRIDGEOMETRY2D.getName().toString(),
                    GeneralGridGeometry.class, null, requestedGridGeometry).createValue();

            /*
             * Test if the parameter "TIME" is present in the WMS request, and by the way in the reading parameters. If it is the case, one can adds
             * it to the request. If an exception is thrown, we have nothing to do.
             */
            final List<GeneralParameterDescriptor> parameterDescriptors = readParametersDescriptor
                    .getDescriptor().descriptors();
            ParameterValue time = null;
            boolean hasTime = timeValues.size() > 0;
            ParameterValue elevation = null;
            boolean hasElevation = elevations != null && !Double.isNaN(elevations[0]);

            if (hasElevation || hasTime) {
                for (GeneralParameterDescriptor pd : parameterDescriptors) {

                    final String code = pd.getName().getCode();

                    //
                    // TIME
                    //
                    if (code.equalsIgnoreCase("TIME")) {
                        time = (ParameterValue) pd.createValue();
                        time.setValue(timeValues);
                    }

                    //
                    // ELEVATION
                    //
                    if (code.equalsIgnoreCase("ELEVATION")) {
                        elevation = (ParameterValue) pd.createValue();
                        elevation.setValue(elevations[0]);
                    }

                    // leave?
                    if ((hasElevation && elevation != null && hasTime && time != null)
                            || !hasElevation && hasTime && time != null || hasElevation
                            && elevation != null && !hasTime)
                        break;
                }
            }
            //
            // add read parameters
            //
            int addedParams = 1 + (hasTime ? 1 : 0) + (hasElevation ? 1 : 0);
            // add to the list
            GeneralParameterValue[] readParametersClone = new GeneralParameterValue[readParameters.length
                    + addedParams--];
            System.arraycopy(readParameters, 0, readParametersClone, 0, readParameters.length);
            readParametersClone[readParameters.length + addedParams--] = requestedGridGeometryParam;
            if (hasTime)
                readParametersClone[readParameters.length + addedParams--] = time;
            if (hasElevation)
                readParametersClone[readParameters.length + addedParams--] = elevation;
            readParameters = readParametersClone;

            // Check we're not being requested to read too much data from input (first check,
            // guesses the grid size using the information contained in CoverageInfo)
            WCSUtils.checkInputLimits(wcs, meta, reader, requestedGridGeometry);

            //
            // Check if we have a filter among the params
            //
            Filter filter = WCSUtils.getRequestFilter();
            if (filter != null) {
                readParameters = CoverageUtils.mergeParameter(parameterDescriptors, readParameters,
                        filter, "FILTER", "Filter");
            }

            //
            // make sure we work in streaming mode
            //
            // work in streaming fashion when JAI is involved
            readParameters = WCSUtils.replaceParameter(readParameters, Boolean.FALSE,
                    AbstractGridFormat.USE_JAI_IMAGEREAD);

            //
            // perform Read ...
            //
            coverage = (GridCoverage2D) reader.read(readParameters);
            if ((coverage == null) || !(coverage instanceof GridCoverage2D)) {
                throw new IOException("The requested coverage could not be found.");
            }

            // now that we have read the coverage double check the input size
            WCSUtils.checkInputLimits(wcs, coverage);

            // some raster sources do not really read less data (arcgrid for example), we may need to crop
            if (!intersectionEnvelopeInSourceCRS.contains(coverage.getEnvelope2D(), true)) {
                coverage = WCSUtils.crop(coverage, intersectionEnvelopeInSourceCRS);
            }

            /**
             * Band Select (works on just one field)
             */
            GridCoverage2D bandSelectedCoverage = coverage;
            String interpolationType = null;
            if (request.getRangeSubset() != null) {
                if (request.getRangeSubset().getFieldSubset().size() > 1) {
                    throw new WcsException("Multi field coverages are not supported yet");
                }

                FieldSubsetType field = (FieldSubsetType) request.getRangeSubset().getFieldSubset()
                        .get(0);
                interpolationType = field.getInterpolationType();

                // handle axis subset
                if (field.getAxisSubset().size() > 1) {
                    throw new WcsException("Multi axis coverages are not supported yet");
                }
                if (field.getAxisSubset().size() == 1) {
                    // prepare a support structure to quickly get the band index
                    // of a
                    // key
                    List<CoverageDimensionInfo> dimensions = meta.getDimensions();
                    Map<String, Integer> dimensionMap = new HashMap<String, Integer>();
                    for (int i = 0; i < dimensions.size(); i++) {
                        String keyName = dimensions.get(i).getName().replace(' ', '_');
                        dimensionMap.put(keyName, i);
                    }

                    // extract the band indexes
                    AxisSubsetType axisSubset = (AxisSubsetType) field.getAxisSubset().get(0);
                    List keys = axisSubset.getKey();
                    int[] bands = new int[keys.size()];
                    for (int j = 0; j < bands.length; j++) {
                        final String key = (String) keys.get(j);
                        Integer index = dimensionMap.get(key);
                        if (index == null)
                            throw new WcsException("Unknown field/axis/key combination "
                                    + field.getIdentifier().getValue() + "/"
                                    + axisSubset.getIdentifier() + "/" + key);
                        bands[j] = index;
                    }

                    // finally execute the band select
                    try {
                        bandSelectedCoverage = (GridCoverage2D) WCSUtils
                                .bandSelect(coverage, bands);
                    } catch (WcsException e) {
                        throw new WcsException(e.getLocalizedMessage());
                    }
                }
            }

            /**
             * Checking for supported Interpolation Methods
             */
            Interpolation interpolation = Interpolation.getInstance(Interpolation.INTERP_NEAREST);
            if (interpolationType != null) {
                if (interpolationType.equalsIgnoreCase("linear") || interpolationType.equalsIgnoreCase("bilinear")) {
                    interpolation = Interpolation.getInstance(Interpolation.INTERP_BILINEAR);
                } else if (interpolationType.equalsIgnoreCase("cubic") || interpolationType.equalsIgnoreCase("bicubic")) {
                    interpolation = Interpolation.getInstance(Interpolation.INTERP_BICUBIC);
                } else if (interpolationType.equalsIgnoreCase("nearest")) {
                    interpolation = Interpolation.getInstance(Interpolation.INTERP_NEAREST);
                }
            }

            // adjust the grid geometry to use the final bbox and crs
            final GeneralEnvelope intersectionEnvelope;
            boolean reprojectionNeeded = !CRS.equalsIgnoreMetadata(nativeCRS, targetCRS);
            if (reprojectionNeeded) {
                CoordinateOperationFactory of = CRS.getCoordinateOperationFactory(true);
                CoordinateOperation co = of.createOperation(nativeCRS, targetCRS);
                intersectionEnvelope = CRS.transform(co, intersectionEnvelopeInSourceCRS);
            } else {
                intersectionEnvelope = intersectionEnvelopeInSourceCRS;
            }

            // compute the output resolution
            double pixelSizeX;
            double pixelSizeY;
            if (gridCRS != null) {
                Double[] origin = (Double[]) gridCRS.getGridOrigin();
                Double[] offsets = (Double[]) gridCRS.getGridOffsets();

                // from the specification if grid origin is omitted and the crs
                // is 2d the default it's 0,0
                if (origin == null) {
                    origin = new Double[] { 0.0, 0.0 };
                }

                // if no offsets has been specified we try to default on the
                // native ones
                if (offsets == null) {
                    offsets = estimateOffsets(reader, gridCRS, gridToCRS, intersectionEnvelope,
                            reprojectionNeeded);
                }

                // building the actual transform for the resulting grid geometry
                AffineTransform tx;
                if (gridCRS.getGridType().equals(GridType.GT2dSimpleGrid.getXmlConstant())) {
                    tx = new AffineTransform(offsets[0], 0, 0, offsets[1], origin[0], origin[1]);
                } else if (gridCRS.getGridType().equals(GridType.GT2dGridIn2dCrs.getXmlConstant())) {
                    tx = new AffineTransform(offsets[0], offsets[1], offsets[2], offsets[3],
                            origin[0], origin[1]);
                } else {
                    tx = new AffineTransform(offsets[0], offsets[4], offsets[1], offsets[3],
                            origin[0], origin[1]);

                    if (origin.length != 3 || offsets.length != 6)
                        throw new WcsException("", InvalidParameterValue, "GridCRS");

                    //
                    // ELEVATIONS
                    //

                    // TODO: draft code ... it needs more study!
                    elevationLevels = (int) Math.round(requestedEnvelope.getUpperCorner()
                            .getOrdinate(2) - requestedEnvelope.getLowerCorner().getOrdinate(2));

                    // compute the elevation levels, we have elevationLevels values
                    if (elevationLevels > 0) {
                        elevations = new double[elevationLevels];

                        elevations[0] = requestedEnvelope.getLowerCorner().getOrdinate(2); // TODO put the extrema
                        elevations[elevationLevels - 1] = requestedEnvelope.getUpperCorner()
                                .getOrdinate(2);
                        if (elevationLevels > 2) {
                            final int adjustedLevelsNum = elevationLevels - 1;
                            double step = (elevations[elevationLevels - 1] - elevations[0])
                                    / adjustedLevelsNum;
                            for (int i = 1; i < adjustedLevelsNum; i++)
                                elevations[i] = elevations[i - 1] + step;
                        }
                    }
                }
                pixelSizeX = Math.abs(tx.getScaleX());
                pixelSizeY = Math.abs(tx.getScaleY());
                gridToCRS = new AffineTransform2D(tx);
            } else {
                Double[] offsets = estimateOffsets(reader, gridCRS, gridToCRS,
                        intersectionEnvelope, reprojectionNeeded);
                if (offsets.length == 2) {
                    pixelSizeX = Math.abs(offsets[0]);
                    pixelSizeY = Math.abs(offsets[1]);
                    AffineTransform tx = new AffineTransform(offsets[0], 0, 0, offsets[1], 0, 0);
                    gridToCRS = new AffineTransform2D(tx);
                } else {
                    AffineTransform tx = new AffineTransform(offsets[0], offsets[1], offsets[3],
                            offsets[4], 0, 0);
                    pixelSizeX = Math.abs(XAffineTransform.getScaleX0(tx));
                    pixelSizeY = Math.abs(XAffineTransform.getScaleY0(tx));
                    gridToCRS = new AffineTransform2D(tx);
                }
            }

            /**
             * Reproject
             */
            // adjust to have at least one pixel in the output
            if (intersectionEnvelope.getSpan(0) < Math.abs(pixelSizeX)) {
                double minX = intersectionEnvelope.getMinimum(0);
                intersectionEnvelope.setRange(0, minX, minX + pixelSizeX);
            }
            if (intersectionEnvelope.getSpan(1) < Math.abs(pixelSizeY)) {
                double minY = intersectionEnvelope.getMinimum(1);
                intersectionEnvelope.setRange(1, minY, minY + pixelSizeY);
            }

            final GridGeometry2D destinationGridGeometry = new GridGeometry2D(
                    PixelInCell.CELL_CENTER, gridToCRS, intersectionEnvelope, null);

            // before extracting the output make sure it's not too big
            WCSUtils.checkOutputLimits(wcs, destinationGridGeometry.getGridRange2D(),
                    bandSelectedCoverage.getRenderedImage().getSampleModel());

            // reproject if necessary
            boolean sameGridGeometry = bandSelectedCoverage.getGridGeometry().equals(
                    destinationGridGeometry);
            if (reprojectionNeeded || !sameGridGeometry) {
                final GridCoverage2D reprojectedCoverage = WCSUtils.resample(bandSelectedCoverage,
                        nativeCRS, targetCRS, destinationGridGeometry, interpolation);

                return new GridCoverage[] { reprojectedCoverage };
            } else {
                return new GridCoverage[] { bandSelectedCoverage };
            }
        } catch (Throwable e) {
            CoverageCleanerCallback.addCoverages(coverage);
            if (e instanceof WcsException) {
                throw (WcsException) e;
            } else {
                throw new WcsException(e);
            }
        }

    }

    private Double[] estimateOffsets(final GridCoverage2DReader reader, final GridCrsType gridCRS,
            MathTransform gridToCRS, final GeneralEnvelope intersectionEnvelope,
            boolean reprojectionNeeded) {
        Double[] offsets;
        if (!(gridToCRS instanceof AffineTransform2D) && !(gridToCRS instanceof IdentityTransform))
            throw new WcsException(
                    "Internal error, the coverage we're playing with does not have an affine transform...");

        if (!reprojectionNeeded) {
            if (gridCRS != null) {
                if (gridToCRS instanceof IdentityTransform) {
                    if (gridCRS.getGridType().equals(GridType.GT2dSimpleGrid.getXmlConstant())
                            || gridCRS.getGridType().equals(
                                    GridType.GT2dGridIn2dCrs.getXmlConstant()))
                        offsets = new Double[] { 1.0, -1.0 };
                    else
                        offsets = new Double[] { 1.0, 0.0, 0.0, 0.0, -1.0, 0.0 };
                } else {
                    AffineTransform2D affine = (AffineTransform2D) gridToCRS;
                    if (gridCRS.getGridType().equals(GridType.GT2dSimpleGrid.getXmlConstant())
                            || gridCRS.getGridType().equals(
                                    GridType.GT2dGridIn2dCrs.getXmlConstant()))
                        offsets = new Double[] { affine.getScaleX(), affine.getScaleY() };
                    else
                        offsets = new Double[] { affine.getScaleX(), affine.getShearX(),
                                affine.getShearY(), affine.getScaleY() };
                }
            } else {
                AffineTransform2D at = (AffineTransform2D) gridToCRS;
                offsets = new Double[] { at.getScaleX(), at.getShearX(), 0d, at.getShearY(),
                        at.getScaleY(), 0d };
            }
        } else {
            // the input resolution is going to be completed unrelated to the output one
            // make an estimate assuming we want to keep the output raster with roughly
            // the same size as the input one
            double teWidth = intersectionEnvelope.getSpan(0);
            double teHeight = intersectionEnvelope.getSpan(1);

            double targetRatio = teWidth / teHeight;
            GridEnvelope gr = reader.getOriginalGridRange();
            int targetRasterWidth = gr.getSpan(0);
            int targetRasterHeight = (int) Math.ceil(targetRasterWidth / targetRatio);
            double scaleX = teWidth / targetRasterWidth;
            double scaleY = -teHeight / targetRasterHeight;

            if (gridCRS == null
                    || gridCRS.getGridType().equals(GridType.GT2dSimpleGrid.getXmlConstant())
                    || gridCRS.getGridType().equals(GridType.GT2dGridIn2dCrs.getXmlConstant())) {
                offsets = new Double[] { scaleX, scaleY };
            } else {
                offsets = new Double[] { scaleX, 0.0, 0.0, 0.0, scaleY, 0.0 };
            }
        }
        return offsets;
    }

    private void checkDomainSubset(CoverageInfo meta, DomainSubsetType domainSubset, WCSInfo wcs)
            throws Exception {
        BoundingBoxType bbox = domainSubset.getBoundingBox();

        // domain subset should actually be always specified, but we try to be more lenient
        // (we should probably have a "strict cite" behavior
        if (bbox == null) {
            return;
        }

        // workaround for http://jira.codehaus.org/browse/GEOT-1710
        if ("urn:ogc:def:crs:OGC:1.3:CRS84".equals(bbox.getCrs())) {
            bbox.setCrs("EPSG:4326");
        }

        CoordinateReferenceSystem bboxCRs = CRS.decode(bbox.getCrs());
        GridCoverage2DReader reader = (GridCoverage2DReader) meta.getGridCoverageReader(null,
                WCSUtils.getReaderHints(wcs));
        Envelope gridEnvelope = reader.getOriginalEnvelope();
        GeneralEnvelope gridEnvelopeBboxCRS = null;
        if (bboxCRs instanceof GeographicCRS) {
            try {
                CoordinateOperationFactory cof = CRS.getCoordinateOperationFactory(true);

                final CoordinateOperation operation = cof.createOperation(
                        gridEnvelope.getCoordinateReferenceSystem(), bboxCRs);
                gridEnvelopeBboxCRS = CRS.transform(operation, gridEnvelope);
            } catch (Exception e) {
                // this may happen, there is nothing we can do about it, we just
                // use the back transformed envelope to be more lenient about
                // which coordinate coorections to make on the longitude axis
                // should the antimeridian style be used
            }
        }

        // check the coordinates, but make sure the case 175,-175 is handled
        // as valid for the longitude axis in a geographic coordinate system
        // see section 7.6.2 of the WCS 1.1.1 spec)
        List<Double> lower = bbox.getLowerCorner();
        List<Double> upper = bbox.getUpperCorner();
        for (int i = 0; i < lower.size(); i++) {
            if (lower.get(i) > upper.get(i)) {
                final CoordinateSystemAxis axis = bboxCRs.getCoordinateSystem().getAxis(i);
                // see if the coordinates can be fixed
                if (bboxCRs instanceof GeographicCRS && axis.getDirection() == AxisDirection.EAST) {

                    if (gridEnvelopeBboxCRS != null) {
                        // try to guess which one needs to be fixed
                        final double envMax = gridEnvelopeBboxCRS.getMaximum(i);
                        if (envMax >= lower.get(i))
                            upper.set(i,
                                    upper.get(i)
                                            + (axis.getMaximumValue() - axis.getMinimumValue()));
                        else
                            lower.set(i,
                                    lower.get(i)
                                            - (axis.getMaximumValue() - axis.getMinimumValue()));

                    } else {
                        // just fix the upper and hope...
                        upper.set(i,
                                upper.get(i) + (axis.getMaximumValue() - axis.getMinimumValue()));
                    }
                }

                // if even after the fix we're in the wrong situation, complain
                if (lower.get(i) > upper.get(i)) {
                    throw new WcsException("illegal bbox, min of dimension " + (i + 1) + ": "
                            + lower.get(i) + " is " + "greater than max of same dimension: "
                            + upper.get(i), WcsExceptionCode.InvalidParameterValue, "BoundingBox");
                }
            }

        }
    }

    /**
     * Checks that the elements of the Output part of the request do make sense by comparing them to the coverage metadata
     *
     * @param info
     * @param rangeSubset
     */
    private void checkOutput(CoverageInfo meta, OutputType output) {
        if (output == null)
            return;

        String format = output.getFormat();
        String declaredFormat = getDeclaredFormat(meta.getSupportedFormats(), format);
        if (declaredFormat == null)
            throw new WcsException("format " + format + " is not supported for this coverage",
                    InvalidParameterValue, "format");

        final GridCrsType gridCRS = output.getGridCRS();
        if (gridCRS != null) {
            // check grid base crs is valid, and eventually default it out
            String gridBaseCrs = gridCRS.getGridBaseCRS();
            if (gridBaseCrs != null) {
                // make sure the requested is among the supported ones, by
                // making a
                // code level
                // comparison (to avoid assuming epsg:xxxx and
                // http://www.opengis.net/gml/srs/epsg.xml#xxx are different
                // ones.
                // We'll also consider the urn one comparable, allowing eventual
                // axis flip on the
                // geographic crs
                String actualCRS = null;
                final String gridBaseCrsCode = extractCode(gridBaseCrs);
                for (Iterator it = meta.getResponseSRS().iterator(); it.hasNext();) {
                    final String responseCRS = (String) it.next();
                    final String code = extractCode(responseCRS);
                    if (code.equalsIgnoreCase(gridBaseCrsCode)) {
                        actualCRS = responseCRS;
                    }
                }
                if (actualCRS == null)
                    throw new WcsException("CRS " + gridBaseCrs
                            + " is not among the supported ones for coverage " + meta.getName(),
                            WcsExceptionCode.InvalidParameterValue, "GridBaseCrs");
                gridCRS.setGridBaseCRS(gridBaseCrs);
            } else {
                String code = GML2EncodingUtils.epsgCode(meta.getCRS());
                gridCRS.setGridBaseCRS("urn:x-ogc:def:crs:EPSG:" + code);
            }

            // check grid type makes sense and apply default otherwise
            String gridTypeValue = gridCRS.getGridType();
            GridType type = GridType.GT2dGridIn2dCrs;
            if (gridTypeValue != null) {
                type = null;
                for (GridType gt : GridType.values()) {
                    if (gt.getXmlConstant().equalsIgnoreCase(gridTypeValue))
                        type = gt;
                }
                if (type == null)
                    throw new WcsException("Unknown grid type " + gridTypeValue,
                            InvalidParameterValue, "GridType");
                else if (type == GridType.GT2dGridIn3dCrs)
                    throw new WcsException("Unsupported grid type " + gridTypeValue,
                            InvalidParameterValue, "GridType");
            }
            gridCRS.setGridType(type.getXmlConstant());

            // check gridcs and apply only value we know about
            String gridCS = gridCRS.getGridCS();
            if (gridCS != null) {
                if (!gridCS.equalsIgnoreCase(GridCS.GCSGrid2dSquare.getXmlConstant()))
                    throw new WcsException("Unsupported grid cs " + gridCS, InvalidParameterValue,
                            "GridCS");
            }
            gridCRS.setGridCS(GridCS.GCSGrid2dSquare.getXmlConstant());

            // check the grid origin and set defaults
            CoordinateReferenceSystem crs = null;
            try {
                crs = CRS.decode(gridCRS.getGridBaseCRS());
            } catch (Exception e) {
                throw new WcsException("Could not understand crs " + gridCRS.getGridBaseCRS(),
                        WcsExceptionCode.InvalidParameterValue, "GridBaseCRS");
            }
            if (!gridCRS.isSetGridOrigin() || gridCRS.getGridOrigin() == null) {
                // if not set, we have a default of "0 0" as a string, since I
                // cannot
                // find a way to make it default to new Double[] {0 0} I'll fix
                // it here
                Double[] origin = new Double[type.getOriginArrayLength()];
                Arrays.fill(origin, 0.0);
                gridCRS.setGridOrigin(origin);
            } else {
                Double[] gridOrigin = (Double[]) gridCRS.getGridOrigin();
                // make sure the origin dimension matches the output crs
                // dimension
                if (gridOrigin.length != type.getOriginArrayLength())
                    throw new WcsException("Grid origin size (" + gridOrigin.length
                            + ") inconsistent with grid type " + type.getXmlConstant()
                            + " that requires (" + type.getOriginArrayLength() + ")",
                            WcsExceptionCode.InvalidParameterValue, "GridOrigin");
                gridCRS.setGridOrigin(gridOrigin);
            }

            // perform same checks on the offsets
            Double[] gridOffsets = (Double[]) gridCRS.getGridOffsets();
            if (gridOffsets != null) {
                // make sure the origin dimension matches the grid type
                if (type.getOffsetArrayLength() != gridOffsets.length)
                    throw new WcsException("Invalid offsets lenght, grid type "
                            + type.getXmlConstant() + " requires " + type.getOffsetArrayLength(),
                            InvalidParameterValue, "GridOffsets");
            } else {
                gridCRS.setGridOffsets(null);
            }
        }
    }

    /**
     * Extracts only the final part of an EPSG code allowing for a specification independent comparison (that is, it removes the EPSG:, urn:xxx:,
     * http://... prefixes)
     *
     * @param srsName
     * @return
     */
    private String extractCode(String srsName) {
        if (srsName.startsWith("http://www.opengis.net/gml/srs/epsg.xml#"))
            return srsName.substring(40);
        else if (srsName.startsWith("urn:"))
            return srsName.substring(srsName.lastIndexOf(':') + 1);
        else if (srsName.startsWith("EPSG:"))
            return srsName.substring(5);
        else
            return srsName;
    }

    /**
     * Checks if the supported format string list contains the specified format, doing a case insensitive search. If found the declared output format
     * name is returned, otherwise null is returned.
     *
     * @param supportedFormats
     * @param format
     * @return
     */
    private String getDeclaredFormat(List supportedFormats, String format) {
        // supported formats may be setup using old style formats, first scan
        // the
        // configured list
        for (Iterator it = supportedFormats.iterator(); it.hasNext();) {
            String sf = (String) it.next();
            if (sf.equalsIgnoreCase(format)) {
                return sf;
            } else {
                CoverageResponseDelegate delegate = responseFactory.encoderFor(sf);
                if (delegate != null && delegate.canProduce(format))
                    return sf;
            }
        }
        return null;
    }

    /**
     * Checks that the elements of the RangeSubset part of the request do make sense by comparing them to the coverage metadata
     *
     * @param info
     * @param rangeSubset
     */
    private void checkRangeSubset(CoverageInfo info, RangeSubsetType rangeSubset) {
        // quick escape if no range subset has been specified (it's legal)
        if (rangeSubset == null)
            return;

        if (rangeSubset.getFieldSubset().size() > 1) {
            throw new WcsException("Multi field coverages are not supported yet",
                    InvalidParameterValue, "RangeSubset");
        }

        // check field identifier
        FieldSubsetType field = (FieldSubsetType) rangeSubset.getFieldSubset().get(0);
        final String fieldId = field.getIdentifier().getValue();
        if (!fieldId.equalsIgnoreCase("contents"))
            throw new WcsException("Unknown field " + fieldId, InvalidParameterValue, "RangeSubset");

        // check interpolation
        String interpolation = field.getInterpolationType();
        if (interpolation != null) {
            boolean interpolationSupported = false;

            if (interpolation.equalsIgnoreCase("nearest")) {
                interpolation = "nearest neighbor";
            } else if (interpolation.equalsIgnoreCase("cubic") || interpolation.equalsIgnoreCase("bicubic")) {
                interpolation = "bicubic";
            } else if (interpolation.equalsIgnoreCase("linear") || interpolation.equalsIgnoreCase("bilinear")) {
                interpolation = "bilinear";
            }

            for (String method : info.getInterpolationMethods()) {
                if (interpolation.equalsIgnoreCase(method)) {
                    interpolationSupported = true;
                    break;
                }
            }

            if (!interpolationSupported)
                throw new WcsException(
                        "The requested Interpolation method is not supported by this Coverage.",
                        InvalidParameterValue, "RangeSubset");
        }

        // check axis
        if (field.getAxisSubset().size() > 1) {
            throw new WcsException("Multi axis coverages are not supported yet",
                    InvalidParameterValue, "RangeSubset");
        } else if (field.getAxisSubset().size() == 0)
            return;

        AxisSubsetType axisSubset = (AxisSubsetType) field.getAxisSubset().get(0);
        final String axisId = axisSubset.getIdentifier();
        if (!axisId.equalsIgnoreCase("Bands"))
            throw new WcsException("Unknown axis " + axisId + " in field " + fieldId,
                    InvalidParameterValue, "RangeSubset");

        // prepare a support structure to quickly get the band index of a key
        // (and remember we replaced spaces with underscores in the keys to
        // avoid issues
        // with the kvp parsing of indentifiers that include spaces)
        List<CoverageDimensionInfo> dimensions = info.getDimensions();
        Set<String> dimensionMap = new HashSet<String>();
        for (int i = 0; i < dimensions.size(); i++) {
            String keyName = dimensions.get(i).getName().replace(' ', '_');
            dimensionMap.add(keyName);
        }

        // check keys
        List keys = axisSubset.getKey();
        int[] bands = new int[keys.size()];
        for (int j = 0; j < bands.length; j++) {
            final String key = (String) keys.get(j);
            String parsedKey = null;
            for (String dimensionName : dimensionMap) {
                if (dimensionName.equalsIgnoreCase(key)) {
                    parsedKey = dimensionName;
                    break;
                }
            }
            if (parsedKey == null)
                throw new WcsException("Unknown field/axis/key combination " + fieldId + "/"
                        + axisSubset.getIdentifier() + "/" + key, InvalidParameterValue,
                        "RangeSubset");
            else
                keys.set(j, parsedKey);
        }
    }

    /**
     *
     * @param date
     * @return
     */
    private static Date cvtToGmt(Date date) {
        TimeZone tz = TimeZone.getDefault();
        Date ret = new Date(date.getTime() - tz.getRawOffset());

        // if we are now in DST, back off by the delta. Note that we are checking the GMT date, this is the KEY.
        if (tz.inDaylightTime(ret)) {
            Date dstDate = new Date(ret.getTime() - tz.getDSTSavings());

            // check to make sure we have not crossed back into standard time
            // this happens when we are on the cusp of DST (7pm the day before the change for PDT)
            if (tz.inDaylightTime(dstDate)) {
                ret = dstDate;
            }
        }

        return ret;
    }

}
TOP

Related Classes of org.geoserver.wcs.DefaultWebCoverageService111

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.