/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2015, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.process.raster;
import it.geosolutions.jaiext.range.Range;
import it.geosolutions.jaiext.stats.Statistics.StatsType;
import it.geosolutions.jaiext.zonal.ZonalStatsDescriptor;
import it.geosolutions.jaiext.zonal.ZoneGeometry;
import java.awt.RenderingHints;
import java.awt.image.RenderedImage;
import java.util.List;
import javax.media.jai.JAI;
import javax.media.jai.operator.NullDescriptor;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.processing.CoverageProcessor;
import org.geotools.coverage.processing.operation.GridCoverage2DRIA;
import org.geotools.coverage.processing.operation.ZonalStatistics;
import org.geotools.process.factory.DescribeParameter;
import org.geotools.process.factory.DescribeProcess;
import org.geotools.process.factory.DescribeResult;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.parameter.ParameterValueGroup;
import com.sun.media.jai.util.SunTileCache;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Polygon;
/**
* This class wraps the "ZonalStats2" OperationJAI and executes the selected operation with the defined parameters.
* No transformation of the optional classifier image is needed because inside the process the classifier image is
* already transformed by the {@link GridCoverage2DRIA} operation. By default the input band is set 0, and the statistics
* to MEAN, MAX, MIN, EXTREMA, VARIANCE, STANDARD DEVIATION.
*
* The main difference between this class and {@link RasterZonalStatistics} is the fact that this operation will calculate
* the requested statistics for all the {@link SimpleFeature}s in a single step, without having to iterate on the features.
*
* This Process is a simple wrapper of the {@link ZonalStatistics} operation
*
* @author geosolutions
*
*/
@DescribeProcess(title = "Raster Zonal Statistics", description = "Computes statistics for the distribution of a certain quantity in a set of polygonal zones.")
public class RasterZonalStatistics2 implements RasterProcess {
/**
* Default processor used for executing the operations.
*/
private final static CoverageProcessor PROCESSOR = CoverageProcessor.getInstance();
/**
* Default statistics to calculate.
*/
private final static StatsType[] DEFAULT_STATISTICS = new StatsType[] { StatsType.MEAN,
StatsType.MAX, StatsType.MIN, StatsType.EXTREMA, StatsType.VARIANCE, StatsType.DEV_STD };
@DescribeResult(name = "zonal statistics", description = "A feature collection with the attributes of the zone layer (prefixed by 'z_') and the statistics fields min,max,sum,avg,stddev")
public List<ZoneGeometry> execute(
@DescribeParameter(name = "source", description = "Input raster to compute statistics for") GridCoverage2D coverage,
@DescribeParameter(name = "bands", description = "Source band used to compute statistics (default is 0)") int[] bands,
@DescribeParameter(name = "zones", description = "Zone polygon features for which to compute statistics") List<SimpleFeature> zones,
@DescribeParameter(name = "classifier", description = "Raster whose values will be used as classes for the statistical analysis. Each zone reports statistics partitioned "
+ "by classes according to the values of the raster. Must be a single band raster with integer values.", min = 0) GridCoverage2D classifier,
@DescribeParameter(name = "nodata", description = "Input Range for NoData") Range nodata,
@DescribeParameter(name = "mask", description = "Optional mask for the statistic calculations") Geometry mask,
@DescribeParameter(name = "useROIAccessor", description = "Boolean indicating if a RasterAccessor associated to the Mask should be used for calculating statistics. (Only with Mask field present)",defaultValue = "false") boolean useROIAccessor,
@DescribeParameter(name = "roi", description = "Optional roi object, if the zones parameter is not used") Polygon roi,
@DescribeParameter(name = "statistics", description = "Statistics to calculate (default are min,max,sum,avg,stddev)") StatsType[] stats,
@DescribeParameter(name = "minbounds", description = "Minimum bounds used for calculating Histogram, median and mode operations (for each band)") double[] minbounds,
@DescribeParameter(name = "maxbounds", description = "Maximum bounds used for calculating Histogram, median and mode operations (for each band)") double[] maxbounds,
@DescribeParameter(name = "numbins", description = "Number of Bins used for calculating Histogram, median and mode operations (for each band)") int[] numbins,
@DescribeParameter(name = "rangeData", description = "Maximum bounds used for calculating Histogram, median and mode operations (for each band)") List<Range> rangeData,
@DescribeParameter(name = "localStats", description = "Number of Bins used for calculating Histogram, median and mode operations (for each band)") boolean localStats) {
// If no band is indicated, then the first band is taken
int[] ibands = new int[] { 0 };
if (bands == null) {
bands = ibands;
}
// If no statistic is defined, then the default statistics are taken
if (stats == null) {
stats = DEFAULT_STATISTICS;
}
RenderedImage classificationRaster = null;
// prepare the classification image if necessary
if (classifier != null) {
// find nodata values
GridSampleDimension sampleDimension = classifier.getSampleDimension(0);
double[] nodataarr = sampleDimension.getNoDataValues();
// Setting of the No Data values
double[] noDataClassifier = nodataarr != null ? nodataarr : new double[]{Double.NaN};
// this will adapt the classification image to the projection and image layout
// of the data coverage
classificationRaster = GridCoverage2DRIA.create(classifier, coverage, noDataClassifier);
// Definition of the JAI TileCache to use
RenderingHints hints = new RenderingHints(JAI.KEY_TILE_CACHE, new SunTileCache());
// Wrap of the classification raster with a NullDescriptor for adding the TileCache hints
classificationRaster = NullDescriptor.create(classificationRaster, hints);
}
// Selection of the operation
final ParameterValueGroup param = PROCESSOR.getOperation("Zonal").getParameters();
// Setting of the parameters
param.parameter("Source").setValue(coverage);
param.parameter("bands").setValue(bands);
param.parameter("classifier").setValue(classificationRaster);
param.parameter("roi").setValue(roi);
param.parameter("roilist").setValue(zones);
param.parameter("NoData").setValue(nodata);
param.parameter("mask").setValue(mask);
param.parameter("useROIAccessor").setValue(useROIAccessor);
param.parameter("stats").setValue(stats);
param.parameter("minbound").setValue(minbounds);
param.parameter("maxbound").setValue(maxbounds);
param.parameter("numbin").setValue(numbins);
param.parameter("rangeData").setValue(rangeData);
param.parameter("localstats").setValue(localStats);
// Operation execution
GridCoverage2D output = (GridCoverage2D) PROCESSOR.doOperation(param);
// Retrieval of the result
List<ZoneGeometry> value = (List<ZoneGeometry>) output
.getProperty(ZonalStatsDescriptor.ZS_PROPERTY);
return value;
}
}