/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2011-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.RangeFactory; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.image.DataBuffer; import java.util.HashMap; 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.coverage.grid.GridEnvelope2D; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.geometry.jts.JTS; import org.geotools.process.ProcessException; import org.geotools.referencing.operation.matrix.XAffineTransform; import org.geotools.referencing.operation.transform.AffineTransform2D; import org.geotools.resources.ClassChanger; import org.geotools.util.Utilities; import it.geosolutions.jaiext.vectorbin.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.DirectPosition; import org.opengis.geometry.Envelope; 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 { public static final String NORTH = "NORTH"; public static final String SOUTH = "SOUTH"; public static final String WEST = "WEST"; public static final String EAST = "EAST"; public static final String XRES = "XRES"; public static final String YRES = "YRES"; public static final String ROWS = "ROWS"; public static final String COLS = "COLS"; public static final String MINY = "MINY"; public static final String MINX = "MINX"; /** * 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(); } public static it.geosolutions.jaiext.rlookup.RangeLookupTable getRangeLookupTableJAIEXT( List<Range> classificationRanges, final int[] outputPixelValues, final Number noDataValue, final int transferType) { final it.geosolutions.jaiext.rlookup.RangeLookupTable.Builder rltBuilder = new it.geosolutions.jaiext.rlookup.RangeLookupTable.Builder(); final int size = classificationRanges.size(); final boolean useCustomOutputPixelValues = outputPixelValues != null && outputPixelValues.length == size; Class<? extends Number> noDataClass = it.geosolutions.jaiext.range.Range.DataType .classFromType(transferType); Class<? extends Number> widestClass = noDataClass; 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); } int rangeType = it.geosolutions.jaiext.range.Range.DataType .dataTypeFromClass(rangeClass); final int reference = useCustomOutputPixelValues ? outputPixelValues[i] : i + 1; it.geosolutions.jaiext.range.Range rangeJaiext = RangeFactory.convert(RangeFactory .create(range.getMin().doubleValue(), range.isMinIncluded(), range.getMax() .doubleValue(), range.isMaxIncluded()), rangeType); rltBuilder.add(rangeJaiext, convert(reference, noDataClass)); } // Add the largest range that contains the no data value int rangeType = it.geosolutions.jaiext.range.Range.DataType.dataTypeFromClass(widestClass); it.geosolutions.jaiext.range.Range rangeJaiext = RangeFactory.convert( RangeFactory.create(getClassMinimum(widestClass).doubleValue(), getClassMaximum(widestClass).doubleValue()), rangeType); rltBuilder.add(rangeJaiext, 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"); } 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"); } } /** * Get the parameters of the region covered by the {@link GridCoverage2D coverage}. * * @param gridCoverage the coverage. * @return the {@link HashMap map} of parameters. ( {@link #NORTH} and the other static vars can be used to retrieve them. */ public static HashMap<String, Double> getRegionParamsFromGridCoverage( GridCoverage2D gridCoverage) { HashMap<String, Double> envelopeParams = new HashMap<String, Double>(); Envelope envelope = gridCoverage.getEnvelope(); DirectPosition lowerCorner = envelope.getLowerCorner(); double[] westSouth = lowerCorner.getCoordinate(); DirectPosition upperCorner = envelope.getUpperCorner(); double[] eastNorth = upperCorner.getCoordinate(); GridGeometry2D gridGeometry = gridCoverage.getGridGeometry(); GridEnvelope2D gridRange = gridGeometry.getGridRange2D(); int height = gridRange.height; int width = gridRange.width; int minX = gridRange.x; int minY = gridRange.y; AffineTransform gridToCRS = (AffineTransform) gridGeometry.getGridToCRS(); double xRes = XAffineTransform.getScaleX0(gridToCRS); double yRes = XAffineTransform.getScaleY0(gridToCRS); envelopeParams.put(NORTH, eastNorth[1]); envelopeParams.put(SOUTH, westSouth[1]); envelopeParams.put(WEST, westSouth[0]); envelopeParams.put(EAST, eastNorth[0]); envelopeParams.put(XRES, xRes); envelopeParams.put(YRES, yRes); envelopeParams.put(ROWS, (double) height); envelopeParams.put(COLS, (double) width); envelopeParams.put(MINY, (double) minY); envelopeParams.put(MINX, (double) minX); return envelopeParams; } }