/* (c) 2014 - 2016 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.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.catalog.CoverageDimensionInfo; import org.geoserver.catalog.CoverageInfo; import org.geotools.coverage.TypeMap; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.geometry.jts.JTS; 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.util.ProgressListener; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.PrecisionModel; /** * This class check whether or not the provided download request goes beyond the provided limits for raster data or not. * * @author Simone Giannecchini, GeoSolutions * */ class RasterEstimator { private static final Logger LOGGER = Logging.getLogger(RasterEstimator.class); /** The downloadServiceConfiguration object containing the limits to check */ private DownloadServiceConfiguration downloadServiceConfiguration; /** * Constructor * * @param limits the parent {@link DownloadEstimatorProcess} that contains the download limits to be enforced. * */ public RasterEstimator(DownloadServiceConfiguration limits) { this.downloadServiceConfiguration = limits; if (limits == null) { throw new NullPointerException("The provided DownloadEstimatorProcess is null!"); } } /** * Check the download limits for raster data. * * @param coverage the {@link CoverageInfo} to estimate the download limits * @param roi the {@link Geometry} for the clip/intersection * @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 * @param selectedBands the band indices selected for output, in case of raster input * */ public boolean execute(final ProgressListener progressListener, CoverageInfo coverageInfo, Geometry roi, CoordinateReferenceSystem targetCRS, boolean clip, Filter filter, Integer targetSizeX, Integer targetSizeY, int[] bandIndices) throws Exception { final long rasterSizeLimits = downloadServiceConfiguration.getRasterSizeLimits(); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Raster size limits: " + rasterSizeLimits); } // // ---> READ FROM NATIVE RESOLUTION <-- // if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Checking download limits for raster request"); } CoordinateReferenceSystem nativeCRS = DownloadUtilities.getNativeCRS(coverageInfo); 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); } // get a reader for this CoverageInfo final GridCoverage2DReader reader = (GridCoverage2DReader) coverageInfo .getGridCoverageReader(null, null); // Area to read in pixel final long areaRead; // 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"); } final Geometry safeRoiInNativeCRS = roiManager.getSafeRoiInNativeCRS(); Geometry roiInNativeCRS_ = safeRoiInNativeCRS.intersection(FeatureUtilities.getPolygon( reader.getOriginalEnvelope(), new GeometryFactory(new PrecisionModel( PrecisionModel.FLOATING)))); if (roiInNativeCRS_.isEmpty()) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Empty intersection, so the result does not exceed the limits"); } return true; // EMPTY Intersection } // 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 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 scaling = new ScaleToTarget(reader); } scaling.setTargetSize(targetSizeX, targetSizeY); GridGeometry2D gg = scaling.getGridGeometry(); areaRead = (long) 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); } // If the area to read or the target image size are above Integer.MAX_VALUE, false is returned, // as raster processing operations (e.g. Crop, Scale) may fail if image size exceeds integer limits long targetArea = 1L; Integer[] targetSize = scaling.getTargetSize(); if (targetSize[0] != null && targetSize[1] != null) { targetArea = (long) targetSize[0] * targetSize[1]; } if (areaRead >= Integer.MAX_VALUE || targetArea >= Integer.MAX_VALUE) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Area to read or target image size exceeds maximum integer value: " + Integer.MAX_VALUE); } return false; } // If the area exceeds the limits, false is returned if (rasterSizeLimits > DownloadServiceConfiguration.NO_LIMIT && (areaRead > rasterSizeLimits || targetArea > rasterSizeLimits)) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Area exceeds the limits"); } return false; } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Area does not exceed the limits"); } // Try to check write limits, using input's coverageinfo int bandsCount = coverageInfo.getDimensions().size(); // Use sample info type for each output band to estimate size List<CoverageDimensionInfo> coverageDimensionInfoList = coverageInfo.getDimensions(); int accumulatedPixelSizeInBits = 0; // Use only selected bands for output, if specified if (bandIndices!=null && bandIndices.length>0){ for (int i=0;i<bandIndices.length;i++){ //Use valid indices if (bandIndices[i]>=0 && bandIndices[i]<bandsCount) accumulatedPixelSizeInBits += TypeMap.getSize(coverageDimensionInfoList.get(bandIndices[i]).getDimensionType()); } }else{ for (int i=0;i<bandsCount;i++){ accumulatedPixelSizeInBits += TypeMap.getSize(coverageDimensionInfoList.get(i).getDimensionType()); } } /// Total size in bytes long rasterSizeInBytes = (long) targetArea*accumulatedPixelSizeInBits/8; final long writeLimits = downloadServiceConfiguration.getWriteLimits(); // If size exceeds the write limits, false is returned if (writeLimits > DownloadServiceConfiguration.NO_LIMIT && rasterSizeInBytes > writeLimits) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Output raw raster size ("+rasterSizeInBytes+") exceeds" + " the specified write limits ("+writeLimits+")"); } return false; } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Output raw raster size ("+rasterSizeInBytes+") does not exceed" + " the specified write limits ("+writeLimits+")"); } return true; } }