Skip to content

Commit

Permalink
[GEOS-7214] Add scaling support to WPS gs:Download process
Browse files Browse the repository at this point in the history
  • Loading branch information
ridethepenguin committed Sep 25, 2015
1 parent d43ea65 commit 22bc2a3
Show file tree
Hide file tree
Showing 7 changed files with 1,166 additions and 43 deletions.
@@ -1,4 +1,4 @@
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
/* (c) 2014 - 2015 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.
Expand Down Expand Up @@ -66,6 +66,8 @@ public DownloadEstimatorProcess(
* @param roiCRS the roi crs
* @param roi the roi
* @param clip the crop to geometry
* @param targetSizeX the size of the target image along the X axis
* @param targetSizeY the size of the target image along the Y axis
* @param progressListener the progress listener
* @return the boolean
* @throws Exception
Expand All @@ -78,6 +80,8 @@ public Boolean execute(
@DescribeParameter(name = "RoiCRS", min = 0, description = "Region Of Interest CRS") CoordinateReferenceSystem roiCRS,
@DescribeParameter(name = "ROI", min = 0, description = "Region Of Interest") Geometry roi,
@DescribeParameter(name = "cropToROI", min = 0, description = "Crop to ROI") Boolean clip,
@DescribeParameter(name = "targetSizeX", min = 0, minValue = 1, description = "X Size of the Target Image (applies to raster data only)") Integer targetSizeX,
@DescribeParameter(name = "targetSizeY", min = 0, minValue = 1, description = "Y Size of the Target Image (applies to raster data only)") Integer targetSizeY,
ProgressListener progressListener) throws Exception {

//
Expand Down Expand Up @@ -153,7 +157,7 @@ public Boolean execute(
}
final CoverageInfo coverage = (CoverageInfo) resourceInfo;
return new RasterEstimator(limits).execute(progressListener, coverage, roi, targetCRS,
clip, filter);
clip, filter, targetSizeX, targetSizeY);
}

if (LOGGER.isLoggable(Level.FINE)) {
Expand Down
@@ -1,4 +1,4 @@
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
/* (c) 2014 - 2015 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.
Expand All @@ -12,6 +12,9 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.jai.Interpolation;
import javax.media.jai.JAI;

import org.apache.commons.io.IOUtils;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CoverageInfo;
Expand All @@ -27,6 +30,7 @@
import org.geotools.process.factory.DescribeProcess;
import org.geotools.process.factory.DescribeResult;
import org.geotools.process.gs.GSProcess;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.util.Utilities;
import org.geotools.util.logging.Logging;
import org.opengis.filter.Filter;
Expand Down Expand Up @@ -92,6 +96,9 @@ public DownloadProcess(GeoServer geoServer, DownloadEstimatorProcess estimator,
* @param roiCRS the roi crs
* @param roi the roi
* @param clip the crop to geometry
* @param interpolation interpolation method to use when reprojecting / scaling
* @param targetSizeX the size of the target image along the X axis
* @param targetSizeY the size of the target image along the Y axis
* @param progressListener the progress listener
* @return the file
* @throws ProcessException the process exception
Expand All @@ -105,6 +112,9 @@ public File execute(
@DescribeParameter(name = "RoiCRS", min = 0, description = "Optional Region Of Interest CRS") CoordinateReferenceSystem roiCRS,
@DescribeParameter(name = "ROI", min = 0, description = "Optional Region Of Interest (Polygon)") Geometry roi,
@DescribeParameter(name = "cropToROI", min = 0, description = "Crop to ROI") Boolean clip,
@DescribeParameter(name = "interpolation", description = "Interpolation function to use when reprojecting / scaling raster data. Values are NEAREST (default), BILINEAR, BICUBIC2, BICUBIC", min = 0) Interpolation interpolation,
@DescribeParameter(name = "targetSizeX", min = 0, minValue = 1, description = "X Size of the Target Image (applies to raster data only)") Integer targetSizeX,
@DescribeParameter(name = "targetSizeY", min = 0, minValue = 1, description = "Y Size of the Target Image (applies to raster data only)") Integer targetSizeY,
final ProgressListener progressListener) throws ProcessException {

try {
Expand Down Expand Up @@ -138,14 +148,24 @@ public File execute(
roi.setUserData(roiCRS);
}

// set default interpolation value
if (interpolation == null) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE,
"Interpolation parameter not specified, using default (Nearest Neighbor)");
}
interpolation = (Interpolation) ImageUtilities.NN_INTERPOLATION_HINT
.get(JAI.KEY_INTERPOLATION);
}

//
// do we respect limits?
//
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Running the estimator");
}
if (!estimator.execute(layerName, filter, targetCRS, roiCRS, roi, clip,
progressListener)) {
if (!estimator.execute(layerName, filter, targetCRS, roiCRS, roi, clip, targetSizeX,
targetSizeY, progressListener)) {
throw new IllegalArgumentException("Download Limits Exceeded. Unable to proceed!");
}

Expand Down Expand Up @@ -201,9 +221,9 @@ public File execute(
//
CoverageInfo cInfo = (CoverageInfo) resourceInfo;
// convert/reproject/crop if needed the coverage
internalOutput = new RasterDownload(limits, resourceManager, context)
.execute(mimeType,
progressListener, cInfo, roi, targetCRS, clip, filter);
internalOutput = new RasterDownload(limits, resourceManager, context).execute(
mimeType, progressListener, cInfo, roi, targetCRS, clip, filter,
interpolation, targetSizeX, targetSizeY);
} else {

// wrong type
Expand Down
@@ -1,17 +1,21 @@
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
/* (c) 2014 - 2015 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 it.geosolutions.imageio.stream.output.FileImageOutputStreamExtImpl;
import it.geosolutions.io.output.adapter.OutputStreamAdapter;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.stream.ImageOutputStream;
import javax.media.jai.Interpolation;

import org.geoserver.catalog.CoverageInfo;
import org.geoserver.data.util.CoverageUtils;
Expand All @@ -25,26 +29,26 @@
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.coverage.processing.Operations;
import org.geotools.data.Parameter;
import org.geotools.factory.GeoTools;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.process.ProcessException;
import org.geotools.process.raster.CropCoverage;
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.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.util.ProgressListener;
import org.springframework.context.ApplicationContext;

import com.vividsolutions.jts.geom.Geometry;

import it.geosolutions.imageio.stream.output.FileImageOutputStreamExtImpl;
import it.geosolutions.io.output.adapter.OutputStreamAdapter;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;

/**
* Implements the download services for raster data. If limits are configured this class will use {@link LimitedImageOutputStream}, which raises an
Expand Down Expand Up @@ -88,6 +92,7 @@ public RasterDownload(DownloadServiceConfiguration limits, WPSResourceManager re
* <ul>
* <li>Reprojection of the coverage (if needed)</li>
* <li>Clips the coverage (if needed)</li>
* <li>Scales the coverage to match the target size (if needed)</li>
* <li>Writes the result</li>
* <li>Cleanup the generated coverages</li>
* </ul>
Expand All @@ -98,15 +103,19 @@ public RasterDownload(DownloadServiceConfiguration limits, WPSResourceManager re
* @param roi input ROI object
* @param targetCRS CRS of the file to write
* @param clip indicates if the clipping geometry must be exactly that of the ROI or simply its envelope
* @param interpolation interpolation method to use when reprojecting / scaling
* @param targetSizeX the size of the target image along the X axis
* @param targetSizeY the size of the target image along the Y axis
* @param filter the {@link Filter} to load the data
* @return
* @throws Exception
*/
public File execute(String mimeType, final ProgressListener progressListener,
CoverageInfo coverageInfo, Geometry roi, CoordinateReferenceSystem targetCRS,
boolean clip, Filter filter) throws Exception {
boolean clip, Filter filter, Interpolation interpolation, Integer targetSizeX,
Integer targetSizeY) throws Exception {

GridCoverage2D clippedGridCoverage = null, reprojectedGridCoverage = null, originalGridCoverage = null;
GridCoverage2D scaledGridCoverage = null, clippedGridCoverage = null, reprojectedGridCoverage = null, originalGridCoverage = null;
try {

//
Expand Down Expand Up @@ -180,7 +189,8 @@ public File execute(String mimeType, final ProgressListener progressListener,
readParameters = CoverageUtils.mergeParameter(parameterDescriptors, readParameters,
filter, "FILTER", "Filter");
}
// read GridGeometry preparation
// read GridGeometry preparation and scaling setup
ScaleToTarget scaling = null;
if (roi != null) {
// set crs in roi manager
roiManager.useNativeCRS(reader.getCoordinateReferenceSystem());
Expand All @@ -193,12 +203,26 @@ public File execute(String mimeType, final ProgressListener progressListener,
final ReferencedEnvelope roiEnvelope = new ReferencedEnvelope(roiManager
.getSafeRoiInNativeCRS().getEnvelopeInternal(), // safe envelope
nativeCRS);
GridGeometry2D gg2D = new GridGeometry2D(PixelInCell.CELL_CENTER,
reader.getOriginalGridToWorld(PixelInCell.CELL_CENTER), roiEnvelope,
GeoTools.getDefaultHints());
final Polygon originalEnvelopeAsPolygon = FeatureUtilities.getPolygon(reader.getOriginalEnvelope(),
new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING)));
originalEnvelopeAsPolygon.setUserData(nativeCRS);
final ReferencedEnvelope originalEnvelope = JTS.toEnvelope(originalEnvelopeAsPolygon);
// calculate intersection between original envelope and ROI, as blindly trusting
// the ROI may give issues with scaling, if target size is not specified for
// both X and Y dimensions
final ReferencedEnvelope intersection = originalEnvelope.intersection(roiEnvelope);
// take scaling into account
scaling = new ScaleToTarget(reader, intersection);
scaling.setTargetSize(targetSizeX, targetSizeY);
GridGeometry2D gg2D = scaling.getGridGeometry();

// TODO make sure the GridRange is not empty, depending on the resolution it might happen
readParameters = CoverageUtils.mergeParameter(parameterDescriptors, readParameters,
gg2D, AbstractGridFormat.READ_GRIDGEOMETRY2D.getName().getCode());
} else {
// we are reading the full coverage
scaling = new ScaleToTarget(reader);
scaling.setTargetSize(targetSizeX, targetSizeY);
}
// make sure we work in streaming fashion
readParameters = CoverageUtils.mergeParameter(parameterDescriptors, readParameters,
Expand All @@ -216,7 +240,7 @@ public File execute(String mimeType, final ProgressListener progressListener,
}
// avoid doing the transform if this is the identity
reprojectedGridCoverage = (GridCoverage2D) Operations.DEFAULT.resample(
originalGridCoverage, targetCRS);
originalGridCoverage, targetCRS, null, interpolation);

} else {
reprojectedGridCoverage = originalGridCoverage;
Expand All @@ -239,8 +263,6 @@ public File execute(String mimeType, final ProgressListener progressListener,
} else {
// use envelope of the ROI to simply crop and not clip the raster. This is important since when
// reprojecting we might read a bit more than needed!
final Geometry polygon = roiManager.getSafeRoiInTargetCRS();
polygon.setUserData(targetCRS);
clippedGridCoverage = cropCoverage.execute(reprojectedGridCoverage,
roiManager.getSafeRoiInTargetCRS(), progressListener);
}
Expand All @@ -250,9 +272,21 @@ public File execute(String mimeType, final ProgressListener progressListener,
}

//
// STEP 3 - Writing
// STEP 3 - scale to target size, if needed
//
return writeRaster(mimeType, coverageInfo, clippedGridCoverage);
if (interpolation != null) {
scaling.setInterpolation(interpolation);
}
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Scaling the layer");
}
// scaling and/or interpolation
scaledGridCoverage = scaling.scale(clippedGridCoverage);

//
// STEP 4 - Writing
//
return writeRaster(mimeType, coverageInfo, scaledGridCoverage);
} finally {
if (originalGridCoverage != null) {
resourceManager.addResource(new GridCoverageResource(originalGridCoverage));
Expand All @@ -263,6 +297,9 @@ public File execute(String mimeType, final ProgressListener progressListener,
if (clippedGridCoverage != null) {
resourceManager.addResource(new GridCoverageResource(clippedGridCoverage));
}
if (scaledGridCoverage != null) {
resourceManager.addResource(new GridCoverageResource(scaledGridCoverage));
}
}
}

Expand Down
@@ -1,24 +1,22 @@
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
/* (c) 2014 - 2015 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.awt.geom.Rectangle2D;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.geoserver.catalog.CoverageInfo;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.resources.coverage.FeatureUtilities;
import org.geotools.util.logging.Logging;
import org.opengis.filter.Filter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.util.ProgressListener;

import com.vividsolutions.jts.geom.Geometry;
Expand Down Expand Up @@ -59,11 +57,13 @@ public RasterEstimator(DownloadServiceConfiguration limits) {
* @param targetCRS the reproject {@link CoordinateReferenceSystem} (useless for the moment)
* @param clip whether or not to clip the resulting data (useless for the moment)
* @param filter the {@link Filter} to load the data
* @param targetSizeX the size of the target image along the X axis
* @param targetSizeY the size of the target image along the Y axis
* @return
*/
public boolean execute(final ProgressListener progressListener, CoverageInfo coverageInfo,
Geometry roi, CoordinateReferenceSystem targetCRS, boolean clip, Filter filter)
throws Exception {
Geometry roi, CoordinateReferenceSystem targetCRS, boolean clip, Filter filter,
Integer targetSizeX, Integer targetSizeY) throws Exception {

//
// Do we need to do anything?
Expand Down Expand Up @@ -110,8 +110,10 @@ public boolean execute(final ProgressListener progressListener, CoverageInfo cov

// Area to read in pixel
final double areaRead;
// If ROI is present, then the coverage BBOX is cropped with the ROI geometry
// take scaling into account
ScaleToTarget scaling = null;
if (roi != null) {
// If ROI is present, then the coverage BBOX is cropped with the ROI geometry
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Reprojecting ROI");
}
Expand All @@ -126,25 +128,25 @@ public boolean execute(final ProgressListener progressListener, CoverageInfo cov
}
return true; // EMPTY Intersection
}
// world to grid transform
final AffineTransform2D w2G = (AffineTransform2D) reader.getOriginalGridToWorld(
PixelInCell.CELL_CORNER).inverse();
final Geometry rasterGeometry = JTS.transform(roiInNativeCRS_, w2G);

// try to make an estimate of the area we need to read
// NOTE I use the envelope since in the end I can only pass down
// a rectangular source region to the ImageIO-Ext reader, but in the end I am only going
// to read the tile I will need during processing as in this case I am going to perform
// deferred reads
areaRead = rasterGeometry.getEnvelope().getArea();
// TODO investigate on improved precision taking into account tiling on raster geometry
ReferencedEnvelope refEnvelope = JTS.toEnvelope(roiInNativeCRS_.getEnvelope());
scaling = new ScaleToTarget(reader, refEnvelope);

// TODO investigate on improved precision taking into account tiling on raster geometry
} else {
// No ROI, we are trying to read the entire coverage
final Rectangle2D originalGridRange = (GridEnvelope2D) reader.getOriginalGridRange();
areaRead = originalGridRange.getWidth() * originalGridRange.getHeight();

scaling = new ScaleToTarget(reader);
}
scaling.setTargetSize(targetSizeX, targetSizeY);
GridGeometry2D gg = scaling.getGridGeometry();

areaRead = gg.getGridRange2D().width * gg.getGridRange2D().height;

// checks on the area we want to download
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Area to read in pixels: " + areaRead);
Expand All @@ -162,4 +164,6 @@ public boolean execute(final ProgressListener progressListener, CoverageInfo cov
return true;

}


}

0 comments on commit 22bc2a3

Please sign in to comment.