Package org.geoserver.wps.gs.download

Source Code of org.geoserver.wps.gs.download.VectorDownload

/* (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.wps.gs.download;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.IOUtils;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.wps.ppio.ComplexPPIO;
import org.geoserver.wps.ppio.ProcessParameterIO;
import org.geoserver.wps.resource.WPSResourceManager;
import org.geotools.data.Parameter;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.store.ReprojectingFeatureCollection;
import org.geotools.factory.GeoTools;
import org.geotools.filter.visitor.SimplifyingFilterVisitor;
import org.geotools.process.ProcessException;
import org.geotools.process.vector.ClipProcess;
import org.geotools.referencing.CRS;
import org.geotools.resources.coverage.FeatureUtilities;
import org.geotools.util.logging.Logging;
import org.opengis.filter.Filter;
import org.opengis.filter.spatial.Intersects;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.util.ProgressListener;

import com.vividsolutions.jts.geom.Geometry;

/**
* The class that does the real work of checking if we are exceeeding the download limits for vector data. Also this class writes the features in the
* output file.
*
* @author Simone Giannecchini, GeoSolutions SAS
*
*/
class VectorDownload {

    private static final Logger LOGGER = Logging.getLogger(VectorDownload.class);

    /** The {@link DownloadServiceConfiguration} object containing the configured limits. */
    private DownloadServiceConfiguration limits;

    /** The resource manager for handling the used resources. */
    private WPSResourceManager resourceManager;

    /**
     * Constructor, takes a {@link DownloadServiceConfiguration} and a {@link WPSResourceManager}.
     *
     * @param limits the {@link DownloadServiceConfiguration} to check for not exceeding the download limits.
     * @param resourceManager the {@link WPSResourceManager} to handle generated resources
     */
    public VectorDownload(DownloadServiceConfiguration limits, WPSResourceManager resourceManager) {
        this.limits = limits;
        this.resourceManager = resourceManager;
    }

    /**
     * Extract vector data to a file, given the provided mime-type. This method does the following operations:
     * <ul>
     * <li>Reads and filter the features (if needed)</li>
     * <li>Reprojects the features (if needed)</li>
     * <li>Clips the features (if needed)</li>
     * <li>Writes the result</li>
     * <li>Cleanup the generated coverages</li>
     * </ul>
     *
     *
     * @param resourceInfo the {@link FeatureTypeInfo} to download from
     * @param mimeType the mme-type for the requested output format
     * @param roi the {@link Geometry} for the clip/intersection
     * @param clip whether or not to clip the resulting data (useless for the moment)
     * @param filter the {@link Filter} to load the data
     * @param targetCRS the reproject {@link CoordinateReferenceSystem}
     * @param progressListener
     * @return a file, given the provided mime-type.
     * @throws Exception
     */
    public File execute(FeatureTypeInfo resourceInfo, String mimeType, Geometry roi, boolean clip,
            Filter filter, CoordinateReferenceSystem targetCRS,
            final ProgressListener progressListener) throws Exception {

        // prepare native CRS
        CoordinateReferenceSystem nativeCRS = DownloadUtilities.getNativeCRS(resourceInfo);
        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.fine("Native CRS is " + nativeCRS.toWKT());
        }

        //
        // STEP 0 - Push ROI back to native CRS (if ROI is provided)
        //
        ROIManager roiManager = null;
        if (roi != null) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Pushing ROI to native CRS");
            }
            CoordinateReferenceSystem roiCRS = (CoordinateReferenceSystem) roi.getUserData();
            roiManager = new ROIManager(roi, roiCRS);
            // set use nativeCRS
            roiManager.useNativeCRS(nativeCRS);
        }

        //
        // STEP 1 - Read and Filter
        //

        // access feature source and collection of features
        final SimpleFeatureSource featureSource = (SimpleFeatureSource) resourceInfo
                .getFeatureSource(null, GeoTools.getDefaultHints());

        // basic filter preparation
        Filter ra = Filter.INCLUDE;
        if (filter != null) {
            ra = filter;
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Using filter " + ra);
            }
        }
        // and with the ROI if we have one
        SimpleFeatureCollection originalFeatures;
        final boolean hasROI = roiManager != null;
        if (hasROI) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Adding Geometry filter with ROI");
            }
            final String dataGeomName = featureSource.getSchema().getGeometryDescriptor()
                    .getLocalName();
            final Intersects intersectionFilter = FeatureUtilities.DEFAULT_FILTER_FACTORY
                    .intersects(FeatureUtilities.DEFAULT_FILTER_FACTORY.property(dataGeomName),
                            FeatureUtilities.DEFAULT_FILTER_FACTORY.literal(roiManager
                                    .getSafeRoiInNativeCRS()));
            ra = FeatureUtilities.DEFAULT_FILTER_FACTORY.and(ra, intersectionFilter);
        }

        // simplify filter
        ra = (Filter) ra.accept(new SimplifyingFilterVisitor(), null);
        // read
        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Reading the filtered features");
        }
        originalFeatures = featureSource.getFeatures(ra);
        DownloadUtilities.checkIsEmptyFeatureCollection(originalFeatures);

        //
        // STEP 2 - Reproject feature collection
        //
        // do we need to reproject?
        SimpleFeatureCollection reprojectedFeatures;
        if (targetCRS != null && !CRS.equalsIgnoreMetadata(nativeCRS, targetCRS)) {
            roiManager.useTargetCRS(targetCRS);
            // testing reprojection...
            final MathTransform targetTX = CRS.findMathTransform(nativeCRS, targetCRS, true);
            if (!targetTX.isIdentity()) {
                // avoid doing the transform if this is the identity
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.log(Level.FINE, "Reprojecting features");
                }
                reprojectedFeatures = new ReprojectingFeatureCollection(originalFeatures, targetCRS);
            } else {
                reprojectedFeatures = originalFeatures;
                DownloadUtilities.checkIsEmptyFeatureCollection(reprojectedFeatures);
            }
        } else {
            reprojectedFeatures = originalFeatures;
            if (hasROI) {
                roiManager.useTargetCRS(nativeCRS);
            }
        }

        //
        // STEP 3 - Clip in targetCRS
        //
        SimpleFeatureCollection clippedFeatures;
        if (clip && roi != null) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Clipping features");
            }
            final ClipProcess clipProcess = new ClipProcess();// TODO avoid unnecessary creation
            clippedFeatures = clipProcess.execute(reprojectedFeatures,
                    roiManager.getSafeRoiInTargetCRS(), true);

            // checks
            DownloadUtilities.checkIsEmptyFeatureCollection(clippedFeatures);
        } else {
            clippedFeatures = reprojectedFeatures;
        }

        //
        // STEP 4 - Write down respecting limits in bytes
        //
        // writing the output, making sure it is a zip
        return writeVectorOutput(clippedFeatures, resourceInfo.getName(), mimeType);

    }

    /**
     * Write vector output with the provided PPIO. It returns the {@link File} it writes to.
     *
     * @param features {@link SimpleFeatureCollection} containing the features to write
     * @param name name of the feature source
     * @param mimeType mimetype of the result
     * @return a {@link File} containing the written features
     * @throws Exception
     */
    private File writeVectorOutput(final SimpleFeatureCollection features, final String name,
            final String mimeType) throws Exception {

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Writing features");
        }
        // Search a proper PPIO
        ProcessParameterIO ppio_ = DownloadUtilities.find(new Parameter<SimpleFeatureCollection>(
                "fakeParam", SimpleFeatureCollection.class), null, mimeType, false);
        if (ppio_ == null) {
            throw new ProcessException("Don't know how to encode in mime type " + mimeType);
        } else if (!(ppio_ instanceof ComplexPPIO)) {
            throw new ProcessException("Invalid PPIO found " + ppio_.getIdentifer());
        }

        // limits
        long limit = DownloadServiceConfiguration.NO_LIMIT;
        if (limits.getHardOutputLimit() > 0) {
            limit = limits.getHardOutputLimit();
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Hard output limits set to " + limit);
            }
        } else {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Hard output limit unset");
            }
        }

        //
        // Get fileName
        //
        String extension = "";
        if (ppio_ instanceof ComplexPPIO) {
            extension = "." + ((ComplexPPIO) ppio_).getFileExtension();
        }

        // create output file
        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Writing file in a temporary folder");
        }
        final File output = resourceManager.getTemporaryFile(extension);

        // write checking limits
        OutputStream os = null;
        try {

            // If limits are configured we must create an OutputStream that checks limits
            final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
                    new FileOutputStream(output));
            if (limit > DownloadServiceConfiguration.NO_LIMIT) {
                os = new LimitedOutputStream(bufferedOutputStream, limit) {

                    @Override
                    protected void raiseError(long pSizeMax, long pCount) throws IOException {
                        IOException ioe = new IOException(
                                "Download Exceeded the maximum HARD allowed size!");
                        throw ioe;
                    }

                };

            } else {
                os = bufferedOutputStream;
            }

            // write with PPIO
            if (ppio_ instanceof ComplexPPIO) {
                ((ComplexPPIO) ppio_).encode(features, os);
            }
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Flushing stream");
            }
            os.flush();
        } finally {
            if (os != null) {
                IOUtils.closeQuietly(os);
            }
        }

        // return
        return output;

    }
}
TOP

Related Classes of org.geoserver.wps.gs.download.VectorDownload

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.