Package org.geotools.arcsde.raster.info

Source Code of org.geotools.arcsde.raster.info.RasterUtils

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2002-2009, 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.arcsde.raster.info;

import static org.geotools.arcsde.raster.info.RasterCellType.TYPE_16BIT_S;
import static org.geotools.arcsde.raster.info.RasterCellType.TYPE_16BIT_U;
import static org.geotools.arcsde.raster.info.RasterCellType.TYPE_1BIT;
import static org.geotools.arcsde.raster.info.RasterCellType.TYPE_32BIT_REAL;
import static org.geotools.arcsde.raster.info.RasterCellType.TYPE_32BIT_S;
import static org.geotools.arcsde.raster.info.RasterCellType.TYPE_32BIT_U;
import static org.geotools.arcsde.raster.info.RasterCellType.TYPE_4BIT;
import static org.geotools.arcsde.raster.info.RasterCellType.TYPE_64BIT_REAL;
import static org.geotools.arcsde.raster.info.RasterCellType.TYPE_8BIT_U;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.geom.Rectangle2D;
import java.awt.image.BandedSampleModel;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.SampleModel;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import javax.imageio.ImageTypeSpecifier;

import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.io.OverviewPolicy;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.resources.image.ColorUtilities;
import org.geotools.resources.image.ComponentColorModelJAI;
import org.geotools.util.NumberRange;
import org.geotools.util.logging.Logging;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.referencing.operation.TransformException;

import com.sun.imageio.plugins.common.BogusColorSpace;

/**
*
* @author Gabriel Roldan (OpenGeo)
* @since 2.5.4
* @version $Id$
*
*
* @source $URL$
*         http://svn.osgeo.org/geotools/trunk/modules/plugin/arcsde/datastore/src/main/java/org
*         /geotools/arcsde/raster/info/RasterUtils.java $
*/
@SuppressWarnings({ "nls" })
public class RasterUtils {

    private static final Logger LOGGER = Logging.getLogger("org.geotools.arcsde.gce");

    private RasterUtils() {
        // do nothing
    }

    /**
     * Returns the grid range specifying the matching tiles for a given pyramid level and grid
     * extent specifying the overlapping area to request in the level's pixel space.
     *
     * @param pixelRange
     * @param tilesHigh
     * @param tilesWide
     * @param tileSize
     * @param numTilesHigh
     * @param numTilesWide
     *
     * @param pixelRange
     * @param level
     *
     * @return a grid range holding the coordinates in tile space that fully covers the requested
     *         pixel range for the given pyramid level, or a negative area range
     */
    private static GridEnvelope findMatchingTiles(final Dimension tileSize, int numTilesWide,
            int numTilesHigh, final GridEnvelope pixelRange) {

        final int minPixelX = pixelRange.getLow(0);
        final int minPixelY = pixelRange.getLow(1);

        int minTileX = (int) Math.floor(minPixelX / tileSize.getWidth());
        int minTileY = (int) Math.floor(minPixelY / tileSize.getHeight());

        int numTilesX = (int) Math.ceil(pixelRange.getSpan(0) / tileSize.getWidth());
        int numTilesY = (int) Math.ceil(pixelRange.getSpan(1) / tileSize.getHeight());

        int maxTiledX = (minTileX + numTilesX) * tileSize.width;
        int maxTiledY = (minTileY + numTilesY) * tileSize.height;

        if (maxTiledX < pixelRange.getHigh(0) && (minTileX + numTilesX) < numTilesWide) {
            numTilesX++;
        }

        if (maxTiledY < pixelRange.getHigh(1) && (minTileY + numTilesY) < numTilesHigh) {
            numTilesY++;
        }

        GridEnvelope2D matchingTiles = new GridEnvelope2D(minTileX, minTileY, numTilesX, numTilesY);
        return matchingTiles;
    }

    private static GridEnvelope getTargetGridRange(final MathTransform modelToRaster,
            final Envelope requestedEnvelope) {
        GridEnvelope levelOverlappingPixels;
        int levelMinPixelX;
        int levelMaxPixelX;
        int levelMinPixelY;
        int levelMaxPixelY;
        {
            // use a model to raster transform to find out which pixel range at the specified level
            // better match the requested extent
            GeneralEnvelope requestedPixels;
            try {
                requestedPixels = CRS.transform(modelToRaster, requestedEnvelope);
            } catch (NoninvertibleTransformException e) {
                throw new IllegalArgumentException(e);
            } catch (TransformException e) {
                throw new IllegalArgumentException(e);
            }

            levelMinPixelX = (int) Math.floor(requestedPixels.getMinimum(0));
            levelMinPixelY = (int) Math.floor(requestedPixels.getMinimum(1));

            levelMaxPixelX = (int) Math.ceil(requestedPixels.getMaximum(0));
            levelMaxPixelY = (int) Math.ceil(requestedPixels.getMaximum(1));

            final int width = levelMaxPixelX - levelMinPixelX;
            final int height = levelMaxPixelY - levelMinPixelY;
            levelOverlappingPixels = new GridEnvelope2D(levelMinPixelX, levelMinPixelY, width,
                    height);
        }
        return levelOverlappingPixels;
    }

    /**
     * Creates an IndexColorModel out of a DataBuffer obtained from an ArcSDE's raster color map.
     *
     * @param colorMapData
     * @return
     */
    public static IndexColorModel sdeColorMapToJavaColorModel(final DataBuffer colorMapData,
            final int bitsPerSample) {
        if (colorMapData == null) {
            throw new NullPointerException("colorMapData");
        }

        if (colorMapData.getNumBanks() < 3 || colorMapData.getNumBanks() > 4) {
            throw new IllegalArgumentException("colorMapData shall have 3 or 4 banks: "
                    + colorMapData.getNumBanks());
        }

        if (bitsPerSample != 8 && bitsPerSample != 16) {
            throw new IllegalAccessError("bits per sample shall be either 8 or 16. Got "
                    + bitsPerSample);
        }

        final int numBanks = colorMapData.getNumBanks();
        final int mapSize = colorMapData.getSize();

        byte[] r = new byte[mapSize];
        byte[] g = new byte[mapSize];
        byte[] b = new byte[mapSize];
        byte[] a = new byte[mapSize];

        for (int i = 0; i < mapSize; i++) {
            r[i] = (byte) (colorMapData.getElem(0, i) & 0xFF);
            g[i] = (byte) (colorMapData.getElem(1, i) & 0xFF);
            b[i] = (byte) (colorMapData.getElem(2, i) & 0xFF);
            a[i] = (byte) ((numBanks == 3 ? 255 : colorMapData.getElem(3, i)) & 0xFF);
        }

        IndexColorModel colorModel = new IndexColorModel(bitsPerSample, mapSize, r, g, b, a);

        return colorModel;
    }

    public static ImageTypeSpecifier createFullImageTypeSpecifier(
            final RasterDatasetInfo rasterInfo, final int rasterIndex) {

        final int numberOfBands = rasterInfo.getNumBands();
        final RasterCellType nativePixelType = rasterInfo.getNativeCellType();
        final RasterCellType pixelType = rasterInfo.getTargetCellType(rasterIndex);

        // Prepare temporary colorModel and sample model, needed to build the final
        // ArcSDEPyramidLevel level;
        final int sampleImageWidth = 1;// rasterInfo.getImageWidth();
        final int sampleImageHeight = 1;// rasterInfo.getImageHeight();

        final ImageTypeSpecifier its;
        // treat special cases...
        final int bitsPerSample = pixelType.getBitsPerSample();
        final int dataType = pixelType.getDataBufferType();
        final boolean hasColorMap = rasterInfo.isColorMapped();

        if (hasColorMap) {
            // special case, a single band colormapped image
            IndexColorModel colorMap = rasterInfo.getColorMap(rasterIndex);
            its = createColorMappedImageSpec(colorMap, sampleImageWidth, sampleImageHeight);

        } else if (nativePixelType == TYPE_1BIT && numberOfBands == 1) {
            byte noDataValue = rasterInfo.getNoDataValue(rasterIndex, 0).byteValue();
            // special case, a single band 1-bit
            its = createOneBitColorMappedImageSpec(sampleImageWidth, sampleImageHeight, noDataValue);

        } else if (nativePixelType == TYPE_4BIT && numberOfBands == 1) {
            byte noDataValue = rasterInfo.getNoDataValue(rasterIndex, 0).byteValue();
            // special case, a single band 4-bit
            its = createFourBitColorMappedImageSpec(sampleImageWidth, sampleImageHeight,
                    noDataValue);
        } else if (numberOfBands == 1) {
            // special case, a single band grayscale image, no matter the pixel depth
            its = createGrayscaleImageSpec(sampleImageWidth, sampleImageHeight, dataType,
                    bitsPerSample);

        } else if (numberOfBands == 3 && pixelType == TYPE_8BIT_U) {
            // special case, an optimizable RGB image
            its = createRGBImageSpec(sampleImageWidth, sampleImageHeight, dataType);

        } else if (numberOfBands == 4 && pixelType == TYPE_8BIT_U) {
            // special case, an optimizable RGBA image
            its = createRGBAImageSpec(sampleImageWidth, sampleImageHeight, dataType);

        } else {
            /*
             * not an special case, go for a more generic sample model, potentially slower than the
             * special case ones, but that'll work anyway
             */

            final ColorModel colorModel;
            final SampleModel sampleModel;
            {
                final ColorSpace colorSpace;
                colorSpace = new BogusColorSpace(numberOfBands);
                int[] numBits = new int[numberOfBands];
                for (int i = 0; i < numberOfBands; i++) {
                    numBits[i] = bitsPerSample;
                }
                colorModel = new ComponentColorModelJAI(colorSpace, numBits, false, false,
                        Transparency.OPAQUE, dataType);
            }
            {
                int[] bankIndices = new int[numberOfBands];
                int[] bandOffsets = new int[numberOfBands];
                // int bandOffset = (tileWidth * tileHeight * pixelType.getBitsPerSample()) / 8;
                for (int i = 0; i < numberOfBands; i++) {
                    bankIndices[i] = i;
                    bandOffsets[i] = 0;// (i * bandOffset);
                }
                sampleModel = new BandedSampleModel(dataType, sampleImageWidth, sampleImageHeight,
                        sampleImageWidth, bankIndices, bandOffsets);
            }
            its = new ImageTypeSpecifier(colorModel, sampleModel);
        }

        return its;
    }

    private static ImageTypeSpecifier createFourBitColorMappedImageSpec(int sampleImageWidth,
            int sampleImageHeight, byte noDataValue) {

        int maxValue = (int) TYPE_4BIT.getSampleValueRange().getMaximum();

        int mapSize = noDataValue > maxValue ? noDataValue : maxValue + 1;

        int[] cmap = new int[mapSize];
        ColorUtilities.expand(new Color[] { Color.BLACK, Color.WHITE }, cmap, 0, maxValue);

        for (int i = maxValue; i < mapSize; i++) {
            cmap[i] = ColorUtilities.getIntFromColor(0, 0, 0, 0);
        }

        int transparentPixel = noDataValue;
        IndexColorModel colorModel = new IndexColorModel(8, mapSize, cmap, 0, true,
                transparentPixel, DataBuffer.TYPE_BYTE);

        SampleModel sampleModel = colorModel.createCompatibleSampleModel(sampleImageWidth,
                sampleImageHeight);
        ImageTypeSpecifier its = new ImageTypeSpecifier(colorModel, sampleModel);
        return its;
    }

    private static ImageTypeSpecifier createOneBitColorMappedImageSpec(int sampleImageWidth,
            int sampleImageHeight, byte noDataValue) {

        assert noDataValue == 2;

        final int FALSE = ColorUtilities.getIntFromColor(255, 255, 255, 255);
        final int TRUE = ColorUtilities.getIntFromColor(0, 0, 0, 255);
        final int NODATA = ColorUtilities.getIntFromColor(255, 255, 255, 0);

        final int mapSize = 3;
        int[] cmap = new int[mapSize];
        cmap[0] = FALSE;
        cmap[1] = TRUE;
        cmap[2] = NODATA;

        int transparentPixel = noDataValue;
        IndexColorModel colorModel = new IndexColorModel(8, mapSize, cmap, 0, false,
                transparentPixel, DataBuffer.TYPE_BYTE);

        SampleModel sampleModel = colorModel.createCompatibleSampleModel(sampleImageWidth,
                sampleImageHeight);
        ImageTypeSpecifier its = new ImageTypeSpecifier(colorModel, sampleModel);
        return its;
    }

    private static ImageTypeSpecifier createRGBAImageSpec(int sampleImageWidth,
            int sampleImageHeight, final int dataType) {

        final ImageTypeSpecifier its;

        ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);
        boolean hasAlpha = true;
        boolean isAlphaPremultiplied = false;
        int transparency = Transparency.TRANSLUCENT;
        int transferType = dataType;

        int[] nBits = { 8, 8, 8, 8 };
        ColorModel colorModel = new ComponentColorModelJAI(colorSpace, nBits, hasAlpha,
                isAlphaPremultiplied, transparency, transferType);

        /*
         * Do not use colorModel.createCompatibleSampleModel cause it creates a
         * PixelInterleavedSampleModel and we need a BandedSampleModel so it matches how the data
         * comes out of ArcSDE
         */
        SampleModel sampleModel = new BandedSampleModel(dataType, sampleImageWidth,
                sampleImageHeight, 4);

        its = new ImageTypeSpecifier(colorModel, sampleModel);
        return its;
    }

    private static ImageTypeSpecifier createRGBImageSpec(int sampleImageWidth,
            int sampleImageHeight, final int dataType) {

        final ImageTypeSpecifier its;
        ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);
        boolean hasAlpha = false;
        boolean isAlphaPremultiplied = false;
        int transparency = Transparency.OPAQUE;
        int transferType = dataType;
        ColorModel colorModel = new ComponentColorModel(colorSpace, new int[] { 8, 8, 8 },
                hasAlpha, isAlphaPremultiplied, transparency, transferType);

        SampleModel sampleModel = new BandedSampleModel(dataType, sampleImageWidth,
                sampleImageHeight, 3);

        its = new ImageTypeSpecifier(colorModel, sampleModel);
        return its;
    }

    private static ImageTypeSpecifier createGrayscaleImageSpec(int sampleImageWidth,
            int sampleImageHeight, final int dataType, int bitsPerPixel) {
        final ImageTypeSpecifier its;
        ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY);
        boolean hasAlpha = false;
        boolean isAlphaPremultiplied = false;
        int transparency = Transparency.OPAQUE;
        int transferType = dataType;
        int[] nbits = { bitsPerPixel };
        ColorModel colorModel = new ComponentColorModelJAI(colorSpace, nbits, hasAlpha,
                isAlphaPremultiplied, transparency, transferType);

        SampleModel sampleModel = colorModel.createCompatibleSampleModel(sampleImageWidth,
                sampleImageHeight);
        its = new ImageTypeSpecifier(colorModel, sampleModel);
        return its;
    }

    private static ImageTypeSpecifier createColorMappedImageSpec(final IndexColorModel colorModel,
            int sampleImageWidth, int sampleImageHeight) {

        final SampleModel sampleModel;
        final ImageTypeSpecifier its;
        LOGGER.fine("Found single-band colormapped raster, using its index color model");
        sampleModel = colorModel.createCompatibleSampleModel(sampleImageWidth, sampleImageHeight);
        its = new ImageTypeSpecifier(colorModel, sampleModel);
        return its;

    }

    /**
     * Given a collection of {@link RasterQueryInfo} instances holding information about how a
     * request fits for each individual raster composing a catalog, figure out where their resulting
     * images fit into the overall mosaic that's gonna be the result of the request.
     *
     * @param rasterInfo
     * @param resultEnvelope
     * @param results
     * @return
     */
    public static GridEnvelope setMosaicLocations(final RasterDatasetInfo rasterInfo,
            final List<RasterQueryInfo> results) {
        final MathTransform modelToRaster;
        final MathTransform rasterToModel;
        int minx = Integer.MAX_VALUE;
        int miny = Integer.MAX_VALUE;
        int maxx = Integer.MIN_VALUE;
        int maxy = Integer.MIN_VALUE;
        {
            /*
             * Of all the rasters that match the requested envelope, chose the one with the lowest
             * resolution as the base to compute the final mosaic layout, so we avoid JAI upsamples,
             * which are buggy and produce repeated patterns over the x axis instead of just scaling
             * up the image.
             */
            RasterQueryInfo dimensionChoice = findLowestResolution(results);
            Long rasterId = dimensionChoice.getRasterId();
            int pyramidLevel = dimensionChoice.getPyramidLevel();
            int rasterIndex = rasterInfo.getRasterIndex(rasterId);
            rasterToModel = rasterInfo.getRasterToModel(rasterIndex, pyramidLevel);
            try {
                modelToRaster = rasterToModel.inverse();
            } catch (NoninvertibleTransformException e) {
                throw new RuntimeException(e);
            }
        }

        for (RasterQueryInfo rasterResultInfo : results) {
            final GeneralEnvelope rasterResultEnvelope = rasterResultInfo.getResultEnvelope();

            final GridEnvelope targetRasterGridRange;
            targetRasterGridRange = getTargetGridRange(modelToRaster, rasterResultEnvelope);

            rasterResultInfo.setMosaicLocation(targetRasterGridRange);

            minx = Math.min(minx, targetRasterGridRange.getLow(0));
            miny = Math.min(miny, targetRasterGridRange.getLow(1));
            maxx = Math.max(maxx, targetRasterGridRange.getHigh(0));
            maxy = Math.max(maxy, targetRasterGridRange.getHigh(1));
        }

        final GridEnvelope2D mosaicDimension;
        mosaicDimension = new GridEnvelope2D(minx, miny, 1 + (maxx - minx), 1 + (maxy - miny));
        return mosaicDimension;
    }

    public static RasterQueryInfo findLowestResolution(List<RasterQueryInfo> results) {
        double[] prev = { Double.MIN_VALUE, Double.MIN_VALUE };
        RasterQueryInfo lowestResQuery = null;

        double[] curr;
        for (RasterQueryInfo query : results) {
            curr = query.getResolution();
            if (curr[0] > prev[0]) {
                prev = curr;
                lowestResQuery = query;
            }
        }
        return lowestResQuery;
    }

    /**
     * Find out the raster ids and their pyramid levels in the raster dataset for the rasters whose
     * envelope overlaps the requested one
     *
     * @param rasterInfo
     * @param requestedEnvelope
     * @param requestedDim
     * @param overviewPolicy
     * @return
     */
    public static List<RasterQueryInfo> findMatchingRasters(final RasterDatasetInfo rasterInfo,
            final GeneralEnvelope requestedEnvelope, final GridEnvelope requestedDim,
            final OverviewPolicy overviewPolicy) {

        final int numRasters = rasterInfo.getNumRasters();
        List<RasterQueryInfo> matchingRasters = new ArrayList<RasterQueryInfo>(numRasters);

        int optimalPyramidLevel;
        GeneralEnvelope gridEnvelope;
        for (int rasterN = 0; rasterN < numRasters; rasterN++) {
            optimalPyramidLevel = rasterInfo.getOptimalPyramidLevel(rasterN, overviewPolicy,
                    requestedEnvelope, requestedDim);
            gridEnvelope = rasterInfo.getGridEnvelope(rasterN, optimalPyramidLevel);
            final boolean edgesInclusive = true;
            if (requestedEnvelope.intersects(gridEnvelope, edgesInclusive)) {
                RasterQueryInfo match = new RasterQueryInfo();
                match.setRequestedEnvelope(requestedEnvelope);
                match.setRequestedDim(requestedDim);

                match.setRasterId(rasterInfo.getRasterId(rasterN));
                match.setRasterIndex(rasterN);
                match.setPyramidLevel(optimalPyramidLevel);
                match.setResolution(rasterInfo.getResolution(rasterN, optimalPyramidLevel));
                matchingRasters.add(match);
            }
        }
        return matchingRasters;
    }

    public static void fitRequestToRaster(final GeneralEnvelope requestedEnvelope,
            final RasterDatasetInfo rasterInfo, final RasterQueryInfo query) {

        GridEnvelope resultGridRange;
        GeneralEnvelope resultEnvelope;
        // GridEnvelope resultDimensionInsideTiledImage;
        GridEnvelope tiledImageGridRange;
        // GridEnvelope levelTileRange;
        GridEnvelope matchingTiles;

        int rasterIndex = query.getRasterIndex();
        int pyramidLevel = query.getPyramidLevel();
        MathTransform2D rasterToModel = (MathTransform2D) rasterInfo.getRasterToModel(rasterIndex,
                pyramidLevel);
        MathTransform2D modelToRaster;

        try {
            modelToRaster = (MathTransform2D) rasterToModel.inverse();
            final GeneralEnvelope adjustedGRange = CRS.transform(modelToRaster, requestedEnvelope);

            int xmin = (int) Math.floor(adjustedGRange.getMinimum(0));
            int ymin = (int) Math.floor(adjustedGRange.getMinimum(1));
            int xmax = (int) Math.ceil(adjustedGRange.getMaximum(0));
            int ymax = (int) Math.ceil(adjustedGRange.getMaximum(1));

            final GridEnvelope levelRange = rasterInfo.getGridRange(rasterIndex, pyramidLevel);

            xmin = Math.max(xmin, levelRange.getLow(0));
            ymin = Math.max(ymin, levelRange.getLow(1));
            xmax = Math.min(xmax, levelRange.getHigh(0) + 1);// 1+ because getHigh is inclusive
            ymax = Math.min(ymax, levelRange.getHigh(1) + 1);// 1+ because getHigh is inclusive

            int width = xmax - xmin;
            int height = ymax - ymin;

            resultGridRange = new GridEnvelope2D(xmin, ymin, width, height);
            Rectangle2D finalEnvelope = CRS.transform(rasterToModel, (Rectangle2D) resultGridRange,
                    new Rectangle2D.Double());

            resultEnvelope = new GeneralEnvelope(finalEnvelope);
            CoordinateReferenceSystem crs = rasterInfo.getCoverageCrs();
            resultEnvelope.setCoordinateReferenceSystem(crs);
        } catch (TransformException e) {
            throw new RuntimeException(e);
        }

        matchingTiles = findMatchingTiles(rasterInfo, rasterIndex, pyramidLevel, resultGridRange);
        tiledImageGridRange = getTiledImageGridRange(rasterInfo.getTileDimension(rasterIndex),
                matchingTiles);

        query.setResultEnvelope(resultEnvelope);
        query.setResultGridRange(resultGridRange);
        query.setMatchingTiles(matchingTiles);
        query.setTiledImageGridRange(tiledImageGridRange);

        // query.setResultDimensionInsideTiledImage(resultDimensionInsideTiledImage);
        // query.setLevelTileRange(levelTileRange);
    }

    private static GridEnvelope getTiledImageGridRange(Dimension tileSize,
            GridEnvelope matchingTiles) {

        int tiledImageMinX = (matchingTiles.getLow(0) * tileSize.width);
        int tiledImageMinY = (matchingTiles.getLow(1) * tileSize.height);
        int tiledWidth = (matchingTiles.getSpan(0) * tileSize.width);
        int tiledHeight = (matchingTiles.getSpan(1) * tileSize.height);

        GridEnvelope2D tiledImageGridRange;
        tiledImageGridRange = new GridEnvelope2D(tiledImageMinX, tiledImageMinY, tiledWidth,
                tiledHeight);
        return tiledImageGridRange;
    }

    private static GridEnvelope findMatchingTiles(RasterDatasetInfo rasterInfo, int rasterIndex,
            int pyramidLevel, GridEnvelope resultGridRange) {
        final Dimension tileSize = rasterInfo.getTileDimension(rasterIndex);
        final int numTilesWide = rasterInfo.getNumTilesWide(rasterIndex, pyramidLevel);
        final int numTilesHigh = rasterInfo.getNumTilesHigh(rasterIndex, pyramidLevel);
        GridEnvelope matchingTiles;
        matchingTiles = findMatchingTiles(tileSize, numTilesWide, numTilesHigh, resultGridRange);
        return matchingTiles;
    }

    /**
     * Returns a color model based on {@code colorMap} that's guaranteed to have at least one
     * transparent pixel whose index can be used as no-data value for colormapped rasters, even if
     * the returned IndexColorModel needs to be of a higher sample depth (ie, 16 instead of 8 bit)
     * to satisfy that.
     *
     * @param colorMap
     *            the raster's native color map the returned one will be based on
     * @return the same {@code colorMap} if it has a transparent pixel, another, possibly of a
     *         higher depth one if not, containing all the colors from {@code colorMap} and a newly
     *         allocated cell for the transparent pixel if necessary
     */
    public static IndexColorModel ensureNoDataPixelIsAvailable(final IndexColorModel colorMap) {
        int transparentPixel = colorMap.getTransparentPixel();
        if (transparentPixel > -1) {
            return colorMap;
        }

        final int transferType = colorMap.getTransferType();
        final int mapSize = colorMap.getMapSize();
        final int maxSize = 65536;// true for either transfer type

        if (mapSize == maxSize) {
            LOGGER.fine("There's no room for a new transparent pixel, "
                    + "returning the original colorMap as is");
            return colorMap;
        }

        /*
         * The original map size is lower than the maximum allowed by a UShort color map, so expand
         * the colormap by one and make that new entry transparent
         */
        final int newMapSize = mapSize + 1;
        final int[] argb = new int[newMapSize];
        colorMap.getRGBs(argb);

        // set the last entry as transparent
        argb[newMapSize - 1] = ColorUtilities.getIntFromColor(0, 0, 0, 0);

        IndexColorModel targetColorModel;
        final int significantBits;
        final int newTransferType;

        {
            if (DataBuffer.TYPE_BYTE == transferType && newMapSize <= 256) {
                /*
                 * REVISIT: check if this needs to be promoted depending on whether I decide to
                 * treat 1 and 4 bit images as indexed with 1 and 4 significant bits respectively
                 */
                significantBits = colorMap.getPixelSize();
                newTransferType = DataBuffer.TYPE_BYTE;
            } else if (DataBuffer.TYPE_BYTE == transferType && newMapSize == 257) {
                // it's being promoted. significantBits = 9 makes for a 512 color model instead of a
                // 65535 one saving a good bit of memory, specially for color mapped raster catalogs
                // where the colormodel for each raster in the catalog is to be held in memory
                significantBits = 9;
                newTransferType = DataBuffer.TYPE_USHORT;
            } else {
                // already was 16-bit
                significantBits = 16;
                newTransferType = DataBuffer.TYPE_USHORT;
            }
        }

        final int transparentPixelIndex = newMapSize - 1;
        final boolean hasalpha = true;
        final int startIndex = 0;

        targetColorModel = new IndexColorModel(significantBits, newMapSize, argb, startIndex,
                hasalpha, transparentPixelIndex, newTransferType);

        return targetColorModel;
    }

    /**
     * For a color-mapped raster, the no-data value is set to the
     * {@link IndexColorModel#getTransparentPixel() transparent pixel}
     *
     * @param colorMap
     * @return the index in the colorMap that's the transparent pixel as is to be used as no-data
     *         value
     */
    public static Number determineNoDataValue(IndexColorModel colorMap) {
        int noDataPixel = colorMap.getTransparentPixel();
        if (-1 == noDataPixel) {
            // there were no room for a transparent pixel, find out the closest match
            noDataPixel = ColorUtilities.getTransparentPixel(colorMap);
        }
        return Integer.valueOf(noDataPixel);
    }

    /**
     * @param numBands
     *            number of bands in the raster dataset for the band whose nodata value is to be
     *            determined. Might be useful to treat special cases where some assumptions are made
     *            depending on the cell type and number of bands
     * @param statsMin
     *            the minimum sample value for the band as reported by the band's statistics, or
     *            {@code NaN}
     * @param statsMax
     *            the maximum sample value for the band as reported by the band's statistics, or
     *            {@code NaN}
     * @param nativeCellType
     *            the band's native cell type
     * @return
     */
    public static Number determineNoDataValue(final int numBands, final double statsMin,
            final double statsMax, final RasterCellType nativeCellType) {

        final Number nodata;

        if (nativeCellType == TYPE_32BIT_REAL) {
            LOGGER.fine("no data value is Float.NaN");
            return Float.valueOf(Float.NaN);
        } else if (nativeCellType == TYPE_64BIT_REAL) {
            LOGGER.fine("no data value is Double.NaN");
            return Double.valueOf(Double.NaN);
        } else if (nativeCellType == TYPE_1BIT) {
            LOGGER.fine("1BIT images no-data value is set to 2,"
                    + " regardless of the raster statistics");
            return Double.valueOf(2);
        } else if (nativeCellType == TYPE_4BIT) {
            LOGGER.fine("4BIT images no-data value is set to 16,"
                    + " regardless of the raster statistics");
            return Double.valueOf(16);
        } else if (!isGeoPhysics(numBands, nativeCellType)) {
            LOGGER.fine("3 or 4 band, 8 bit unsigned image, assumed to be "
                    + "RGB or RGBA respectively and nodata value hardcoded to 255");
            return (Number) nativeCellType.getSampleValueRange().getMaxValue();
        }

        final NumberRange<?> sampleValueRange = nativeCellType.getSampleValueRange();

        final double minimumSample = sampleValueRange.getMinimum(true);
        final double maximumSample = sampleValueRange.getMaximum(true);

        double lower;
        double greater;
        if (Double.isNaN(statsMin) || Double.isNaN(statsMax)) {
            lower = Math.ceil(minimumSample - 1);
            greater = Math.floor(maximumSample + 1);
        } else {
            lower = Math.ceil(statsMin - 1);
            greater = Math.floor(statsMax + 1);
        }

        final boolean isUnsigned = minimumSample == 0;

        if (sampleValueRange.contains((Number) Double.valueOf(lower))) {
            // lower is ok
            nodata = lower;
        } else if (sampleValueRange.contains((Number) Double.valueOf(greater))) {
            // upper is ok
            nodata = greater;
        } else if (isUnsigned) {
            // need to set no-data to the higher value, floor is zero
            nodata = greater;
            // if (cellType == TYPE_1BIT || cellType == TYPE_4BIT) {
            // nodata = greater;
            // } else {
            // // best guess without promoting. We don't actually want to promote a raster that is
            // // non
            // // colormapped and either has no statistics or it's range is full to preserve the
            // // cases
            // // were it may affect badly the visualization (for example, a 3 band 8bit raster
            // // promoted to 3 band 16bit is gonna look almost black
            // nodata = maximumSample;
            // }
        } else {
            // no-data as the lower value is ok, floor is non zero (the celltype is signed)
            nodata = lower;
        }

        return nodata;
    }

    public static boolean isGeoPhysics(final int numBands, final RasterCellType nativeCellType) {
        boolean geophysics = true;
        if (nativeCellType == TYPE_8BIT_U && (numBands == 3 || numBands == 4)) {
            geophysics = false;
        }
        return geophysics;
    }

    public static RasterCellType determineTargetCellType(final RasterCellType nativeCellType,
            final List<Number> noDataValues) {

        if (TYPE_32BIT_REAL == nativeCellType || TYPE_64BIT_REAL == nativeCellType) {
            // no data value is NaN, so no need to promote. For other types NaN is not available
            for (Number nodata : noDataValues) {
                if (!Double.isNaN(nodata.doubleValue())) {
                    throw new IllegalArgumentException("no data values for float and "
                            + "double cell types shall be NaN: " + nodata);
                }
            }
            return nativeCellType;
        }

        // find a cell type that's deep enough for all the bands in the given raster
        double noDataMin = Double.POSITIVE_INFINITY, noDataMax = Double.NEGATIVE_INFINITY;
        {
            for (Number noData : noDataValues) {
                noDataMin = Math.min(noDataMin, noData.doubleValue());
                noDataMax = Math.max(noDataMax, noData.doubleValue());
            }
        }
        final NumberRange<Double> sampleValueRange;
        sampleValueRange = nativeCellType.getSampleValueRange().castTo(Double.class);

        final RasterCellType targetCellType;

        if (sampleValueRange.contains((Number) Double.valueOf(noDataMin))
                && sampleValueRange.contains((Number) Double.valueOf(noDataMax))) {
            /*
             * The native cell type can hold the no-data values for all bands in the raster
             */
            targetCellType = nativeCellType;
        } else {
            targetCellType = promote(nativeCellType);
        }
        return targetCellType;
    }

    private static RasterCellType promote(final RasterCellType nativeCellType) {
        switch (nativeCellType) {
        case TYPE_1BIT:
        case TYPE_4BIT:
            return TYPE_8BIT_U;
        case TYPE_8BIT_U:
            return TYPE_16BIT_U;
        case TYPE_8BIT_S:
            return TYPE_16BIT_S;
        case TYPE_16BIT_U:
            return TYPE_32BIT_U;
        case TYPE_16BIT_S:
            return TYPE_32BIT_S;
        case TYPE_32BIT_S:
        case TYPE_32BIT_REAL:
        case TYPE_32BIT_U:
            return TYPE_64BIT_REAL;
        default:
            throw new IllegalArgumentException(
                    "Can't promote a raster of type 64-bit-real, there's "
                            + "no higher pixel depth than that!");
        }
    }
}
TOP

Related Classes of org.geotools.arcsde.raster.info.RasterUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.