Package org.geoserver.wcs2_0.eo.response

Source Code of org.geoserver.wcs2_0.eo.response.DescribeEOCoverageSetTransformer

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

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.opengis.wcs20.DescribeEOCoverageSetType;
import net.opengis.wcs20.DimensionTrimType;
import net.opengis.wcs20.Section;

import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.DimensionInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.ows.util.ResponseUtils;
import org.geoserver.platform.OWS20Exception.OWSExceptionCode;
import org.geoserver.wcs.WCSInfo;
import org.geoserver.wcs2_0.GetCoverage;
import org.geoserver.wcs2_0.eo.EOCoverageResourceCodec;
import org.geoserver.wcs2_0.eo.WCSEOMetadata;
import org.geoserver.wcs2_0.exception.WCS20Exception;
import org.geoserver.wcs2_0.response.WCS20DescribeCoverageTransformer;
import org.geoserver.wcs2_0.response.WCS20DescribeCoverageTransformer.WCS20DescribeCoverageTranslator;
import org.geoserver.wcs2_0.response.WCSDimensionsHelper;
import org.geoserver.wcs2_0.util.EnvelopeAxesLabelsMapper;
import org.geotools.coverage.grid.io.DimensionDescriptor;
import org.geotools.coverage.grid.io.GranuleSource;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader;
import org.geotools.data.DataUtilities;
import org.geotools.data.Query;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.store.MaxFeaturesFeatureCollection;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.GeoTools;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.CRS.AxisOrder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.util.DateRange;
import org.geotools.util.NumberRange;
import org.geotools.util.logging.Logging;
import org.geotools.xml.impl.DatatypeConverterImpl;
import org.geotools.xml.transform.TransformerBase;
import org.geotools.xml.transform.Translator;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.helpers.AttributesImpl;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Polygon;

/**
* Encodes a DescribeEOCoverageSet response
*
* @author Andrea Aime - GeoSolutions
*/
public class DescribeEOCoverageSetTransformer extends TransformerBase {

    static Logger LOGGER = Logging.getLogger(WCS20DescribeEOCoverageSetTranslator.class);

    EOCoverageResourceCodec codec;

    WCS20DescribeCoverageTransformer dcTransformer;

    EnvelopeAxesLabelsMapper envelopeAxisMapper;

    WCSInfo wcs;

    public DescribeEOCoverageSetTransformer(WCSInfo wcs, EOCoverageResourceCodec codec,
            EnvelopeAxesLabelsMapper envelopeAxesMapper,
            WCS20DescribeCoverageTransformer dcTransformer) {
        this.wcs = wcs;
        this.codec = codec;
        this.envelopeAxisMapper = envelopeAxesMapper;
        this.dcTransformer = dcTransformer;
        setIndentation(2);
    }

    @Override
    public Translator createTranslator(ContentHandler handler) {
        WCS20DescribeCoverageTranslator dcTranslator = dcTransformer.createTranslator(handler);
        return new WCS20DescribeEOCoverageSetTranslator(handler, dcTranslator);
    }

    public class WCS20DescribeEOCoverageSetTranslator extends TranslatorSupport {

        private WCS20DescribeCoverageTranslator dcTranslator;

        public WCS20DescribeEOCoverageSetTranslator(ContentHandler handler,
                WCS20DescribeCoverageTranslator dcTranslator) {
            super(handler, null, null);
            this.dcTranslator = dcTranslator;
        }

        /**
         * Encode the object.
         */
        @Override
        public void encode(Object o) throws IllegalArgumentException {
            DescribeEOCoverageSetType dcs = (DescribeEOCoverageSetType) o;

            List<CoverageInfo> coverages = getCoverages(dcs);
            List<CoverageGranules> coverageGranules = getCoverageGranules(dcs, coverages);
            int granuleCount = getGranuleCount(coverageGranules);
            Integer maxCoverages = getMaxCoverages(dcs);
            int returned;
            if(maxCoverages != null) {
                returned = granuleCount < maxCoverages ? granuleCount : maxCoverages;
            } else {
                returned = granuleCount;
            }

            String eoSchemaLocation = ResponseUtils.buildSchemaURL(dcs.getBaseUrl(),
                    "wcseo/1.0/wcsEOAll.xsd");
            Attributes atts = atts(
                    "xmlns:eop",
                    "http://www.opengis.net/eop/2.0", //
                    "xmlns:gml",
                    "http://www.opengis.net/gml/3.2", //
                    "xmlns:wcsgs",
                    "http://www.geoserver.org/wcsgs/2.0", //
                    "xmlns:gmlcov", "http://www.opengis.net/gmlcov/1.0", "xmlns:om",
                    "http://www.opengis.net/om/2.0", "xmlns:swe", "http://www.opengis.net/swe/2.0",
                    "xmlns:wcs", "http://www.opengis.net/wcs/2.0", "xmlns:wcseo",
                    "http://www.opengis.net/wcseo/1.0", "xmlns:xlink",
                    "http://www.w3.org/1999/xlink", "xmlns:xsi",
                    "http://www.w3.org/2001/XMLSchema-instance", "numberMatched",
                    String.valueOf(granuleCount), "numberReturned", String.valueOf(returned),
                    "xsi:schemaLocation", "http://www.opengis.net/wcseo/1.0 " + eoSchemaLocation);

            start("wcseo:EOCoverageSetDescription", atts);

            if(!coverageGranules.isEmpty()) {
                List<CoverageGranules> reducedGranules = coverageGranules;
                if(maxCoverages != null) {
                    reducedGranules = applyMaxCoverages(coverageGranules, maxCoverages);
                }

                boolean allSections = dcs.getSections() == null
                        || dcs.getSections().getSection() == null
                        || dcs.getSections().getSection().contains(Section.ALL);
                if (allSections
                        || dcs.getSections().getSection().contains(Section.COVERAGEDESCRIPTIONS)) {
                    handleCoverageDescriptions(reducedGranules);
                }
                if (allSections
                        || dcs.getSections().getSection().contains(Section.DATASETSERIESDESCRIPTIONS)) {
                    handleDatasetSeriesDescriptions(coverageGranules);
                }
            }

            end("wcseo:EOCoverageSetDescription");
        }

        /**
         * Returns the max number of coverages to return, if any (null otherwise)
         *
         * @param dcs
         * @return
         */
        private Integer getMaxCoverages(DescribeEOCoverageSetType dcs) {
            if (dcs.getCount() > 0) {
                return dcs.getCount();
            }

            // fall back on the the default value, it's ok if it's null
            return wcs.getMetadata().get(WCSEOMetadata.COUNT_DEFAULT.key, Integer.class);
        }

        private List<CoverageGranules> applyMaxCoverages(List<CoverageGranules> coverageGranules,
                Integer maxCoverages) {
            List<CoverageGranules> result = new ArrayList<DescribeEOCoverageSetTransformer.CoverageGranules>();
            for (CoverageGranules cg : coverageGranules) {
                int size = cg.granules.size();
                if (size > maxCoverages) {
                    cg.granules = DataUtilities
                            .simple(new MaxFeaturesFeatureCollection<SimpleFeatureType, SimpleFeature>(
                                    cg.granules, maxCoverages));
                }
                result.add(cg);
                maxCoverages -= size;
                if (maxCoverages <= 0) {
                    break;
                }
            }

            return result;
        }

        private void handleDatasetSeriesDescriptions(List<CoverageGranules> coverages) {
            start("wcseo:DatasetSeriesDescriptions");
            for (CoverageGranules gc : coverages) {
                CoverageInfo ci = gc.coverage;
                String datasetId = codec.getDatasetName(ci);
                start("wcseo:DatasetSeriesDescription", atts("gml:id", datasetId));

                try {
                    GridCoverage2DReader reader = (GridCoverage2DReader) ci.getGridCoverageReader(
                            null, null);

                    // encode the bbox
                    encodeDatasetBounds(ci, reader);

                    // the dataset series id
                    element("wcseo:DatasetSeriesId", datasetId);

                    // encode the time
                    DimensionInfo time = ci.getMetadata().get(ResourceInfo.TIME, DimensionInfo.class);
                    WCSDimensionsHelper timeHelper = new WCSDimensionsHelper(time, reader, datasetId);
                    dcTranslator.encodeTimePeriod(timeHelper.getBeginTime(), timeHelper.getEndTime(), datasetId + "_timeperiod", null, null);

                    end("wcseo:DatasetSeriesDescription");
                } catch (IOException e) {
                    throw new WCS20Exception("Failed to build the description for dataset series "
                            + codec.getDatasetName(ci), e);
                }
            }
            end("wcseo:DatasetSeriesDescriptions");
        }

        private void encodeDatasetBounds(CoverageInfo ci, GridCoverage2DReader reader)
                throws IOException {
            // get the crs and look for an EPSG code
            final CoordinateReferenceSystem crs = ci.getCRS();
            GeneralEnvelope envelope = reader.getOriginalEnvelope();
            List<String> axesNames = envelopeAxisMapper.getAxesNames(envelope, 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).equals(AxisOrder.EAST_NORTH);

            final StringBuilder builder = new StringBuilder();
            for (String axisName : axesNames) {
                builder.append(axisName).append(" ");
            }
            String axesLabel = builder.substring(0, builder.length() - 1);
            dcTranslator.handleBoundedBy(envelope, axisSwap, srsName, axesLabel, null);
        }

        private void handleCoverageDescriptions(List<CoverageGranules> coverageGranules) {
            start("wcs:CoverageDescriptions");
            for (CoverageGranules cg : coverageGranules) {
                SimpleFeatureIterator features = cg.granules.features();
                try {
                    while (features.hasNext()) {
                        SimpleFeature f = features.next();
                        String granuleId = codec.getGranuleId(cg.coverage, f.getID());
                        dcTranslator.handleCoverageDescription(granuleId, new GranuleCoverageInfo(
                                cg.coverage, f, cg.dimensionDescriptors));
                    }
                } finally {
                    if (features != null) {
                        features.close();
                    }
                }
            }
            end("wcs:CoverageDescriptions");
        }

        private int getGranuleCount(List<CoverageGranules> granules) {
            int sum = 0;
            for (CoverageGranules cg : granules) {
                sum += cg.granules.size();
            }

            return sum;
        }

        private List<CoverageInfo> getCoverages(DescribeEOCoverageSetType dcs) {
            List<CoverageInfo> results = new ArrayList<CoverageInfo>();
            for (String id : dcs.getEoId()) {
                CoverageInfo ci = codec.getDatasetCoverage(id);
                if (ci == null) {
                    throw new IllegalArgumentException(
                            "The dataset id is invalid, should have been checked earlier?");
                }
                results.add(ci);
            }

            return results;
        }

        private List<CoverageGranules> getCoverageGranules(DescribeEOCoverageSetType dcs,
                List<CoverageInfo> coverages) {
            List<CoverageGranules> results = new ArrayList<CoverageGranules>();
            for (CoverageInfo ci : coverages) {
                GranuleSource source = null;
                try {
                    StructuredGridCoverage2DReader reader = (StructuredGridCoverage2DReader) ci
                            .getGridCoverageReader(null, GeoTools.getDefaultHints());
                    String name = codec.getCoverageName(ci);
                    source = reader.getGranules(name, true);

                    Query q = buildQueryFromDimensionTrims(dcs, reader, name);
                    SimpleFeatureCollection collection = source.getGranules(q);

                    // only report in output coverages that have at least one matched granule
                    if(!collection.isEmpty()) {
                        List<DimensionDescriptor> descriptors = getActiveDimensionDescriptor(ci, reader, name);
                        CoverageGranules granules = new CoverageGranules(ci, name, reader, collection, descriptors);
                        results.add(granules);
                    }
                } catch (IOException e) {
                    throw new WCS20Exception("Failed to load the coverage granules for covearge "
                            + ci.prefixedName(), e);
                } finally {
                    try {
                        if (source != null) {
                            source.dispose();
                        }
                    } catch (IOException e) {
                        LOGGER.log(Level.FINE, "Failed to dispose granule source", e);
                    }
                }
            }
            return results;
        }

        private List<DimensionDescriptor> getActiveDimensionDescriptor(CoverageInfo ci,
                StructuredGridCoverage2DReader reader, String name) throws IOException {
            // map the source descriptors for easy retrieval
            Map<String, DimensionDescriptor> sourceDescriptors = new HashMap<String, DimensionDescriptor>();
            for (DimensionDescriptor dimensionDescriptor : reader.getDimensionDescriptors(name)) {
                sourceDescriptors.put(dimensionDescriptor.getName().toUpperCase(), dimensionDescriptor);
            }
            // select only those that have been activated vai the GeoServer GUI
            List<DimensionDescriptor> enabledDescriptors = new ArrayList<DimensionDescriptor>();
            for(Entry<String, Serializable> entry : ci.getMetadata().entrySet()) {
                if(entry.getValue() instanceof DimensionInfo) {
                    DimensionInfo di = (DimensionInfo) entry.getValue();
                    if(di.isEnabled()) {
                        String dimensionName = entry.getKey();
                        if(dimensionName.startsWith(ResourceInfo.CUSTOM_DIMENSION_PREFIX)) {
                            dimensionName = dimensionName.substring(ResourceInfo.CUSTOM_DIMENSION_PREFIX.length());
                        }
                        DimensionDescriptor selected = sourceDescriptors.get(dimensionName.toUpperCase());
                        if(selected != null) {
                            enabledDescriptors.add(selected);
                        }
                    }
                }
            }
           
            return enabledDescriptors;
        }

        private Query buildQueryFromDimensionTrims(DescribeEOCoverageSetType dcs,
                StructuredGridCoverage2DReader reader, String coverageName) throws IOException {
            // no selection, get all
            if (dcs.getDimensionTrim() == null || dcs.getDimensionTrim().size() == 0) {
                return Query.ALL;
            }

            // find out what the selection is about
            DateRange timeRange = null;
            NumberRange<Double> lonRange = null;
            NumberRange<Double> latRange = null;
            for (DimensionTrimType trim : dcs.getDimensionTrim()) {
                String name = trim.getDimension();
                if ("Long".equals(name)) {
                    if (lonRange != null) {
                        throw new WCS20Exception("Long trim specified more than once",
                                OWSExceptionCode.InvalidParameterValue, "subset");
                    }
                    lonRange = parseNumberRange(trim);
                } else if ("Lat".equals(name)) {
                    if (latRange != null) {
                        throw new WCS20Exception("Lat trim specified more than once",
                                OWSExceptionCode.InvalidParameterValue, "subset");
                    }
                    latRange = parseNumberRange(trim);
                } else if ("phenomenonTime".equals(name)) {
                    if (timeRange != null) {
                        throw new WCS20Exception("phenomenonTime trim specified more than once",
                                OWSExceptionCode.InvalidParameterValue, "subset");
                    }
                    timeRange = parseDateRange(trim);
                } else {
                    throw new WCS20Exception("Invalid dimension name " + name
                            + ", the only valid values "
                            + "by WCS EO spec are Long, Lat and phenomenonTime",
                            OWSExceptionCode.InvalidParameterValue, "subset");
                }
            }

            // check the desired containment
            boolean overlaps;
            String containment = dcs.getContainmentType();
            if (containment == null || "overlaps".equals(containment)) {
                overlaps = true;
            } else if ("contains".equals(containment)) {
                overlaps = false;
            } else {
                throw new WCS20Exception("Invalid containment value " + containment
                        + ", the only valid values by WCS EO spec are contains and overlaps",
                        OWSExceptionCode.InvalidParameterValue, "containment");
            }

            // spatial subset
            FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
            Filter filter = null;
            if (lonRange != null || latRange != null) {
                try {
                    // we are going to intersect the trims with the original envelope
                    // since the trims can only be expressed in wgs84 we need to reproject back and forth
                    ReferencedEnvelope original = new ReferencedEnvelope(reader.getOriginalEnvelope())
                            .transform(DefaultGeographicCRS.WGS84, true);
                    if (lonRange != null) {
                        ReferencedEnvelope lonTrim = new ReferencedEnvelope(lonRange.getMinimum(),
                                lonRange.getMaximum(), -90, 90, DefaultGeographicCRS.WGS84);
                        original = new ReferencedEnvelope(original.intersection(lonTrim), DefaultGeographicCRS.WGS84);
                    }
                    if (latRange != null) {
                        ReferencedEnvelope latTrim = new ReferencedEnvelope(-180, 180,
                                latRange.getMinimum(), latRange.getMaximum(),
                                DefaultGeographicCRS.WGS84);
                        original = new ReferencedEnvelope(original.intersection(latTrim), DefaultGeographicCRS.WGS84);
                    }
                    if (original.isEmpty()) {
                        filter = Filter.EXCLUDE;
                    } else {
                        Polygon llPolygon = JTS.toGeometry(original);
                        GeometryDescriptor geom = reader.getGranules(coverageName, true).getSchema().getGeometryDescriptor();
                        PropertyName geometryProperty = ff.property(geom.getLocalName());
                        Geometry nativeCRSPolygon = JTS.transform(llPolygon, CRS.findMathTransform(DefaultGeographicCRS.WGS84, reader.getCoordinateReferenceSystem()));
                        Literal polygonLiteral = ff.literal(nativeCRSPolygon);
                        if(overlaps) {
                            filter = ff.intersects(geometryProperty, polygonLiteral);
                        } else {
                            filter = ff.within(geometryProperty, polygonLiteral);
                        }
                    }
                } catch(Exception e) {
                    throw new WCS20Exception("Failed to translate the spatial trim into a native filter", e);
                }
            }

            // temporal subset
            if (timeRange != null && filter != Filter.EXCLUDE) {
                DimensionDescriptor timeDescriptor = WCSDimensionsHelper.getDimensionDescriptor(reader, coverageName, "TIME");
                String start = timeDescriptor.getStartAttribute();
                String end = timeDescriptor.getEndAttribute();
               
                Filter timeFilter;
                if(end == null) {
                    // single value time
                    timeFilter = ff.between(ff.property(start), ff.literal(timeRange.getMinValue()), ff.literal(timeRange.getMaxValue()));
                } else {
                    // range value, we need to account for containment then
                    if(overlaps) {
                        Filter f1 = ff.lessOrEqual(ff.property(start), ff.literal(timeRange.getMaxValue()));
                        Filter f2 = ff.greaterOrEqual(ff.property(end), ff.literal(timeRange.getMinValue()));
                        timeFilter = ff.and(Arrays.asList(f1, f2));
                    } else {
                        Filter f1 = ff.greaterOrEqual(ff.property(start), ff.literal(timeRange.getMinValue()));
                        Filter f2 = ff.lessOrEqual(ff.property(end), ff.literal(timeRange.getMaxValue()));
                        timeFilter = ff.and(Arrays.asList(f1, f2));
                    }
                }
               
                if(filter == null) {
                    filter = timeFilter;
                } else {
                    filter = ff.and(filter, timeFilter);
                }
            }

            return new Query(null, filter);
        }

        private DateRange parseDateRange(DimensionTrimType trim) {
            DatatypeConverterImpl xmlTimeConverter = DatatypeConverterImpl.getInstance();
            try {
                final Date low = xmlTimeConverter.parseDateTime(trim.getTrimLow()).getTime();
                final Date high = xmlTimeConverter.parseDateTime(trim.getTrimHigh()).getTime();

                // low > high???
                if (low.compareTo(high) > 0) {
                    throw new WCS20Exception("Low greater than High in trim for dimension: "
                            + trim.getDimension(),
                            WCS20Exception.WCS20ExceptionCode.InvalidSubsetting, "dimensionTrim");
                }

                return new DateRange(low, high);
            } catch (IllegalArgumentException e) {
                throw new WCS20Exception("Invalid date value",
                        OWSExceptionCode.InvalidParameterValue, "dimensionTrim", e);
            }

        }

        private NumberRange<Double> parseNumberRange(DimensionTrimType trim) {
            try {
                double low = Double.parseDouble(trim.getTrimLow());
                double high = Double.parseDouble(trim.getTrimHigh());

                // low > high???
                if (low > high) {
                    throw new WCS20Exception("Low greater than High in trim for dimension: "
                            + trim.getDimension(),
                            WCS20Exception.WCS20ExceptionCode.InvalidSubsetting, "dimensionTrim");
                }

                return new NumberRange<Double>(Double.class, low, high);
            } catch (NumberFormatException e) {
                throw new WCS20Exception("Invalid numeric value",
                        OWSExceptionCode.InvalidParameterValue, "dimensionTrim", e);
            }
        }

        Attributes atts(String... atts) {
            AttributesImpl attributes = new AttributesImpl();
            for (int i = 0; i < atts.length; i += 2) {
                attributes.addAttribute(null, atts[i], atts[i], null, atts[i + 1]);
            }
            return attributes;
        }
    }

    static class CoverageGranules {
        CoverageInfo coverage;

        StructuredGridCoverage2DReader reader;

        SimpleFeatureCollection granules;

        private String name;

        private List<DimensionDescriptor> dimensionDescriptors;

        public CoverageGranules(CoverageInfo coverage, String name, StructuredGridCoverage2DReader reader,
                SimpleFeatureCollection granules, List<DimensionDescriptor> dimensionDescriptors) {
            this.coverage = coverage;
            this.name = name;
            this.reader = reader;
            this.granules = granules;
            this.dimensionDescriptors = dimensionDescriptors;
        }

    }

}
TOP

Related Classes of org.geoserver.wcs2_0.eo.response.DescribeEOCoverageSetTransformer

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.