/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2011-2015, Open Source Geospatial Foundation (OSGeo)
* (C) 2001-2007 TOPP - www.openplans.org.
*
* 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.rlookup.RangeLookupTable;
import it.geosolutions.jaiext.JAIExt;
import it.geosolutions.jaiext.range.NoDataContainer;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.util.HashMap;
import java.util.List;
import javax.media.jai.RenderedOp;
import org.geotools.process.factory.DescribeParameter;
import org.geotools.process.factory.DescribeProcess;
import org.geotools.process.factory.DescribeResult;
import org.geotools.coverage.Category;
import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.factory.GeoTools;
import org.geotools.image.ImageWorker;
import org.geotools.process.ProcessException;
import org.geotools.renderer.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.image.ColorUtilities;
import org.jaitools.numeric.Range;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.util.ProgressListener;
/**
* A raster reclassified process
*
* @author Andrea Antonello (www.hydrologis.com)
* @author Emanuele Tajariol (GeoSolutions)
* @author Simone Giannecchini (GeoSolutions)
* @author Andrea Aime - GeoSolutions
* @author Daniele Romagnoli - GeoSolutions
*
* @source $URL$
*/
@DescribeProcess(title = "Reclassify", description = "Reclassifies a continous raster into integer values defined by a set of ranges")
public class RangeLookupProcess implements RasterProcess {
private final static double DEFAULT_NODATA = 0d;
@DescribeResult(name = "reclassified", description = "The reclassified raster")
public GridCoverage2D execute(
@DescribeParameter(name = "coverage", description = "Input raster") GridCoverage2D coverage,
@DescribeParameter(name = "band", description = "Source band to use for classification (default is 0)", min = 0, defaultValue = "0") Integer classificationBand,
@DescribeParameter(name = "ranges", description = "Specifier for a value range in the format ( START ; END ). START and END values are optional. [ and ] can also be used as brackets, to indicate inclusion of the relevant range endpoint.",
collectionType=Range.class) List<Range> classificationRanges,
@DescribeParameter(name = "outputPixelValues", description = "Value to be assigned to corresponding range", min = 0 ) int[] outputPixelValues,
@DescribeParameter(name = "noData", description = "Value to be assigned to pixels outside any range (defaults to 0)", min = 0, defaultValue = "0" ) Double noData,
ProgressListener listener) throws ProcessException {
//
// initial checks
//
if(coverage==null){
throw new ProcessException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1,"coverage"));
}
if(classificationRanges==null){
throw new ProcessException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1,"classificationRanges"));
}
double nd = DEFAULT_NODATA;
NoDataContainer noDataProperty = org.geotools.resources.coverage.CoverageUtilities.getNoDataProperty(coverage);
if (noData != null) {
nd = noData.doubleValue();
} else if (noDataProperty != null) {
nd = noDataProperty.getAsSingleValue();
}
if (outputPixelValues != null && outputPixelValues.length > 0){
final int ranges = classificationRanges.size();
if (ranges != outputPixelValues.length){
throw new ProcessException(Errors.format(ErrorKeys.MISMATCHED_ARRAY_LENGTH, "outputPixelValues"));
}
}
RenderedImage sourceImage = coverage.getRenderedImage();
ImageWorker worker = new ImageWorker(sourceImage);
// parse the band
if (classificationBand != null) {
final int band = classificationBand;
final int numbands=sourceImage.getSampleModel().getNumBands();
if(band<0 || numbands<=band){
throw new ProcessException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,"band",band));
}
if(band==0 && numbands>0 || band>0){
worker.retainBands(new int []{band});
}
}
//
// Check the number of ranges we have in order to decide which type we can use for the output values.
// Our goal is to use the smallest possible data type that can hold the image values.
//
Object lookupTable;
final int size=classificationRanges.size();
int transferType = ColorUtilities.getTransferType(size);
if(JAIExt.isJAIExtOperation("RLookup")){
lookupTable = CoverageUtilities.getRangeLookupTableJAIEXT(classificationRanges, outputPixelValues, nd, transferType);
}else{
// Builds the range lookup table
//final RangeLookupTable lookupTable;
switch (transferType) {
case DataBuffer.TYPE_BYTE:
lookupTable = CoverageUtilities.getRangeLookupTable(classificationRanges, outputPixelValues, (byte) nd );
break;
case DataBuffer.TYPE_USHORT:
lookupTable = CoverageUtilities.getRangeLookupTable(classificationRanges, outputPixelValues, (short) nd );
break;
case DataBuffer.TYPE_INT:
lookupTable = CoverageUtilities.getRangeLookupTable(classificationRanges, outputPixelValues, nd );
break;
default:
throw new IllegalArgumentException(org.geotools.resources.i18n.Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,
"classification ranges size",size));
}
}
worker.setROI(org.geotools.resources.coverage.CoverageUtilities.getROIProperty(coverage));
worker.setBackground(new double[]{nd});
final RenderedOp indexedClassification = worker.rangeLookup(lookupTable)
.getRenderedOperation();
//
// build the output coverage
//
// build the output sample dimensions, use the default value ( 0 ) as the no data
final GridSampleDimension outSampleDimension = new GridSampleDimension("classification",
new Category[] { Category.NODATA }, null);
final GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(GeoTools.getDefaultHints());
HashMap<String,Object> properties = new HashMap<String,Object>(){{
put(NoDataContainer.GC_NODATA,new NoDataContainer(0d));
}};
org.geotools.resources.coverage.CoverageUtilities.setROIProperty(properties, worker.getROI());
final GridCoverage2D output = factory.create("reclassified", indexedClassification, coverage
.getGridGeometry(), new GridSampleDimension[] { outSampleDimension },
new GridCoverage[] { coverage }, properties);
return output;
}
/**
* Execute the RangeLookupProcess on the provided coverage (left for backwards compatibility)
*
* @param coverage The continuous coverage to be reclassified
* @param classificationBand The band to be used for classification
* @param classificationRanges The list of ranges to be applied
* @param listener The progress listener
* @return The reclassified coverage
* @throws ProcessException
*/
public GridCoverage2D execute(GridCoverage2D coverage, Integer classificationBand,
List<Range> classificationRanges, ProgressListener listener) throws ProcessException {
return execute(coverage, classificationBand, classificationRanges, null, 0d, listener);
}
}