/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2011, 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.gs;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Logger;
import javax.media.jai.JAI;
import javax.media.jai.RenderedOp;
import javax.media.jai.operator.BandSelectDescriptor;
import org.geotools.process.factory.DescribeParameter;
import org.geotools.process.factory.DescribeProcess;
import org.geotools.process.factory.DescribeResult;
import org.geotools.process.gs.GSProcess;
import org.geotools.process.raster.CoverageUtilities;
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.image.jai.Registry;
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.geotools.util.logging.Logging;
import org.jaitools.media.jai.rangelookup.RangeLookupDescriptor;
import org.jaitools.media.jai.rangelookup.RangeLookupRIF;
import org.jaitools.media.jai.rangelookup.RangeLookupTable;
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 coverage into a set of ranges identified by a number")
public class RangeLookupProcess implements GSProcess {
private final static double DEFAULT_NODATA = 0d;
private final static Logger LOGGER = Logging.getLogger(RangeLookupProcess.class);
static {
Registry.registerRIF(JAI.getDefaultInstance(), new RangeLookupDescriptor(), new RangeLookupRIF(), Registry.JAI_TOOLS_PRODUCT);
}
@DescribeResult(name = "reclassified", description = "The resulting reclassified coverage")
public GridCoverage2D execute(
@DescribeParameter(name = "coverage", description = "The continuous coverage to be reclassified") GridCoverage2D coverage,
@DescribeParameter(name = "band", description = "The band to be used for classification "
+ "(defaults to 0)", min = 0) Integer classificationBand,
@DescribeParameter(name = "ranges", description = "The list of ranges to be applied. \n"
+ "Each range is expressed as 'OPEN START ; END CLOSE'\n"
+ "where 'OPEN:=(|[, CLOSE=)|]',\n "
+ "START is the low value, or nothing to imply -INF,\n"
+ "CLOSE is the biggest value, or nothing to imply +INF", collectionType=Range.class) List<Range> classificationRanges,
@DescribeParameter(name = "outputPixelValues", description = "The array of pixel values to be associated to the ranges", min = 0 ) int[] outputPixelValues,
@DescribeParameter(name = "noData", description = "The pixel value to be assigned to input pixels outside any range (defaults to 0)", min = 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;
if (noData != null){
nd = noData.doubleValue();
}
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();
// 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)
sourceImage=BandSelectDescriptor.create(sourceImage, new int []{band}, null);
}
//
// 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.
//
// Builds the range lookup table
final RangeLookupTable lookupTable;
final int size=classificationRanges.size();
switch (ColorUtilities.getTransferType(size)) {
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));
}
// reclassify the source image
final RenderedOp indexedClassification = RangeLookupDescriptor.create(sourceImage, lookupTable, null);
//
// 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).geophysics(true);
final GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null);
final GridCoverage2D output = factory.create("reclassified", indexedClassification, coverage
.getGridGeometry(), new GridSampleDimension[] { outSampleDimension },
new GridCoverage[] { coverage }, new HashMap<String,Double>(){{
put("GC_NODATA",0d);
}});
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);
}
}