/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-2008, 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.coverage.io; import it.geosolutions.imageio.stream.input.FileImageInputStreamExtImpl; import java.awt.Rectangle; import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.awt.image.renderable.ParameterBlock; import java.io.File; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; import javax.imageio.spi.ImageReaderSpi; import javax.measure.unit.Unit; import javax.media.jai.IHSColorSpace; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import org.geotools.coverage.Category; import org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.factory.Hints; import org.geotools.geometry.GeneralEnvelope; import org.geotools.referencing.operation.transform.ConcatenatedTransform; import org.geotools.referencing.operation.transform.ProjectiveTransform; import org.geotools.util.NumberRange; import org.opengis.coverage.grid.GridCoverage; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; /** * A BaseCoverageResponse. An instance of this class is produced everytime a * requestCoverage is called to a reader. * * @author Daniele Romagnoli, GeoSolutions */ class BaseCoverageResponse { /** Logger. */ private final static Logger LOGGER = org.geotools.util.logging.Logging .getLogger("org.geotools.imageio"); /** * The GridCoverage produced after a {@link #compute()} method call */ protected GridCoverage gridCoverage; /** The {@link BaseCoverageRequest} originating this response */ protected BaseCoverageRequest originatingCoverageRequest; /** The readerSPI to be used for data read operations */ private ImageReaderSpi readerSpi; /** The coverage factory producing a {@link GridCoverage} from an image */ private GridCoverageFactory coverageFactory; /** The hints to be used to produce this coverage */ private Hints hints; // //////////////////////////////////////////////////////////////////////// // // Information obtained by the coverageRequest instance // // //////////////////////////////////////////////////////////////////////// /** The coverage grid to world transformation */ private MathTransform raster2Model; /** The base envelope related to the input coverage */ private GeneralEnvelope coverageEnvelope; /** The CRS of the input coverage */ private CoordinateReferenceSystem coverageCRS; /** The name of the input coverage */ private String coverageName; private int imageIndex; private double[] scalingParameters; private double[] validRange; private double[] noDataValues; private Unit unit = null; private String longName; /** * Construct a {@code BaseCoverageResponse} given a specific * {@link BaseCoverageRequest}, a {@code GridCoverageFactory} to produce * {@code GridCoverage}s and an {@code ImageReaderSpi} to be used for * instantiating an Image Reader for a read operation, * * @param request * a {@link BaseCoverageRequest} originating this response. * @param coverageFactory * a {@code GridCoverageFactory} to produce a * {@code GridCoverage} when calling the {@link #compute()} * method. * @param readerSpi * the Image Reader Service provider interface. */ public BaseCoverageResponse(BaseCoverageRequest request, GridCoverageFactory coverageFactory, ImageReaderSpi readerSpi) { originatingCoverageRequest = request; hints = request.getHints(); coverageEnvelope = request.getCoverageEnvelope(); coverageCRS = request.getCoverageCRS(); raster2Model = request.getRaster2Model(); imageIndex = request.getImageIndex(); scalingParameters = request.getScaleAndOffset(); validRange = request.getValidRange(); noDataValues = request.getNoDataValues(); longName = request.getLongName(); unit = request.getUnit(); this.coverageName = request.getCoverageName(); this.coverageFactory = coverageFactory; this.readerSpi = readerSpi; } /** * @return the {@link GridCoverage} produced as computation of this response * using the {@link #compute()} method. * @uml.property name="gridCoverage" */ public GridCoverage getGridCoverage() { return gridCoverage; } /** * @return the {@link BaseCoverageRequest} originating this response. * * @uml.property name="originatingCoverageRequest" */ public BaseCoverageRequest getOriginatingCoverageRequest() { return originatingCoverageRequest; } /** * Compute the coverage request and produce a grid coverage which will be * returned by {@link #getGridCoverage()}. The produced grid coverage may * be {@code null} in case of empty request. * * @throws IOException */ public void compute() throws IOException { originatingCoverageRequest.prepare(); boolean isEmptyRequest = originatingCoverageRequest.isEmptyRequest(); if (isEmptyRequest) gridCoverage = null; else { ImageReadParam imageReadParam = originatingCoverageRequest .getImageReadParam(); File input = originatingCoverageRequest.getInput(); boolean useMultithreading = originatingCoverageRequest .useMultithreading(); boolean newTransform = originatingCoverageRequest .needTransformation(); boolean useJAI = originatingCoverageRequest.useJAI(); gridCoverage = createCoverage(input, imageReadParam, useJAI, useMultithreading, newTransform); } } /** * This method creates the GridCoverage2D from the underlying file given a * specified envelope, and a requested dimension. * * @param iUseJAI * specify if the underlying read process should leverage on * a JAI ImageRead operation or a simple direct call to the * {@code read} method of a proper {@code ImageReader}. * @param useMultithreading * specify if the underlying read process should use * multithreading when a JAI ImageRead operation is requested * @param overviewPolicy * the overview policy which need to be adopted * @return a {@code GridCoverage} * * @throws java.io.IOException */ private GridCoverage createCoverage(File input, ImageReadParam imageReadParam, final boolean useJAI, final boolean useMultithreading, final boolean newTransform) throws IOException { // //////////////////////////////////////////////////////////////////// // // Doing an image read for reading the coverage. // // //////////////////////////////////////////////////////////////////// final PlanarImage image = readRaster(input, useJAI, imageReadParam, useMultithreading); // ///////////////////////////////////////////////////////////////////// // // Creating the coverage // // ///////////////////////////////////////////////////////////////////// if (newTransform) { // I need to calculate a new transformation (raster2Model) // between the cropped image and the required envelope final int ssWidth = image.getWidth(); final int ssHeight = image.getHeight(); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Coverage read: width = " + ssWidth + " height = " + ssHeight); } // // // // setting new coefficients to define a new affineTransformation // to be applied to the grid to world transformation // ------------------------------------------------------ // // With respect to the original envelope, the obtained // planarImage needs to be rescaled and translated. The scaling // factors are computed as the ratio between the cropped source // region sizes and the read image sizes. The translate // settings are represented by the offsets of the source region. // // // final Rectangle sourceRegion = imageReadParam.getSourceRegion(); final double scaleX = sourceRegion.width / (1.0 * ssWidth); final double scaleY = sourceRegion.height / (1.0 * ssHeight); final double translateX = sourceRegion.x; final double translateY = sourceRegion.y; return createCoverageFromImage(image, ConcatenatedTransform.create( ProjectiveTransform.create(new AffineTransform(scaleX, 0, 0, scaleY, translateX, translateY)), raster2Model)); } else { // In case of no transformation is required (As an instance, // when reading the whole image) return createCoverageFromImage(image); } } /** * Creates a {@link GridCoverage} for the provided {@link PlanarImage} using * the {@link #raster2Model} that was provided for this coverage. * * <p> * This method is vital when working with coverages that have a raster to * model transformation that is not a simple scale and translate. * * @param image * contains the data for the coverage to create. * @param raster2Model * is the {@link MathTransform} that maps from the raster * space to the model space. * @return a {@link GridCoverage} * @throws IOException */ protected GridCoverage createCoverageFromImage(PlanarImage image, MathTransform raster2Model) throws IOException { // creating bands final int numBands = image.getSampleModel().getNumBands(); final GridSampleDimension[] bands = new GridSampleDimension[numBands]; // checking the names final ColorModel cm = image.getColorModel(); final String[] names = new String[numBands]; // in case of index color model we are already done. if (cm instanceof IndexColorModel) { names[0] = "index band"; } else { // in case of multiband image we are not done yet. final ColorSpace cs = cm.getColorSpace(); if (cs instanceof IHSColorSpace) { names[0] = "Intensity band"; names[1] = "Hue band"; names[2] = "Saturation band"; } else { // not IHS, let's take the type final int type = cs.getType(); switch (type) { case ColorSpace.CS_GRAY: case ColorSpace.TYPE_GRAY: names[0] = "GRAY"; break; case ColorSpace.CS_sRGB: case ColorSpace.CS_LINEAR_RGB: case ColorSpace.TYPE_RGB: names[0] = "RED"; names[1] = "GREEN"; names[2] = "BLUE"; break; case ColorSpace.TYPE_CMY: names[0] = "CYAN"; names[1] = "MAGENTA"; names[2] = "YELLOW"; break; case ColorSpace.TYPE_CMYK: names[0] = "CYAN"; names[1] = "MAGENTA"; names[2] = "YELLOW"; names[3] = "K"; break; } } } Unit unit = null; if (this.unit != null) unit = this.unit; // setting bands names. for (int i = 0; i < numBands; i++) { Category[] categories = null; Category values = null; Category nan = null; int cat = 0; // // TODO // // Actually, the underlying readers use fillValue as noData // // Is this correct? // if (noDataValues != null) { // final int size = noDataValues.length; // if (size == 1) { // final double noData = noDataValues[0]; // if (!Double.isNaN(noData)) { // nan = new Category(Vocabulary // .formatInternational(VocabularyKeys.NODATA), // new Color[] { new Color(0, 0, 0, 0) }, // NumberRange.create(0, 0), NumberRange.create( // noData, noData)); // } // } // // TODO: Handle more nodatavalues // cat++; // } if (validRange != null) { double min = validRange[0]; double max = validRange[1]; // TODO: Workaround to handle fillValue = valid min if (nan != null) { if (noDataValues[0] == validRange[0]) min += Double.MIN_VALUE; } values = new Category("values", null, NumberRange.create(min, max), scalingParameters[0], scalingParameters[1]); cat++; } if (cat > 0) { categories = new Category[cat]; if (cat == 2) { categories[0] = nan; categories[1] = values; } else categories[0] = nan == null ? values :nan ; } // categories[0] = values; // if (nan != null) // categories[1] = nan; // // final GridSampleDimension band = new // GridSampleDimension(longName, // categories, unit); // final GridSampleDimension band = new GridSampleDimension(longName, // categories, unit); final GridSampleDimension band = new GridSampleDimension(names[i], categories, unit); bands[i] = band; } // creating coverage if (raster2Model != null) { return coverageFactory.create(coverageName, image, coverageCRS, raster2Model, bands, null, null); } return coverageFactory.create(coverageName, image, new GeneralEnvelope( coverageEnvelope), bands, null, null); } /** * Creates a {@link GridCoverage} for the provided {@link PlanarImage} using * the {@link #coverageEnvelope} that was provided for this coverage. * * @param image * contains the data for the coverage to create. * @return a {@link GridCoverage} * @throws IOException */ protected final GridCoverage createCoverageFromImage(PlanarImage image) throws IOException { return createCoverageFromImage(image, null); } /** * Returns a {@code PlanarImage} given a set of parameter specifying the * type of read operation to be performed. * * @param new * FileImageInputStreamExtImplinput the input * {@code ImageInputStream} to be used for reading the image. * @param useJAI * {@code true} if we need to use a JAI ImageRead operation, * {@code false} if we need a simple direct * {@code ImageReader.read(...)} call. * @param imageReadParam * an {@code ImageReadParam} specifying the read parameters * @param useMultithreading * {@code true} if a JAI ImageRead operation is requested * with support for multithreading. This parameter will be * ignored if requesting a direct read operation. * @return the read {@code PlanarImage} * @throws IOException */ protected PlanarImage readRaster(final File input, final boolean useJAI, final ImageReadParam imageReadParam, final boolean useMultithreading) throws IOException { PlanarImage raster; ImageReader reader; if (useJAI) { final ParameterBlock pbjImageRead = new ParameterBlock(); pbjImageRead.add(new FileImageInputStreamExtImpl(input)); pbjImageRead.add(imageIndex); pbjImageRead.add(Boolean.FALSE); pbjImageRead.add(Boolean.FALSE); pbjImageRead.add(Boolean.FALSE); pbjImageRead.add(null); pbjImageRead.add(null); pbjImageRead.add(imageReadParam); reader = readerSpi.createReaderInstance(); pbjImageRead.add(reader); // Check if to use a simple JAI ImageRead operation or a // multithreaded one final String jaiOperation = useMultithreading ? "ImageReadMT" : "ImageRead"; raster = JAI.create(jaiOperation, pbjImageRead, hints); } else { reader = readerSpi.createReaderInstance(); reader.setInput(new FileImageInputStreamExtImpl(input), true, true); raster = PlanarImage.wrapRenderedImage(reader.read(imageIndex, imageReadParam)); reader.dispose(); } return raster; } }