All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotools.gce.geotiff.GeoTiffReader Maven / Gradle / Ivy

/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2005-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.
 */
/*
 * NOTICE OF RELEASE TO THE PUBLIC DOMAIN
 *
 * This work was created by employees of the USDA Forest Service's
 * Fire Science Lab for internal use.  It is therefore ineligible for
 * copyright under title 17, section 105 of the United States Code.  You
 * may treat it as you would treat any public domain work: it may be used,
 * changed, copied, or redistributed, with or without permission of the
 * authors, for free or for compensation.  You may not claim exclusive
 * ownership of this code because it is already owned by everyone.  Use this
 * software entirely at your own risk.  No warranty of any kind is given.
 *
 * A copy of 17-USC-105 should have accompanied this distribution in the file
 * 17USC105.html.  If not, you may access the law via the US Government's
 * public websites:
 *   - http://www.copyright.gov/title17/92chap1.html#105
 *   - http://www.gpoaccess.gov/uscode/  (enter "17USC105" in the search box.)
 */
package org.geotools.gce.geotiff;

import it.geosolutions.imageio.core.BasicAuthURI;
import it.geosolutions.imageio.maskband.DatasetLayout;
import it.geosolutions.imageio.utilities.ImageIOUtilities;
import it.geosolutions.imageioimpl.plugins.cog.CogImageInputStreamSpi;
import it.geosolutions.imageioimpl.plugins.cog.CogSourceSPIProvider;
import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReaderSpi;
import it.geosolutions.imageioimpl.plugins.tiff.TiffDatasetLayoutImpl;
import it.geosolutions.jaiext.range.NoDataContainer;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.ColorModel;
import java.awt.image.SampleModel;
import java.awt.image.renderable.ParameterBlock;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.ROI;
import javax.media.jai.RenderedOp;
import org.geotools.coverage.Category;
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.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.coverage.grid.io.GroundControlPoints;
import org.geotools.coverage.grid.io.OverviewPolicy;
import org.geotools.coverage.grid.io.imageio.MaskOverviewProvider;
import org.geotools.coverage.grid.io.imageio.MaskOverviewProvider.MaskInfo;
import org.geotools.coverage.grid.io.imageio.geotiff.GeoTiffIIOMetadataDecoder;
import org.geotools.coverage.grid.io.imageio.geotiff.GeoTiffMetadata2CRSAdapter;
import org.geotools.coverage.grid.io.imageio.geotiff.TiePoint;
import org.geotools.coverage.util.CoverageUtilities;
import org.geotools.data.DataSourceException;
import org.geotools.data.FileGroupProvider.FileGroup;
import org.geotools.data.MapInfoFileReader;
import org.geotools.data.PrjFileReader;
import org.geotools.data.WorldFileReader;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.image.ImageWorker;
import org.geotools.image.io.ImageIOExt;
import org.geotools.image.util.ImageUtilities;
import org.geotools.metadata.i18n.Vocabulary;
import org.geotools.metadata.i18n.VocabularyKeys;
import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.matrix.XAffineTransform;
import org.geotools.referencing.operation.transform.ProjectiveTransform;
import org.geotools.util.NumberRange;
import org.geotools.util.URLs;
import org.geotools.util.Utilities;
import org.geotools.util.factory.Hints;
import org.opengis.coverage.ColorInterpretation;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.geometry.Envelope;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterValue;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;

/**
 * this class is responsible for exposing the data and the Georeferencing metadata available to the
 * Geotools library. This reader is heavily based on the capabilities provided by the ImageIO tools
 * and JAI libraries.
 *
 * @author Bryce Nordgren, USDA Forest Service
 * @author Simone Giannecchini
 * @since 2.1
 */
public class GeoTiffReader extends AbstractGridCoverage2DReader implements GridCoverage2DReader {

    private static final String DEFAULT_COVERAGE_NAME = "geotiff_coverage";

    /** Logger for the {@link GeoTiffReader} class. */
    private Logger LOGGER = org.geotools.util.logging.Logging.getLogger(GeoTiffReader.class);

    /**
     * With this java switch I can control whether or not an external PRJ files takes precedence
     * over the internal CRS definition
     */
    public static final String OVERRIDE_CRS_SWITCH = "org.geotools.gce.geotiff.override.crs";

    /**
     * With this java switch I can control whether or not an external PRJ files takes precedence
     * over the internal CRS definition
     */
    static boolean OVERRIDE_INNER_CRS =
            Boolean.valueOf(System.getProperty(GeoTiffReader.OVERRIDE_CRS_SWITCH, "True"));

    /** SPI for creating tiff readers in ImageIO tools when not using COG */
    private static final TIFFImageReaderSpi TIFF_READER_SPI = new TIFFImageReaderSpi();

    private ImageReaderSpi readerSpi;

    /** Adapter for the GeoTiff crs. */
    private GeoTiffMetadata2CRSAdapter gtcs;

    private double noData = Double.NaN;

    /** File containing image overviews */
    private File ovrSource;

    /** {@link ImageInputStreamSpi} for the file containing external overviews */
    private ImageInputStreamSpi ovrInStreamSPI = null;

    /** Image index of the overviews file */
    private int extOvrImgChoice = -1;

    /** {@link MaskOverviewProvider} instance used for handling internal/external Overviews */
    private MaskOverviewProvider maskOvrProvider;

    /** Boolean indicating if {@link MaskOverviewProvider} is present */
    private boolean hasMaskOvrProvider;

    /** The ground control points, populated if there is no grid to world transformation */
    private GroundControlPoints gcps;

    /**
     * Creates a new instance of GeoTiffReader
     *
     * @param input the GeoTiff file
     */
    public GeoTiffReader(Object input) throws DataSourceException {
        this(input, new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE));
    }

    /**
     * Creates a new instance of GeoTiffReader
     *
     * @param input the GeoTiff file
     * @param uHints user-supplied hints TODO currently are unused
     */
    public GeoTiffReader(Object input, Hints uHints) throws DataSourceException {
        super(input, uHints);

        // /////////////////////////////////////////////////////////////////////
        //
        // Set the source being careful in case it is an URL pointing to a file
        //
        // /////////////////////////////////////////////////////////////////////
        try {
            readerSpi = TIFF_READER_SPI;
            // setting source
            if (input instanceof URL) {
                final URL sourceURL = (URL) input;
                source = URLs.urlToFile(sourceURL);
            }

            closeMe = true;

            // /////////////////////////////////////////////////////////////////////
            //
            // Get a stream in order to read from it for getting the basic
            // information for this coverage
            //
            // /////////////////////////////////////////////////////////////////////
            if ((source instanceof InputStream) || (source instanceof ImageInputStream))
                closeMe = false;
            if (source instanceof CogSourceSPIProvider) {
                CogSourceSPIProvider readerInputObject = (CogSourceSPIProvider) input;
                readerSpi = readerInputObject.getReaderSpi();
                inStreamSPI = readerInputObject.getStreamSpi();
                inStream = readerInputObject.getStream();
            } else if (source instanceof ImageInputStream) inStream = (ImageInputStream) source;
            else {

                inStreamSPI = ImageIOExt.getImageInputStreamSPI(source);
                if (inStreamSPI == null)
                    throw new IllegalArgumentException("No input stream for the provided source");
                inStream =
                        inStreamSPI.createInputStreamInstance(
                                source, ImageIO.getUseCache(), ImageIO.getCacheDirectory());
            }
            if (inStream == null) {
                // Try to figure out what went wrong and provide some info to the user.
                if (source instanceof File) {
                    File f = (File) source;
                    if (!f.exists()) {
                        throw new FileNotFoundException(
                                "File " + f.getAbsolutePath() + " does not exist.");
                    } else if (f.isDirectory()) {
                        throw new IOException("File " + f.getAbsolutePath() + " is a directory.");
                    } else if (!f.canRead()) {
                        throw new IOException("File " + f.getAbsolutePath() + " can not be read.");
                    }
                }

                // If we can't give anything more specific, throw the generic error.
                throw new IllegalArgumentException("No input stream for the provided source");
            }

            // /////////////////////////////////////////////////////////////////////
            //
            // Informations about multiple levels and such
            //
            // /////////////////////////////////////////////////////////////////////
            getHRInfo(this.hints);

            // /////////////////////////////////////////////////////////////////////
            //
            // Coverage name
            //
            // /////////////////////////////////////////////////////////////////////

            coverageName = extractCoverageName();
            final int dotIndex = coverageName.lastIndexOf('.');
            if (dotIndex != -1 && dotIndex != coverageName.length())
                coverageName = coverageName.substring(0, dotIndex);

        } catch (IOException e) {
            throw new DataSourceException(e);
        } finally {
            // /////////////////////////////////////////////////////////////////////
            //
            // Freeing streams
            //
            // /////////////////////////////////////////////////////////////////////
            if (closeMe && inStream != null) //
            try {
                    inStream.close();
                } catch (Throwable t) {
                }
        }
    }

    private String extractCoverageName() {
        if (source instanceof File) {
            return ((File) source).getName();
        } else if (source instanceof CogSourceSPIProvider) {
            BasicAuthURI uri = ((CogSourceSPIProvider) source).getCogUri();
            String path = uri.getUri().getPath();
            int indexOf = path.lastIndexOf("/");
            path = path.substring(indexOf + 1);
            int extensionIndex = path.lastIndexOf(".");
            String name = extensionIndex > 0 ? path.substring(0, extensionIndex) : path;
            return name;
        }
        return DEFAULT_COVERAGE_NAME;
    }

    /** Collect georeferencing information about this geotiff. */
    private void getHRInfo(Hints hints) throws DataSourceException {
        ImageReader reader = null;
        ImageReader ovrReader = null;
        ImageInputStream ovrStream = null;
        try {
            // //
            //
            // Get a reader for this format
            //
            // //
            reader = readerSpi.createReaderInstance();

            // //
            //
            // get the METADATA
            //
            // //
            inStream.mark();
            reader.setInput(inStream);
            final IIOMetadata iioMetadata = reader.getImageMetadata(0);
            final GeoTiffIIOMetadataDecoder metadata = new GeoTiffIIOMetadataDecoder(iioMetadata);
            gtcs = new GeoTiffMetadata2CRSAdapter(hints);

            // //
            //
            // get the CRS INFO
            //
            // //
            final Object tempCRS = this.hints.get(Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM);
            if (tempCRS != null) {
                this.crs = (CoordinateReferenceSystem) tempCRS;
                if (LOGGER.isLoggable(Level.FINE))
                    LOGGER.log(Level.FINE, "Using forced coordinate reference system");
            } else {

                // check external prj first
                if (!ImageIOUtilities.isSkipExternalFilesLookup()) {
                    crs = getCRS(source);
                }

                // now, if we did not want to override the inner CRS or we did not have any external
                // PRJ at hand
                // let's look inside the geotiff
                if (!OVERRIDE_INNER_CRS || crs == null) {
                    if (metadata.hasGeoKey() && gtcs != null) {
                        crs = gtcs.createCoordinateSystem(metadata);
                    }
                }
            }

            //
            // No data
            //
            if (metadata.hasNoData()) {
                noData = metadata.getNoData();
            }

            // collect scales and offsets is present
            collectScaleOffset(iioMetadata);

            //
            // parse and set layout
            //
            setLayout(reader);

            //
            // parse TIFF StreamMetadata
            //
            dtLayout = TiffDatasetLayoutImpl.parseLayout(reader.getStreamMetadata());

            // Creating a new OverviewsProvider instance
            File inputFile = null;
            if (source instanceof File) {
                inputFile = (File) source;
            } else if (source instanceof URL && (((URL) source).getProtocol() == "file")) {
                inputFile = URLs.urlToFile((URL) source);
            }
            if (inputFile != null) {
                maskOvrProvider = new MaskOverviewProvider(dtLayout, inputFile);
                hasMaskOvrProvider = true;
            } else if (dtLayout != null && dtLayout.getExternalMasks() != null) {
                String path = dtLayout.getExternalMasks().getAbsolutePath();
                maskOvrProvider =
                        new MaskOverviewProvider(
                                dtLayout, new File(path.substring(0, path.length() - 4)));
                hasMaskOvrProvider = true;
            } else if (source instanceof CogSourceSPIProvider) {
                CogSourceSPIProvider cogSourceProvider = (CogSourceSPIProvider) source;
                maskOvrProvider =
                        new MaskOverviewProvider(
                                null,
                                cogSourceProvider.getSourceUrl(),
                                new MaskOverviewProvider.SpiHelper(cogSourceProvider),
                                false);
                hasMaskOvrProvider = true;
            }

            // //
            //
            // get the dimension of the hr image and build the model as well as
            // computing the resolution
            // //
            numOverviews =
                    hasMaskOvrProvider
                            ? maskOvrProvider.getNumOverviews()
                            : dtLayout.getNumInternalOverviews();
            int hrWidth = reader.getWidth(0);
            int hrHeight = reader.getHeight(0);
            final Rectangle actualDim = new Rectangle(0, 0, hrWidth, hrHeight);
            originalGridRange = new GridEnvelope2D(actualDim);

            if (gtcs != null
                    && metadata != null
                    && (metadata.hasModelTrasformation()
                            || (metadata.hasPixelScales() && metadata.hasTiePoints()))) {
                this.raster2Model = GeoTiffMetadata2CRSAdapter.getRasterToModel(metadata);
            } else {
                // world file
                this.raster2Model = parseWorldFile(source);

                // now world file --> mapinfo?
                if (raster2Model == null) {
                    MapInfoFileReader mifReader = parseMapInfoFile(source);
                    if (mifReader != null) {
                        raster2Model = mifReader.getTransform();
                        crs = mifReader.getCRS();
                    }
                }
            }

            if (crs == null) {
                if (LOGGER.isLoggable(Level.WARNING)) {
                    LOGGER.warning("Coordinate Reference System is not available");
                }
                crs = AbstractGridFormat.getDefaultCRS();
            }

            if (this.raster2Model == null) {
                TiePoint[] modelTiePoints = metadata.getModelTiePoints();
                if (modelTiePoints != null && modelTiePoints.length > 1) {
                    // use a unit transform and expose the GCPs
                    gcps = new GroundControlPoints(Arrays.asList(modelTiePoints), crs);
                    raster2Model = ProjectiveTransform.create(new AffineTransform());
                    crs = AbstractGridFormat.getDefaultCRS();
                } else {
                    throw new DataSourceException(
                            "Raster to Model Transformation is not available for: "
                                    + getSourceAsFile());
                }
            }

            // create envelope using corner transformation
            final AffineTransform tempTransform =
                    new AffineTransform((AffineTransform) raster2Model);
            tempTransform.concatenate(CoverageUtilities.CENTER_TO_CORNER);
            originalEnvelope =
                    CRS.transform(
                            ProjectiveTransform.create(tempTransform),
                            new GeneralEnvelope(actualDim));
            originalEnvelope.setCoordinateReferenceSystem(crs);

            // ///
            //
            // setting the higher resolution available for this coverage
            //
            // ///
            highestRes = new double[2];
            highestRes[0] = XAffineTransform.getScaleX0(tempTransform);
            highestRes[1] = XAffineTransform.getScaleY0(tempTransform);

            // External Overview management
            if (maskOvrProvider != null) {
                extOvrImgChoice =
                        maskOvrProvider.getNumExternalOverviews() > 0
                                ? maskOvrProvider.getNumInternalOverviews() + 1
                                : -1;
            } else {
                File extOvrFile = dtLayout.getExternalOverviews();
                if (extOvrFile != null && extOvrFile.exists()) {
                    // Setting the overview file
                    ovrSource = extOvrFile;
                    ovrInStreamSPI = ImageIOExt.getImageInputStreamSPI(extOvrFile);

                    ovrReader = TIFF_READER_SPI.createReaderInstance();
                    ovrStream =
                            ovrInStreamSPI.createInputStreamInstance(
                                    extOvrFile, ImageIO.getUseCache(), ImageIO.getCacheDirectory());
                    ovrReader.setInput(ovrStream);
                    // this includes the real image as this is a image index, we need to add one.
                    extOvrImgChoice = numOverviews + 1;
                    numOverviews = numOverviews + dtLayout.getNumExternalOverviews();
                    if (numOverviews < extOvrImgChoice) extOvrImgChoice = -1;
                }
            }

            // //
            //
            // get information for the successive images
            //
            // //
            if (numOverviews >= 1) {
                overViewResolutions = new double[numOverviews][2];
                // Internal overviews start at 1, so lastInternalOverview matches numOverviews if no
                // external.
                int firstExternalOverview =
                        extOvrImgChoice == -1 ? numOverviews : extOvrImgChoice - 1;
                double spanRes0 = highestRes[0] * this.originalGridRange.getSpan(0);
                double spanRes1 = highestRes[1] * this.originalGridRange.getSpan(1);
                if (maskOvrProvider != null) {
                    overViewResolutions =
                            maskOvrProvider.getOverviewResolutions(spanRes0, spanRes1);
                } else {

                    for (int i = 0; i < firstExternalOverview; i++) {
                        // Setting the correct overview index
                        int overviewImageIndex = dtLayout.getInternalOverviewImageIndex(i + 1);
                        int index = overviewImageIndex >= 0 ? overviewImageIndex : 0;
                        overViewResolutions[i][0] = spanRes0 / reader.getWidth(index);
                        overViewResolutions[i][1] = spanRes1 / reader.getHeight(index);
                    }
                    for (int i = firstExternalOverview; i < numOverviews; i++) {
                        overViewResolutions[i][0] =
                                spanRes0 / ovrReader.getWidth(i - firstExternalOverview);
                        overViewResolutions[i][1] =
                                spanRes1 / ovrReader.getHeight(i - firstExternalOverview);
                    }
                }
            } else overViewResolutions = null;
        } catch (Throwable e) {
            throw new DataSourceException(e);
        } finally {
            if (reader != null)
                try {
                    reader.dispose();
                } catch (Throwable t) {
                }

            if (ovrReader != null)
                try {
                    ovrReader.dispose();
                } catch (Throwable t) {
                }

            if (ovrStream != null)
                try {
                    ovrStream.close();
                } catch (Throwable t) {
                }

            if (inStream != null)
                try {
                    inStream.reset();
                } catch (Throwable t) {
                }
        }
    }

    /** @see org.opengis.coverage.grid.GridCoverageReader#getFormat() */
    public Format getFormat() {
        return new GeoTiffFormat();
    }

    /**
     * This method reads in the TIFF image, constructs an appropriate CRS, determines the math
     * transform from raster to the CRS model, and constructs a GridCoverage.
     *
     * @param params currently ignored, potentially may be used for hints.
     * @return grid coverage represented by the image
     * @throws IOException on any IO related troubles
     */
    public GridCoverage2D read(GeneralParameterValue[] params) throws IOException {
        GeneralEnvelope requestedEnvelope = null;
        Rectangle dim = null;
        Color inputTransparentColor = null;
        OverviewPolicy overviewPolicy = null;
        int[] suggestedTileSize = null;
        boolean rescalePixels = AbstractGridFormat.RESCALE_PIXELS.getDefaultValue();

        //
        // Checking params
        //
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                final ParameterValue param = (ParameterValue) params[i];
                final ReferenceIdentifier name = param.getDescriptor().getName();
                if (name.equals(AbstractGridFormat.READ_GRIDGEOMETRY2D.getName())) {
                    final GridGeometry2D gg = (GridGeometry2D) param.getValue();
                    requestedEnvelope = new GeneralEnvelope((Envelope) gg.getEnvelope2D());
                    dim = gg.getGridRange2D().getBounds();
                    continue;
                }
                if (name.equals(AbstractGridFormat.OVERVIEW_POLICY.getName())) {
                    overviewPolicy = (OverviewPolicy) param.getValue();
                    continue;
                }
                if (name.equals(AbstractGridFormat.INPUT_TRANSPARENT_COLOR.getName())) {
                    inputTransparentColor = (Color) param.getValue();
                    continue;
                }
                if (name.equals(AbstractGridFormat.SUGGESTED_TILE_SIZE.getName())) {
                    String suggestedTileSize_ = (String) param.getValue();
                    if (suggestedTileSize_ != null && suggestedTileSize_.length() > 0) {
                        suggestedTileSize_ = suggestedTileSize_.trim();
                        int commaPosition = suggestedTileSize_.indexOf(",");
                        if (commaPosition < 0) {
                            int tileDim = Integer.parseInt(suggestedTileSize_);
                            suggestedTileSize = new int[] {tileDim, tileDim};
                        } else {
                            int tileW =
                                    Integer.parseInt(
                                            suggestedTileSize_.substring(0, commaPosition));
                            int tileH =
                                    Integer.parseInt(
                                            suggestedTileSize_.substring(commaPosition + 1));
                            suggestedTileSize = new int[] {tileW, tileH};
                        }
                    }
                    continue;
                }
                if (name.equals(AbstractGridFormat.RESCALE_PIXELS.getName())) {
                    rescalePixels = Boolean.TRUE.equals(param.getValue());
                }
            }
        }

        //
        // set params
        //
        Integer imageChoice = Integer.valueOf(0);
        final ImageReadParam readP = new ImageReadParam();
        try {
            imageChoice = setReadParams(overviewPolicy, readP, requestedEnvelope, dim);
        } catch (TransformException e) {
            throw new DataSourceException(e);
        }

        //
        // IMAGE READ OPERATION
        //
        Hints newHints = null;
        if (suggestedTileSize != null) {
            newHints = hints.clone();
            final ImageLayout layout = new ImageLayout();
            layout.setTileGridXOffset(0);
            layout.setTileGridYOffset(0);
            layout.setTileHeight(suggestedTileSize[1]);
            layout.setTileWidth(suggestedTileSize[0]);
            newHints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout));
        }
        final ParameterBlock pbjRead = new ParameterBlock();
        // Image Index used for the Overview management
        if (maskOvrProvider != null) {
            if (maskOvrProvider.isExternalOverview(imageChoice)) {
                pbjRead.add(
                        maskOvrProvider
                                .getSourceSpiProvider()
                                .getCompatibleSourceProvider(maskOvrProvider.getOvrURL())
                                .getStream());
            } else {
                pbjRead.add(maskOvrProvider.getSourceSpiProvider().getStream());
            }
            pbjRead.add(maskOvrProvider.getOverviewIndex(imageChoice));
        } else {
            if (extOvrImgChoice >= 0 && imageChoice >= extOvrImgChoice) {
                pbjRead.add(
                        ovrInStreamSPI.createInputStreamInstance(
                                ovrSource, ImageIO.getUseCache(), ImageIO.getCacheDirectory()));
                pbjRead.add(imageChoice - extOvrImgChoice);
            } else {
                pbjRead.add(getImageInputStream());
                // Setting correct ImageChoice (taking into account overviews and masks)
                int overviewImageIndex = dtLayout.getInternalOverviewImageIndex(imageChoice);
                int index = overviewImageIndex >= 0 ? overviewImageIndex : 0;
                pbjRead.add(index);
            }
        }
        pbjRead.add(Boolean.FALSE);
        pbjRead.add(Boolean.FALSE);
        pbjRead.add(Boolean.FALSE);
        pbjRead.add(null);
        pbjRead.add(null);
        pbjRead.add(readP);
        pbjRead.add(readerSpi.createReaderInstance());
        PlanarImage coverageRaster =
                JAI.create("ImageRead", pbjRead, newHints != null ? newHints : null);

        // applying rescale if needed
        if (rescalePixels) {
            if (!Double.isNaN(noData)) {
                // Force nodata settings since JAI ImageRead may lost that
                // We have to make sure that noData pixels won't be rescaled
                PlanarImage t = PlanarImage.wrapRenderedImage(coverageRaster);
                t.setProperty(NoDataContainer.GC_NODATA, new NoDataContainer(noData));
                coverageRaster = t;
            }
            coverageRaster =
                    PlanarImage.wrapRenderedImage(
                            ImageUtilities.applyRescaling(
                                    scales, offsets, coverageRaster, newHints));
        }

        //
        // MASKING INPUT COLOR as indicated
        //
        if (inputTransparentColor != null) {
            coverageRaster =
                    new ImageWorker(coverageRaster)
                            .setRenderingHints(newHints)
                            .makeColorTransparent(inputTransparentColor)
                            .getRenderedOperation();
        }

        //
        // External/Internal Masking
        //
        // ROI definition
        ROI roi = null;
        // Using MaskOvrProvider
        if (hasMaskOvrProvider) {
            // Parameter definition
            GridEnvelope ogr = getOriginalGridRange();
            Rectangle sourceRegion;
            if (readP.getSourceRegion() != null) {
                sourceRegion = readP.getSourceRegion();
            } else {
                sourceRegion = new Rectangle(ogr.getSpan(0), ogr.getSpan(1));
            }

            MaskInfo info = maskOvrProvider.getMaskInfo(imageChoice, sourceRegion, readP);
            if (info != null) {
                // Reading Mask
                RenderedOp roiRaster =
                        readROIRaster(
                                info.streamSpi,
                                URLs.fileToUrl(info.file),
                                info.index,
                                newHints,
                                info.readParameters);
                roi = MaskOverviewProvider.scaleROI(roiRaster, coverageRaster.getBounds());
            }
        }

        //
        // BUILDING COVERAGE
        //
        AffineTransform rasterToModel = getRescaledRasterToModel(coverageRaster);
        try {
            return createCoverage(coverageRaster, ProjectiveTransform.create(rasterToModel), roi);
        } catch (Exception e) {
            // dispose and close file
            ImageUtilities.disposePlanarImageChain(coverageRaster);

            // rethrow
            if (e instanceof DataSourceException) {
                throw (DataSourceException) e;
            }
            throw new DataSourceException(e);
        }
    }

    private ImageInputStream getImageInputStream() throws IOException {
        if (inStream instanceof ImageInputStream && !closeMe) {
            return inStream;
        } else if (inStreamSPI == null) {
            return ImageIO.createImageInputStream(source);
        } else if (inStreamSPI instanceof CogImageInputStreamSpi) {
            return ((CogSourceSPIProvider) source).getStream();
        } else {
            return inStreamSPI.createInputStreamInstance(
                    source, ImageIO.getUseCache(), ImageIO.getCacheDirectory());
        }
    }

    /**
     * General method for reading an input ROI Mask from a file
     *
     * @return A {@link RenderedOp} representing the Raster ROI
     */
    private RenderedOp readROIRaster(
            ImageInputStreamSpi spi,
            URL inFile,
            int index,
            RenderingHints newHints,
            ImageReadParam readP) {
        // Raster initialization
        RenderedOp raster = null;
        try {
            // ParameterBlock creation
            ParameterBlock pb = new ParameterBlock();
            pb.add(
                    spi.createInputStreamInstance(
                            inFile, ImageIO.getUseCache(), ImageIO.getCacheDirectory()));
            pb.add(index);
            pb.add(Boolean.FALSE);
            pb.add(Boolean.FALSE);
            pb.add(Boolean.FALSE);
            pb.add(null);
            pb.add(null);
            pb.add(readP);
            pb.add(readerSpi.createReaderInstance());
            raster =
                    JAI.create(
                            "ImageRead", pb, newHints != null ? (RenderingHints) newHints : null);
        } catch (Exception e) {
            LOGGER.severe("Unable to read input Mask Band for coverage: " + coverageName);
        }

        return raster;
    }

    /**
     * Returns the geotiff metadata for this geotiff file.
     *
     * @return the metadata
     */
    @SuppressWarnings("PMD.CloseResource") // conditional, might have to close the stream, or not
    public GeoTiffIIOMetadataDecoder getMetadata() {
        GeoTiffIIOMetadataDecoder metadata = null;
        ImageReader reader = null;
        boolean closeMe = true;
        ImageInputStream stream = null;

        try {
            if ((source instanceof InputStream) || (source instanceof ImageInputStream)) {
                closeMe = false;
            }
            if (source instanceof ImageInputStream) {
                stream = (ImageInputStream) source;
            } else {
                inStreamSPI = ImageIOExt.getImageInputStreamSPI(source);
                if (inStreamSPI == null) {
                    throw new IllegalArgumentException("No input stream for the provided source");
                }
                stream =
                        inStreamSPI.createInputStreamInstance(
                                source, ImageIO.getUseCache(), ImageIO.getCacheDirectory());
            }
            if (stream == null) {
                throw new IllegalArgumentException("No input stream for the provided source");
            }
            stream.mark();
            reader = readerSpi.createReaderInstance();
            reader.setInput(stream);
            final IIOMetadata iioMetadata = reader.getImageMetadata(0);
            metadata = new GeoTiffIIOMetadataDecoder(iioMetadata);
        } catch (IOException e) {
            if (LOGGER.isLoggable(Level.SEVERE)) {
                LOGGER.log(Level.SEVERE, e.getMessage(), e);
            }
        } finally {
            if (reader != null)
                try {
                    reader.dispose();
                } catch (Throwable t) {
                }

            if (stream != null) {
                try {
                    stream.reset();
                } catch (Throwable t) {
                }
                if (closeMe) {
                    try {
                        stream.close();
                    } catch (Throwable t) {
                    }
                }
            }
        }
        return metadata;
    }

    /**
     * Creates a {@link GridCoverage} for the provided {@link PlanarImage} using the {@link
     * #raster2Model} that was provided for this coverage.
     *
     * 

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. * @param roi Optional ROI used as Mask * @return a {@link GridCoverage} */ protected final GridCoverage2D createCoverage( PlanarImage image, MathTransform raster2Model, ROI roi) throws IOException { // creating bands final SampleModel sm = image.getSampleModel(); final ColorModel cm = image.getColorModel(); final int numBands = sm.getNumBands(); final GridSampleDimension[] bands = new GridSampleDimension[numBands]; // setting bands names. Category noDataCategory = null; final Map properties = new HashMap(); if (!Double.isNaN(noData)) { noDataCategory = new Category( Vocabulary.formatInternational(VocabularyKeys.NODATA), new Color[] {new Color(0, 0, 0, 0)}, NumberRange.create(noData, noData)); CoverageUtilities.setNoDataProperty(properties, Double.valueOf(noData)); image.setProperty(NoDataContainer.GC_NODATA, new NoDataContainer(noData)); } // Setting ROI Property if (roi != null) { image.setProperty("ROI", roi); CoverageUtilities.setROIProperty(properties, roi); } Set bandNames = new HashSet(); for (int i = 0; i < numBands; i++) { final ColorInterpretation colorInterpretation = TypeMap.getColorInterpretation(cm, i); if (colorInterpretation == null) throw new IOException("Unrecognized sample dimension type"); Category[] categories = null; if (noDataCategory != null) { categories = new Category[] {noDataCategory}; } String bandName = colorInterpretation.name(); // make sure we create no duplicate band names if (colorInterpretation == ColorInterpretation.UNDEFINED || bandNames.contains(bandName)) { bandName = "Band" + (i + 1); } bands[i] = new GridSampleDimension(bandName, categories, null); } // creating coverage if (raster2Model != null) { return coverageFactory.create( coverageName, image, crs, raster2Model, bands, null, properties); } return coverageFactory.create( coverageName, image, new GeneralEnvelope(originalEnvelope), bands, null, properties); } private CoordinateReferenceSystem getCRS(Object source) { CoordinateReferenceSystem crs = null; if (source instanceof File || (source instanceof URL && (((URL) source).getProtocol() == "file"))) { // getting name for the prj file final String sourceAsString; if (source instanceof File) { sourceAsString = ((File) source).getAbsolutePath(); } else { String auth = ((URL) source).getAuthority(); String path = ((URL) source).getPath(); if (auth != null && !auth.equals("")) { sourceAsString = "//" + auth + path; } else { sourceAsString = path; } } final int index = sourceAsString.lastIndexOf("."); final String base = index > 0 ? sourceAsString.substring(0, index) + ".prj" : sourceAsString + ".prj"; // does it exist? final File prjFile = new File(base.toString()); if (prjFile.exists()) { // it exists then we have top read it PrjFileReader projReader = null; try (FileInputStream instream = new FileInputStream(prjFile); FileChannel channel = instream.getChannel()) { projReader = new PrjFileReader(channel); crs = projReader.getCoordinateReferenceSystem(); } catch (FileNotFoundException e) { // warn about the error but proceed, it is not fatal // we have at least the default crs to use LOGGER.log(Level.INFO, e.getLocalizedMessage(), e); } catch (IOException e) { // warn about the error but proceed, it is not fatal // we have at least the default crs to use LOGGER.log(Level.INFO, e.getLocalizedMessage(), e); } catch (FactoryException e) { // warn about the error but proceed, it is not fatal // we have at least the default crs to use LOGGER.log(Level.INFO, e.getLocalizedMessage(), e); } finally { if (projReader != null) try { projReader.close(); } catch (IOException e) { // warn about the error but proceed, it is not fatal // we have at least the default crs to use LOGGER.log(Level.FINE, e.getLocalizedMessage(), e); } } } } return crs; } /** @throws IOException */ static MathTransform parseWorldFile(Object source) throws IOException { MathTransform raster2Model = null; // TODO: Add support for FileImageInputStreamExt // TODO: Check for WorldFile on URL beside the actual connection. if (source instanceof File) { final File sourceFile = ((File) source); String parentPath = sourceFile.getParent(); String filename = sourceFile.getName(); final int i = filename.lastIndexOf('.'); filename = (i == -1) ? filename : filename.substring(0, i); // getting name and extension final String base = (parentPath != null) ? new StringBuilder(parentPath) .append(File.separator) .append(filename) .toString() : filename; // We can now construct the baseURL from this string. File file2Parse = new File(new StringBuilder(base).append(".wld").toString()); if (file2Parse.exists()) { final WorldFileReader reader = new WorldFileReader(file2Parse); raster2Model = reader.getTransform(); } else { // looking for another extension file2Parse = new File(new StringBuilder(base).append(".tfw").toString()); if (file2Parse.exists()) { // parse world file final WorldFileReader reader = new WorldFileReader(file2Parse); raster2Model = reader.getTransform(); } } } return raster2Model; } /** @throws IOException */ static MapInfoFileReader parseMapInfoFile(Object source) throws IOException { if (source instanceof File) { final File sourceFile = ((File) source); File file2Parse = getSibling(sourceFile, ".tab"); if (file2Parse != null && file2Parse.exists()) { final MapInfoFileReader reader = new MapInfoFileReader(file2Parse); return reader; } } return null; } @Override protected boolean checkName( String coverageName) { // GEOS-6327 - tolerate geotiff_coverage as coverageName if ("geotiff_coverage".equalsIgnoreCase(coverageName)) { return true; } else { Utilities.ensureNonNull("coverageName", coverageName); return coverageName.equalsIgnoreCase(this.coverageName); } } /** * Number of coverages for this reader is 1 * * @return the number of coverages for this reader. */ @Override public int getGridCoverageCount() { return 1; } @Override public GroundControlPoints getGroundControlPoints() { return gcps; } @Override protected List getFiles() { File file = getSourceAsFile(); if (file == null) { return null; } List files = new ArrayList<>(); // add all common sidecars addAllSiblings(file, files, ".prj", ".tab", ".wld", ".tfw"); if (hasMaskOvrProvider) { DatasetLayout layout = maskOvrProvider.getLayout(); addSiblings( files, layout.getExternalMaskOverviews(), layout.getExternalOverviews(), layout.getExternalMasks()); } return Collections.singletonList(new FileGroup(file, files, null)); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy