package mil.nga.giat.geowave.adapter.raster; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.IndexColorModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import javax.media.jai.BorderExtender; import javax.media.jai.Histogram; import javax.media.jai.Interpolation; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import javax.media.jai.RasterFactory; import javax.media.jai.RenderedImageAdapter; import javax.media.jai.RenderedOp; import javax.media.jai.TiledImage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.geotools.coverage.Category; import org.geotools.coverage.CoverageFactoryFinder; import org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.TypeMap; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.coverage.processing.Operations; import org.geotools.geometry.DirectPosition2D; import org.geotools.geometry.Envelope2D; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.JTSFactoryFinder; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.image.ImageWorker; import org.geotools.referencing.CRS; import org.geotools.referencing.operation.builder.GridToEnvelopeMapper; import org.geotools.referencing.operation.matrix.MatrixFactory; import org.geotools.referencing.operation.transform.ProjectiveTransform; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.resources.image.ImageUtilities; import org.geotools.util.NumberRange; import org.opengis.coverage.SampleDimension; import org.opengis.coverage.SampleDimensionType; import org.opengis.coverage.grid.GridCoverage; import org.opengis.geometry.Envelope; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.TransformException; import com.google.common.collect.ImmutableMap; import com.sun.media.imageioimpl.common.BogusColorSpace; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import mil.nga.giat.geowave.adapter.raster.adapter.RasterDataAdapter; import mil.nga.giat.geowave.adapter.raster.adapter.merge.RasterTileMergeStrategy; import mil.nga.giat.geowave.adapter.raster.plugin.GeoWaveGTRasterFormat; import mil.nga.giat.geowave.core.index.FloatCompareUtils; import mil.nga.giat.geowave.core.index.sfc.data.MultiDimensionalNumericData; public class RasterUtils { private static final RenderingHints DEFAULT_RENDERING_HINTS = new RenderingHints( new ImmutableMap.Builder().put( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY).put( RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY).put( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON).put( RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY).put( RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE).put( JAI.KEY_BORDER_EXTENDER, BorderExtender.createInstance(BorderExtender.BORDER_COPY)).build()); private static Operations resampleOperations; private final static Logger LOGGER = LoggerFactory.getLogger(RasterUtils.class); private static final int MIN_SEGMENTS = 5; private static final int MAX_SEGMENTS = 500; private static final int MAX_VERTICES_BEFORE_SIMPLIFICATION = 20; private static final double SIMPLIFICATION_MAX_DEGREES = 0.0001; public static Geometry getFootprint( final GridCoverage gridCoverage, final CoordinateReferenceSystem targetCrs ) { return getFootprint( getReferenceEnvelope( gridCoverage, targetCrs), gridCoverage); } public static ReferencedEnvelope getReferenceEnvelope( final GridCoverage gridCoverage, final CoordinateReferenceSystem targetCrs ) { final CoordinateReferenceSystem sourceCrs = gridCoverage.getCoordinateReferenceSystem(); final Envelope sampleEnvelope = gridCoverage.getEnvelope(); final ReferencedEnvelope sampleReferencedEnvelope = new ReferencedEnvelope( new com.vividsolutions.jts.geom.Envelope( sampleEnvelope.getMinimum(0), sampleEnvelope.getMaximum(0), sampleEnvelope.getMinimum(1), sampleEnvelope.getMaximum(1)), gridCoverage.getCoordinateReferenceSystem()); ReferencedEnvelope projectedReferenceEnvelope = sampleReferencedEnvelope; if ((targetCrs != null) && !targetCrs.equals(sourceCrs)) { try { projectedReferenceEnvelope = sampleReferencedEnvelope.transform( targetCrs, true); } catch (TransformException | FactoryException e) { LOGGER.warn( "Unable to transform envelope of grid coverage to " + targetCrs.getName(), e); } } return projectedReferenceEnvelope; } public static Geometry getFootprint( final ReferencedEnvelope projectedReferenceEnvelope, final GridCoverage gridCoverage ) { try { final Envelope sampleEnvelope = gridCoverage.getEnvelope(); final double avgSpan = (projectedReferenceEnvelope.getSpan(0) + projectedReferenceEnvelope.getSpan(1)) / 2; final MathTransform gridCrsToWorldCrs = CRS.findMathTransform( gridCoverage.getCoordinateReferenceSystem(), GeoWaveGTRasterFormat.DEFAULT_CRS, true); final Coordinate[] polyCoords = getWorldCoordinates( sampleEnvelope.getMinimum(0), sampleEnvelope.getMinimum(1), sampleEnvelope.getMaximum(0), sampleEnvelope.getMaximum(1), gridCrsToWorldCrs.isIdentity() ? 2 : (int) Math.min( Math.max( (avgSpan * MIN_SEGMENTS) / SIMPLIFICATION_MAX_DEGREES, MIN_SEGMENTS), MAX_SEGMENTS), gridCrsToWorldCrs); final Polygon poly = new GeometryFactory().createPolygon(polyCoords); if (polyCoords.length > MAX_VERTICES_BEFORE_SIMPLIFICATION) { final Geometry retVal = DouglasPeuckerSimplifier.simplify( poly, SIMPLIFICATION_MAX_DEGREES); if (retVal.isEmpty()) { return poly; } return retVal; } else { return poly; } } catch (MismatchedDimensionException | TransformException | FactoryException e1) { LOGGER.warn( "Unable to calculate grid coverage footprint", e1); } return null; } public static Geometry combineIntoOneGeometry( final Geometry geometry1, final Geometry geometry2 ) { if (geometry1 == null) { return geometry2; } else if (geometry2 == null) { return geometry1; } final List<Geometry> geometry = new ArrayList<Geometry>(); geometry.add(geometry1); geometry.add(geometry2); return DouglasPeuckerSimplifier.simplify( combineIntoOneGeometry(geometry), SIMPLIFICATION_MAX_DEGREES); } private static Geometry combineIntoOneGeometry( final Collection<Geometry> geometries ) { final GeometryFactory factory = JTSFactoryFinder.getGeometryFactory(null); // note the following geometry collection may be invalid (say with // overlapping polygons) final Geometry geometryCollection = factory.buildGeometry(geometries); // try { return geometryCollection.union(); // } // catch (Exception e) { // LOGGER.warn("Error creating a union of this geometry collection", e); // return geometryCollection; // } } private static Coordinate[] getWorldCoordinates( final double minX, final double minY, final double maxX, final double maxY, final int numPointsPerSegment, final MathTransform gridToCRS ) throws MismatchedDimensionException, TransformException { final Point2D[] gridCoordinates = getGridCoordinates( minX, minY, maxX, maxY, numPointsPerSegment); final Coordinate[] worldCoordinates = new Coordinate[gridCoordinates.length]; for (int i = 0; i < gridCoordinates.length; i++) { final DirectPosition2D worldPt = new DirectPosition2D(); final DirectPosition2D dp = new DirectPosition2D( gridCoordinates[i]); gridToCRS.transform( dp, worldPt); worldCoordinates[i] = new Coordinate( worldPt.getX(), worldPt.getY()); } return worldCoordinates; } private static Point2D[] getGridCoordinates( final double minX, final double minY, final double maxX, final double maxY, final int numPointsPerSegment ) { final Point2D[] coordinates = new Point2D[((numPointsPerSegment - 1) * 4) + 1]; fillCoordinates( true, minX, minY, maxY, (maxY - minY) / (numPointsPerSegment - 1), 0, coordinates); fillCoordinates( false, maxY, minX, maxX, (maxX - minX) / (numPointsPerSegment - 1), numPointsPerSegment - 1, coordinates); fillCoordinates( true, maxX, maxY, minY, (maxY - minY) / (numPointsPerSegment - 1), (numPointsPerSegment - 1) * 2, coordinates); fillCoordinates( false, minY, maxX, minX, (maxX - minX) / (numPointsPerSegment - 1), (numPointsPerSegment - 1) * 3, coordinates); return coordinates; } private static void fillCoordinates( final boolean constantX, final double constant, final double start, final double stop, final double inc, final int coordinateArrayOffset, final Point2D[] coordinates ) { int i = coordinateArrayOffset; if (constantX) { final double x = constant; if (stop < start) { for (double y = start; y >= stop; y -= inc) { coordinates[i++] = new Point2D.Double( x, y); } } else { for (double y = start; y <= stop; y += inc) { coordinates[i++] = new Point2D.Double( x, y); } } } else { final double y = constant; if (stop < start) { double x = start; while (x >= stop) { coordinates[i] = new Point2D.Double( x, y); i++; x = start - ((i - coordinateArrayOffset) * inc); } } else { for (double x = start; x <= stop; x += inc) { coordinates[i++] = new Point2D.Double( x, y); } } } } /** * Creates a math transform using the information provided. * * @return The math transform. * @throws IllegalStateException * if the grid range or the envelope were not set. */ public static MathTransform createTransform( final double[] idRangePerDimension, final MultiDimensionalNumericData fullBounds ) throws IllegalStateException { final GridToEnvelopeMapper mapper = new GridToEnvelopeMapper(); final boolean swapXY = mapper.getSwapXY(); final boolean[] reverse = mapper.getReverseAxis(); final PixelInCell gridType = PixelInCell.CELL_CORNER; final int dimension = 2; /* * Setup the multi-dimensional affine transform for use with OpenGIS. * According OpenGIS specification, transforms must map pixel center. * This is done by adding 0.5 to grid coordinates. */ final double translate; if (PixelInCell.CELL_CENTER.equals(gridType)) { translate = 0.5; } else if (PixelInCell.CELL_CORNER.equals(gridType)) { translate = 0.0; } else { throw new IllegalStateException( Errors.format( ErrorKeys.ILLEGAL_ARGUMENT_$2, "gridType", gridType)); } final Matrix matrix = MatrixFactory.create(dimension + 1); final double[] minValuesPerDimension = fullBounds.getMinValuesPerDimension(); final double[] maxValuesPerDimension = fullBounds.getMaxValuesPerDimension(); for (int i = 0; i < dimension; i++) { // NOTE: i is a dimension in the 'gridRange' space (source // coordinates). // j is a dimension in the 'userRange' space (target coordinates). int j = i; if (swapXY) { j = 1 - j; } double scale = idRangePerDimension[j]; double offset; if ((reverse == null) || (j >= reverse.length) || !reverse[j]) { offset = minValuesPerDimension[j]; } else { scale = -scale; offset = maxValuesPerDimension[j]; } offset -= scale * (-translate); matrix.setElement( j, j, 0.0); matrix.setElement( j, i, scale); matrix.setElement( j, dimension, offset); } return ProjectiveTransform.create(matrix); } /** * Returns the math transform as a two-dimensional affine transform. * * @return The math transform as a two-dimensional affine transform. * @throws IllegalStateException * if the math transform is not of the appropriate type. */ public static AffineTransform createAffineTransform( final double[] idRangePerDimension, final MultiDimensionalNumericData fullBounds ) throws IllegalStateException { final MathTransform transform = createTransform( idRangePerDimension, fullBounds); if (transform instanceof AffineTransform) { return (AffineTransform) transform; } throw new IllegalStateException( Errors.format(ErrorKeys.NOT_AN_AFFINE_TRANSFORM)); } public static void fillWithNoDataValues( final WritableRaster raster, final double[][] noDataValues ) { if ((noDataValues != null) && (noDataValues.length >= raster.getNumBands())) { final double[] noDataFilledArray = new double[raster.getWidth() * raster.getHeight()]; for (int b = 0; b < raster.getNumBands(); b++) { if ((noDataValues[b] != null) && (noDataValues[b].length > 0)) { // just fill every sample in this band with the first no // data value for that band Arrays.fill( noDataFilledArray, noDataValues[b][0]); raster.setSamples( raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), b, noDataFilledArray); } } } } public static GridCoverage2D mosaicGridCoverages( final Iterator<GridCoverage> gridCoverages, final Color backgroundColor, final Color outputTransparentColor, final Rectangle pixelDimension, final GeneralEnvelope requestEnvelope, final double levelResX, final double levelResY, final double[][] noDataValues, final boolean xAxisSwitch, final GridCoverageFactory coverageFactory, final String coverageName, final Interpolation interpolation, final Histogram histogram, final boolean scaleTo8BitSet, final boolean scaleTo8Bit, final ColorModel defaultColorModel ) { if (pixelDimension == null) { LOGGER.error("Pixel dimension can not be null"); throw new IllegalArgumentException( "Pixel dimension can not be null"); } final double rescaleX = levelResX / (requestEnvelope.getSpan(0) / pixelDimension.getWidth()); final double rescaleY = levelResY / (requestEnvelope.getSpan(1) / pixelDimension.getHeight()); final double width = pixelDimension.getWidth() / rescaleX; final double height = pixelDimension.getHeight() / rescaleY; final int imageWidth = (int) Math.max( Math.round(width), 1); final int imageHeight = (int) Math.max( Math.round(height), 1); BufferedImage image = null; int numDimensions; SampleDimension[] sampleDimensions = null; double[][] extrema = null; boolean extremaValid = false; while (gridCoverages.hasNext()) { final GridCoverage currentCoverage = gridCoverages.next(); if (sampleDimensions == null) { numDimensions = currentCoverage.getNumSampleDimensions(); sampleDimensions = new SampleDimension[numDimensions]; extrema = new double[2][numDimensions]; extremaValid = true; for (int d = 0; d < numDimensions; d++) { sampleDimensions[d] = currentCoverage.getSampleDimension(d); extrema[0][d] = sampleDimensions[d].getMinimumValue(); extrema[1][d] = sampleDimensions[d].getMaximumValue(); if ((extrema[1][d] - extrema[0][d]) <= 0) { extremaValid = false; } } } final Envelope coverageEnv = currentCoverage.getEnvelope(); final RenderedImage coverageImage = currentCoverage.getRenderedImage(); if (image == null) { image = copyImage( imageWidth, imageHeight, backgroundColor, noDataValues, coverageImage); } final int posx = (int) ((coverageEnv.getMinimum(0) - requestEnvelope.getMinimum(0)) / levelResX); final int posy = (int) ((requestEnvelope.getMaximum(1) - coverageEnv.getMaximum(1)) / levelResY); image.getRaster().setRect( posx, posy, coverageImage.getData()); } if (image == null) { image = getEmptyImage( imageWidth, imageHeight, backgroundColor, null, // the transparent color will be used later defaultColorModel); } GeneralEnvelope resultEnvelope = null; if (xAxisSwitch) { final Rectangle2D tmp = new Rectangle2D.Double( requestEnvelope.getMinimum(1), requestEnvelope.getMinimum(0), requestEnvelope.getSpan(1), requestEnvelope.getSpan(0)); resultEnvelope = new GeneralEnvelope( tmp); resultEnvelope.setCoordinateReferenceSystem(requestEnvelope.getCoordinateReferenceSystem()); } else { resultEnvelope = requestEnvelope; } final double scaleX = rescaleX * (width / imageWidth); final double scaleY = rescaleY * (height / imageHeight); if ((Math.abs(scaleX - 1) > FloatCompareUtils.COMP_EPSILON) || (Math.abs(scaleY - 1) > FloatCompareUtils.COMP_EPSILON)) { image = rescaleImageViaPlanarImage( interpolation, rescaleX * (width / imageWidth), rescaleY * (height / imageHeight), image); } RenderedImage result = image; // hypothetically masking the output transparent color should happen // before histogram stretching, but the masking seems to only work now // when the image is bytes in each band which requires some amount of // modification to the original data, we'll use extrema if (extremaValid && scaleTo8Bit) { final int dataType = result.getData().getDataBuffer().getDataType(); switch (dataType) { // in case the original image has a USHORT pixel type without // being associated // with an index color model I would still go to 8 bits case DataBuffer.TYPE_USHORT: if (result.getColorModel() instanceof IndexColorModel) { break; } case DataBuffer.TYPE_DOUBLE: case DataBuffer.TYPE_FLOAT: if (!scaleTo8BitSet && (dataType != DataBuffer.TYPE_USHORT)) { break; } case DataBuffer.TYPE_INT: case DataBuffer.TYPE_SHORT: // rescale to byte final ImageWorkerPredefineStats w = new ImageWorkerPredefineStats( result); // it was found that geoserver will perform this, and worse // perform it on local extrema calculated from a single // tile, this is our one opportunity to at least ensure this // transformation is done without too much harm by using // global extrema result = w.setExtrema( extrema).rescaleToBytes().getRenderedImage(); break; default: // findbugs seems to want to have a default case, default is // to do nothing break; } } if (outputTransparentColor != null) { result = ImageUtilities.maskColor( outputTransparentColor, result); } if (histogram != null) { // we should perform histogram equalization final int numBands = histogram.getNumBands(); final float[][] cdFeq = new float[numBands][]; final double[][] computedExtrema = new double[2][numBands]; for (int b = 0; b < numBands; b++) { computedExtrema[0][b] = histogram.getLowValue(b); computedExtrema[1][b] = histogram.getHighValue(b); final int numBins = histogram.getNumBins()[b]; cdFeq[b] = new float[numBins]; for (int i = 0; i < numBins; i++) { cdFeq[b][i] = (float) (i + 1) / (float) (numBins); } } final RenderedImageAdapter adaptedResult = new RenderedImageAdapter( result); adaptedResult.setProperty( "histogram", histogram); adaptedResult.setProperty( "extrema", computedExtrema); result = JAI.create( "matchcdf", adaptedResult, cdFeq); } return coverageFactory.create( coverageName, result, resultEnvelope); } @SuppressFBWarnings(value = { "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT" }, justification = "incorrect; drawImage has side effects") public static BufferedImage toBufferedImage( final Image image, final int type ) { final BufferedImage bi = new BufferedImage( image.getWidth(null), image.getHeight(null), type); final Graphics g = bi.getGraphics(); g.drawImage( image, 0, 0, null); g.dispose(); return bi; } private static BufferedImage copyImage( final int targetWidth, final int targetHeight, final Color backgroundColor, final double[][] noDataValues, final RenderedImage originalImage ) { Hashtable<String, Object> properties = null; if (originalImage.getPropertyNames() != null) { properties = new Hashtable<String, Object>(); for (final String name : originalImage.getPropertyNames()) { properties.put( name, originalImage.getProperty(name)); } } final SampleModel sm = originalImage.getSampleModel().createCompatibleSampleModel( targetWidth, targetHeight); final WritableRaster raster = Raster.createWritableRaster( sm, null); final ColorModel colorModel = originalImage.getColorModel(); final boolean alphaPremultiplied = colorModel.isAlphaPremultiplied(); RasterUtils.fillWithNoDataValues( raster, noDataValues); final BufferedImage image = new BufferedImage( colorModel, raster, alphaPremultiplied, properties); if (noDataValues == null) { final Graphics2D g2D = (Graphics2D) image.getGraphics(); final Color save = g2D.getColor(); g2D.setColor(backgroundColor); g2D.fillRect( 0, 0, image.getWidth(), image.getHeight()); g2D.setColor(save); } return image; } private static BufferedImage rescaleImageViaPlanarImage( final Interpolation interpolation, final double rescaleX, final double rescaleY, final BufferedImage image ) { final PlanarImage planarImage = new TiledImage( image, image.getWidth(), image.getHeight()); final ImageWorker w = new ImageWorker( planarImage); w.scale( (float) rescaleX, (float) rescaleY, 0.0f, 0.0f, interpolation); final RenderedOp result = w.getRenderedOperation(); final Raster raster = result.getData(); final WritableRaster scaledImageRaster; if (raster instanceof WritableRaster) { scaledImageRaster = (WritableRaster) raster; } else { scaledImageRaster = raster.createCompatibleWritableRaster(); scaledImageRaster.setDataElements( 0, 0, raster); } final ColorModel colorModel = image.getColorModel(); try { final BufferedImage scaledImage = new BufferedImage( colorModel, scaledImageRaster, image.isAlphaPremultiplied(), null); return scaledImage; } catch (final IllegalArgumentException e) { LOGGER.warn( "Unable to rescale image", e); return image; } } public static void forceRenderingHints( final RenderingHints renderingHints ) { resampleOperations = new Operations( renderingHints); } public static synchronized Operations getCoverageOperations() { if (resampleOperations == null) { resampleOperations = new Operations( DEFAULT_RENDERING_HINTS); } return resampleOperations; } public static BufferedImage getEmptyImage( final int width, final int height, final Color backgroundColor, final Color outputTransparentColor, final ColorModel defaultColorModel ) { BufferedImage emptyImage = new BufferedImage( defaultColorModel, defaultColorModel.createCompatibleWritableRaster( width, height), defaultColorModel.isAlphaPremultiplied(), null); final Graphics2D g2D = (Graphics2D) emptyImage.getGraphics(); final Color save = g2D.getColor(); g2D.setColor(backgroundColor); g2D.fillRect( 0, 0, emptyImage.getWidth(), emptyImage.getHeight()); g2D.setColor(save); if (outputTransparentColor != null) { emptyImage = new RenderedImageAdapter( ImageUtilities.maskColor( outputTransparentColor, emptyImage)).getAsBufferedImage(); } return emptyImage; } public static WritableRaster createRasterTypeDouble( final int numBands, final int tileSize ) { final WritableRaster raster = RasterFactory.createBandedRaster( DataBuffer.TYPE_DOUBLE, tileSize, tileSize, numBands, null); final double[] defaultValues = new double[tileSize * tileSize * numBands]; Arrays.fill( defaultValues, Double.NaN); raster.setDataElements( 0, 0, tileSize, tileSize, defaultValues); return raster; } public static RasterDataAdapter createDataAdapterTypeDouble( final String coverageName, final int numBands, final int tileSize ) { return createDataAdapterTypeDouble( coverageName, numBands, tileSize, null); } public static RasterDataAdapter createDataAdapterTypeDouble( final String coverageName, final int numBands, final int tileSize, final RasterTileMergeStrategy<?> mergeStrategy ) { return createDataAdapterTypeDouble( coverageName, numBands, tileSize, null, null, null, mergeStrategy); } public static RasterDataAdapter createDataAdapterTypeDouble( final String coverageName, final int numBands, final int tileSize, final double[] minsPerBand, final double[] maxesPerBand, final String[] namesPerBand, final RasterTileMergeStrategy<?> mergeStrategy ) { final double[][] noDataValuesPerBand = new double[numBands][]; final double[] backgroundValuesPerBand = new double[numBands]; final int[] bitsPerSample = new int[numBands]; for (int i = 0; i < numBands; i++) { noDataValuesPerBand[i] = new double[] { Double.valueOf(Double.NaN) }; backgroundValuesPerBand[i] = Double.valueOf(Double.NaN); bitsPerSample[i] = DataBuffer.getDataTypeSize(DataBuffer.TYPE_DOUBLE); } final SampleModel sampleModel = createRasterTypeDouble( numBands, tileSize).getSampleModel(); return new RasterDataAdapter( coverageName, sampleModel, new ComponentColorModel( new BogusColorSpace( numBands), bitsPerSample, false, false, Transparency.OPAQUE, DataBuffer.TYPE_DOUBLE), new HashMap<String, String>(), tileSize, minsPerBand, maxesPerBand, namesPerBand, noDataValuesPerBand, backgroundValuesPerBand, null, false, Interpolation.INTERP_NEAREST, false, mergeStrategy); } public static GridCoverage2D createCoverageTypeDouble( final String coverageName, final double westLon, final double eastLon, final double southLat, final double northLat, final WritableRaster raster ) { final GridCoverageFactory gcf = CoverageFactoryFinder.getGridCoverageFactory(null); Envelope mapExtent; try { mapExtent = new ReferencedEnvelope( westLon, eastLon, southLat, northLat, GeoWaveGTRasterFormat.DEFAULT_CRS); } catch (final IllegalArgumentException e) { LOGGER.warn( "Unable to use default CRS", e); mapExtent = new Envelope2D( new DirectPosition2D( westLon, southLat), new DirectPosition2D( eastLon, northLat)); } return gcf.create( coverageName, raster, mapExtent); } public static GridCoverage2D createCoverageTypeDouble( final String coverageName, final double westLon, final double eastLon, final double southLat, final double northLat, final double[] minPerBand, final double[] maxPerBand, final String[] namePerBand, final WritableRaster raster ) { final GridCoverageFactory gcf = CoverageFactoryFinder.getGridCoverageFactory(null); Envelope mapExtent; try { mapExtent = new ReferencedEnvelope( westLon, eastLon, southLat, northLat, GeoWaveGTRasterFormat.DEFAULT_CRS); } catch (final IllegalArgumentException e) { LOGGER.warn( "Unable to use default CRS", e); mapExtent = new Envelope2D( new DirectPosition2D( westLon, southLat), new DirectPosition2D( eastLon, northLat)); } final GridSampleDimension[] bands = new GridSampleDimension[raster.getNumBands()]; create( namePerBand, raster.getSampleModel(), minPerBand, maxPerBand, bands); return gcf.create( coverageName, raster, mapExtent, bands); } /** * NOTE: This is a small bit of functionality "inspired by" * org.geotools.coverage.grid.RenderedSampleDimension ie. some of the code * has been modified/simplified from the original version, but it had * private visibility and could not be re-used as is Creates a set of sample * dimensions for the data backing the given iterator. Particularly, it was * desirable to be able to provide the name per band which was not provided * in the original. * * @param name * The name for each band of the data (e.g. "Elevation"). * @param model * The image or raster sample model. * @param min * The minimal value, or {@code null} for computing it * automatically. * @param max * The maximal value, or {@code null} for computing it * automatically. * @param dst * The array where to store sample dimensions. The array length * must matches the number of bands. */ private static void create( final CharSequence[] name, final SampleModel model, final double[] min, final double[] max, final GridSampleDimension[] dst ) { final int numBands = dst.length; if ((min != null) && (min.length != numBands)) { throw new IllegalArgumentException( Errors.format( ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3, numBands, min.length, "min[i]")); } if ((name != null) && (name.length != numBands)) { throw new IllegalArgumentException( Errors.format( ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3, numBands, name.length, "name[i]")); } if ((max != null) && (max.length != numBands)) { throw new IllegalArgumentException( Errors.format( ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3, numBands, max.length, "max[i]")); } /* * Arguments are know to be valids. We now need to compute two ranges: * * STEP 1: Range of target (sample) values. This is computed in the * following block. STEP 2: Range of source (geophysics) values. It will * be computed one block later. * * The target (sample) values will typically range from 0 to 255 or 0 to * 65535, but the general case is handled as well. If the source * (geophysics) raster uses floating point numbers, then a "nodata" * category may be added in order to handle NaN values. If the source * raster use integer numbers instead, then we will rescale samples only * if they would not fit in the target data type. */ final SampleDimensionType sourceType = TypeMap.getSampleDimensionType( model, 0); final boolean sourceIsFloat = TypeMap.isFloatingPoint(sourceType); // Default to TYPE_BYTE for floating point images only; otherwise // keep unchanged. SampleDimensionType targetType = sourceIsFloat ? SampleDimensionType.UNSIGNED_8BITS : sourceType; // Default setting: no scaling final boolean targetIsFloat = TypeMap.isFloatingPoint(targetType); NumberRange targetRange = TypeMap.getRange(targetType); Category[] categories = new Category[1]; final boolean needScaling; if (targetIsFloat) { // Never rescale if the target is floating point numbers. needScaling = false; } else if (sourceIsFloat) { // Always rescale for "float to integer" conversions. In addition, // Use 0 value as a "no data" category for unsigned data type only. needScaling = true; if (!TypeMap.isSigned(targetType)) { categories = new Category[2]; categories[1] = Category.NODATA; targetRange = TypeMap.getPositiveRange(targetType); } } else { // In "integer to integer" conversions, rescale only if // the target range is smaller than the source range. needScaling = !targetRange.contains(TypeMap.getRange(sourceType)); } /* * Now, constructs the sample dimensions. We will inconditionnaly * provides a "nodata" category for floating point images targeting * unsigned integers, since we don't know if the user plan to have NaN * values. Even if the current image doesn't have NaN values, it could * have NaN later if the image uses a writable raster. */ for (int b = 0; b < numBands; b++) { // if (needScaling) { // sourceRange = NumberRange.create( // min[b], // max[b]).castTo( // sourceRange.getElementClass()); // categories[0] = new Category( // name[b], // null, // targetRange, // sourceRange); // } // else { // categories[0] = new Category( // name[b], // null, // targetRange, // LinearTransform1D.IDENTITY); // } // dst[b] = new GridSampleDimension( // name[b], // categories, // null); categories[0] = new Category( name[b], (Color) null, targetRange); dst[b] = new GridSampleDimension( name[b], categories, null); } } }