package org.geotools.process.raster;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.DataBuffer;
import java.util.List;
import javax.media.jai.ROI;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.TypeMap;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.JTS;
import org.geotools.process.ProcessException;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.resources.ClassChanger;
import org.geotools.util.Utilities;
import org.jaitools.imageutils.ROIGeometry;
import org.jaitools.media.jai.rangelookup.RangeLookupTable;
import org.jaitools.numeric.Range;
import org.opengis.coverage.SampleDimensionType;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier;
/**
* A set of utilities methods for the Grid Coverage package. Those methods are not really
* rigorous; must of them should be seen as temporary implementations.
*
* @author Simone Giannecchini, GeoSolutions
*
* @source $URL$
*/
public class CoverageUtilities {
/**
* Do not allows instantiation of this class.
*/
private CoverageUtilities() {
}
/**
* Utility method for transforming a geometry ROI into the raster space, using the provided affine transformation.
*
* @param roi a {@link Geometry} in model space.
* @param mt2d an {@link AffineTransform} that maps from raster to model space. This is already referred to the pixel corner.
* @return a {@link ROI} suitable for using with JAI.
* @throws ProcessException in case there are problems with ivnerting the provided {@link AffineTransform}. Very unlikely to happen.
*/
public static ROI prepareROI(Geometry roi, AffineTransform mt2d) throws ProcessException {
// transform the geometry to raster space so that we can use it as a ROI source
Geometry rasterSpaceGeometry;
try {
rasterSpaceGeometry = JTS.transform(roi, new AffineTransform2D(mt2d.createInverse()));
} catch (MismatchedDimensionException e) {
throw new ProcessException(e);
} catch (TransformException e) {
throw new ProcessException(e);
} catch (NoninvertibleTransformException e) {
throw new ProcessException(e);
}
// System.out.println(rasterSpaceGeometry);
// System.out.println(rasterSpaceGeometry.getEnvelopeInternal());
// simplify the geometry so that it's as precise as the coverage, excess coordinates
// just make it slower to determine the point in polygon relationship
Geometry simplifiedGeometry = DouglasPeuckerSimplifier.simplify(
rasterSpaceGeometry, 1);
// build a shape using a fast point in polygon wrapper
return new ROIGeometry(simplifiedGeometry);
}
/**
* Creates a {@link SimpleFeatureType} that exposes a coverage as a collections
* of feature points, mapping the centre of each pixel as a point plus all the bands as attributes.
*
* <p>
* The FID is the long that combines x+y*width.
*
* @param gc2d the {@link GridCoverage2D} to wrap.
* @param geometryClass the class for the geometry.
* @return a {@link SimpleFeatureType} or <code>null</code> in case we are unable to wrap the coverage
*/
public static SimpleFeatureType createFeatureType(final GridCoverage2D gc2d, final Class<? extends Geometry> geometryClass) {
// checks
Utilities.ensureNonNull("gc2d", gc2d);
// building a feature type for this coverage
final SimpleFeatureTypeBuilder ftBuilder= new SimpleFeatureTypeBuilder();
ftBuilder.setName(gc2d.getName().toString());
ftBuilder.setNamespaceURI("http://www.geotools.org/");
// CRS
ftBuilder.setCRS(gc2d.getCoordinateReferenceSystem2D());
// ftBuilder.setCRS(DefaultEngineeringCRS.GENERIC_2D);
// TYPE is as follows the_geom | band
ftBuilder.setDefaultGeometry("the_geom");
ftBuilder.add("the_geom", geometryClass);
if(!geometryClass.equals(Point.class)){
ftBuilder.add("value",Double.class);
}else{
// get sample type on bands
final GridSampleDimension[] sampleDimensions = gc2d.getSampleDimensions();
for(GridSampleDimension sd:sampleDimensions){
final SampleDimensionType sdType=sd.getSampleDimensionType();
final int dataBuffType =TypeMap.getDataBufferType(sdType);
// TODO I think this should be a public utility inside the FeatureUtilities class
@SuppressWarnings("rawtypes")
final Class bandClass;
switch(dataBuffType){
case DataBuffer.TYPE_BYTE:
bandClass=Byte.class;
break;
case DataBuffer.TYPE_DOUBLE:
bandClass=Double.class;
break;
case DataBuffer.TYPE_FLOAT:
bandClass=Float.class;
break;
case DataBuffer.TYPE_INT:
bandClass=Integer.class;
break;
case DataBuffer.TYPE_SHORT:case DataBuffer.TYPE_USHORT:
bandClass=Short.class;
break;
case DataBuffer.TYPE_UNDEFINED: default:
return null;
}
ftBuilder.add(sd.getDescription().toString(),bandClass);
}
}
return ftBuilder.buildFeatureType();
}
public static RangeLookupTable getRangeLookupTable(
final List<Range> classificationRanges,
final Number noDataValue) {
return getRangeLookupTable(classificationRanges, noDataValue, noDataValue.getClass());
}
public static RangeLookupTable getRangeLookupTable(
final List<Range> classificationRanges,
final Number noDataValue,
final Class clazz) {
return getRangeLookupTable(classificationRanges, null, noDataValue, noDataValue.getClass());
}
public static RangeLookupTable getRangeLookupTable(
final List<Range> classificationRanges,
final int[] outputPixelValues,
final Number noDataValue) {
return getRangeLookupTable(classificationRanges, outputPixelValues, noDataValue, noDataValue.getClass());
}
public static RangeLookupTable getRangeLookupTable(List<Range> classificationRanges, final int[] outputPixelValues,
final Number noDataValue, final Class<? extends Number> clazz)
{
final RangeLookupTable.Builder rltBuilder = new RangeLookupTable.Builder();
final int size = classificationRanges.size();
final boolean useCustomOutputPixelValues = outputPixelValues != null && outputPixelValues.length == size;
Class<? extends Number> widestClass = noDataValue.getClass();
for (int i = 0; i < size; i++) {
final Range range = classificationRanges.get(i);
final Class<? extends Number> rangeClass = range.getMin().getClass();
if (widestClass != rangeClass) {
widestClass = ClassChanger.getWidestClass(widestClass, rangeClass);
}
final int reference = useCustomOutputPixelValues ? outputPixelValues [i] : i + 1;
rltBuilder.add(range, convert(reference, noDataValue.getClass()));
}
// Add the largest range that contains the no data value
rltBuilder.add(new Range(getClassMinimum(widestClass), true, getClassMaximum(widestClass), true), noDataValue);
return rltBuilder.build();
}
private static Number getClassMinimum(Class<? extends Number> numberClass) {
if (numberClass == null) {
return null;
}
else if (Double.class.equals(numberClass)) {
return Double.MIN_VALUE;
}
else if (Float.class.equals(numberClass)) {
return Float.MIN_VALUE;
}
else if (Long.class.equals(numberClass)) {
return Long.MIN_VALUE;
}
else if (Integer.class.equals(numberClass)) {
return Integer.MIN_VALUE;
}
else if (Short.class.equals(numberClass)) {
return Short.MIN_VALUE;
}
else if (Byte.class.equals(numberClass)) {
return Byte.MIN_VALUE;
}
throw new UnsupportedOperationException("Class " + numberClass + " can't be used in a value Range");
}
private static Number getClassMaximum(Class<? extends Number> numberClass) {
if (numberClass == null) {
return null;
}
else if (Double.class.equals(numberClass)) {
return Double.MAX_VALUE;
}
else if (Float.class.equals(numberClass)) {
return Float.MAX_VALUE;
}
else if (Long.class.equals(numberClass)) {
return Long.MAX_VALUE;
}
else if (Integer.class.equals(numberClass)) {
return Integer.MAX_VALUE;
}
else if (Short.class.equals(numberClass)) {
return Short.MAX_VALUE;
}
else if (Byte.class.equals(numberClass)) {
return Byte.MAX_VALUE;
}
throw new UnsupportedOperationException("Class " + numberClass + " can't be used in a value Range");
}
// @SuppressWarnings("unchecked")
// public static <T extends Number & Comparable> T guessNoDataValue(Class<T> type){
// if (type == null) {
// return null;
// } else if (Double.class.equals(type)) {
// return (T) new Double(Double.NaN);
// } else if (Float.class.equals(type)) {
// return (T) new Float(Float.NaN);
// } else if (Integer.class.equals(type)) {
// return (T) Integer.valueOf(Integer.MIN_VALUE);
// } else if (Byte.class.equals(type)) {
// return (T) Byte.valueOf(0));
// } else if (Short.class.equals(type)) {
// return (T) Byte.valueOf(0));
// } else {
// throw new UnsupportedOperationException("Class " + type
// + " can't be used in a value Range");
// }
// }
public static Number convert(Number val, Class<? extends Number> type) {
if (val == null) {
return null;
} else if (Double.class.equals(type)) {
if(val instanceof Double){
return val;
}
return Double.valueOf(val.doubleValue());
} else if (Float.class.equals(type)) {
if(val instanceof Float){
return val;
}
return Float.valueOf(val.floatValue());
} else if (Integer.class.equals(type)) {
if(val instanceof Integer){
return val;
}
return Integer.valueOf(val.intValue());
} else if (Byte.class.equals(type)) {
if(val instanceof Byte){
return val;
}
return Byte.valueOf(val.byteValue());
} else if (Short.class.equals(type)) {
if(val instanceof Short){
return val;
}
return Short.valueOf(val.shortValue());
} else {
throw new UnsupportedOperationException("Class " + type
+ " can't be used in a value Range");
}
}
// public static <T extends Number & Comparable> Range<T> convertRange(Range src, Class<T> type) {
// return new Range<T>(convert(src.getMin(), type), src.isMinIncluded(), convert(src.getMax(),
// type), src.isMaxIncluded());
// }
// public static <T extends Number & Comparable<T>> Class<T> mapDataBufferType(int type) {
//
// switch (type) {
// case DataBuffer.TYPE_BYTE:
// return (Class<T>)Byte.class;
// case DataBuffer.TYPE_USHORT:
// return (Class<T>)Short.class;
// case DataBuffer.TYPE_SHORT:
// return (Class<T>)Short.class;
// case DataBuffer.TYPE_INT:
// return (Class<T>)Integer.class;
// case DataBuffer.TYPE_FLOAT:
// return (Class<T>)Float.class;
// case DataBuffer.TYPE_DOUBLE:
// return (Class<T>) Double.class;
// default:
// throw new IllegalArgumentException("Unknown DataBuffer type " + type);
// }
// }
}