Search in sources :

Example 1 with LargeRenderedImage

use of org.geotoolkit.image.io.large.LargeRenderedImage in project geotoolkit by Geomatys.

the class ImageCoverageReader method read.

/**
 * Converts geodetic parameters to image parameters, reads the image and wraps it in a
 * grid coverage. First, this method creates an initially empty block of image parameters
 * by invoking the {@link #createImageReadParam(int)} method. The image parameter
 * {@linkplain ImageReadParam#setSourceRegion source region},
 * {@linkplain ImageReadParam#setSourceSubsampling source subsampling} and
 * {@linkplain ImageReadParam#setSourceBands source bands} are computed from the
 * parameter given to this {@code read} method. Then, the following image parameters
 * are set (if the image parameter class allows such settings):
 * <p>
 * <ul>
 *   <li><code>{@linkplain SpatialImageReadParam#setSampleConversionAllowed
 *       setSampleConversionAllowed}({@linkplain SampleConversionType#REPLACE_FILL_VALUES
 *       REPLACE_FILL_VALUES}, true)</code> in order to allow the replacement of
 *       fill values by {@link Float#NaN NaN}.</li>
 *
 *   <li><code>{@linkplain SpatialImageReadParam#setSampleConversionAllowed
 *       setSampleConversionAllowed}({@linkplain SampleConversionType#SHIFT_SIGNED_INTEGERS
 *       SHIFT_SIGNED_INTEGERS}, true)</code> if the sample dimensions declare an unsigned
 *       range of sample values.</li>
 *
 *   <li><code>{@linkplain MosaicImageReadParam#setSubsamplingChangeAllowed
 *       setSubsamplingChangeAllowed}(true)</code> in order to allow {@link MosaicImageReader}
 *       to use a different resolution than the requested one. This is crucial from a
 *       performance point of view. Since the {@code GridCoverageReader} contract does not
 *       guarantee that the grid geometry of the returned coverage is the requested geometry,
 *       we are allowed to do that.</li>
 * </ul>
 * <p>
 * Finally, the image is read and wrapped in a {@link GridCoverage2D} using the
 * information provided by {@link #getGridGeometry(int)} and {@link #getSampleDimensions(int)}.
 *
 * /!\ If {@link org.geotoolkit.coverage.io.GridCoverageReadParam#setDeferred(boolean)} parameter is set to true, the
 * returned coverage will rely on the current reader to cache it's data on the fly, so you CANNOT dispose of the current
 * reader while your using the resulting coverage.
 *
 * @param  param Optional parameters used to control the reading process, or {@code null}.
 * @return The {@link GridCoverage} at the specified index.
 * @throws IllegalStateException if the input source has not been set.
 * @throws IndexOutOfBoundsException if the supplied index is out of bounds.
 * @throws CoverageStoreException If an error occurs while reading the information from the input source.
 * @throws CancellationException If {@link #abort()} has been invoked in an other thread during
 *         the execution of this method.
 *
 * @see ImageReader#read(int)
 */
public GridCoverage2D read(final GridCoverageReadParam param) throws DataStoreException, CancellationException {
    final int index = 0;
    final boolean loggingEnabled = isLoggable();
    long fullTime = (loggingEnabled) ? System.nanoTime() : 0;
    ignoreGridTransforms = !loggingEnabled;
    /*
         * Parameters check.
         */
    abortRequested = false;
    // Protect from changes.
    final ImageReader imageReader = this.imageReader;
    if (imageReader == null) {
        throw new IllegalStateException(formatErrorMessage(Errors.Keys.NoImageInput));
    }
    GridGeometry gridGeometry = getGridGeometry();
    final int gridDimensionX, gridDimensionY;
    if (gridGeometry.getDimension() == 2) {
        gridDimensionX = 0;
        gridDimensionY = 1;
    } else if (gridGeometry.isDefined(GridGeometry.EXTENT)) {
        final int[] subSpace = gridGeometry.getExtent().getSubspaceDimensions(2);
        gridDimensionX = subSpace[0];
        gridDimensionY = subSpace[1];
    } else
        throw new DataStoreException("Cannot determine X and Y axis of the coverage");
    checkAbortState();
    final ImageReadParam imageParam;
    try {
        imageParam = createImageReadParam(0);
    } catch (IOException e) {
        throw new CoverageStoreException(formatErrorMessage(e), e);
    }
    final int[] srcBands;
    final int[] dstBands;
    MathTransform2D destToExtractedGrid = null;
    boolean supportBandSelection = true;
    if (param != null) {
        srcBands = param.getSourceBands();
        dstBands = param.getDestinationBands();
        if (srcBands != null && dstBands != null && srcBands.length != dstBands.length) {
            throw new IllegalArgumentException(Errors.getResources(locale).getString(Errors.Keys.MismatchedArrayLength_2, "sourceBands", "destinationBands"));
        }
        /*
             * Convert geodetic envelope and resolution to pixel coordinates.
             * Store the result of the above conversions in the ImageReadParam object.
             */
        destToExtractedGrid = geodeticToPixelCoordinates(gridGeometry, param, imageParam, false);
        /*
             * Conceptually we could compute right now:
             *
             *     AffineTransform change = new AffineTransform();
             *     change.translate(sourceRegion.x, sourceRegion.y);
             *     change.scale(xSubsampling, ySubsampling);
             *
             * However this implementation will scale only after the image has been read,
             * because the MosaicImageReader may have changed the subsampling to more
             * efficient values if it was authorized to make such change.
             */
        if (srcBands != null) {
            // -- particulaity case for tiff image reader which does not support setbands indexes.
            try {
                imageParam.setSourceBands(srcBands);
            } catch (UnsupportedOperationException ex) {
                LOGGER.log(Level.WARNING, ex.getMessage() + "Read coverage without set any source bands.");
                supportBandSelection = false;
            }
        }
        if (dstBands != null) {
            try {
                imageParam.setDestinationBands(dstBands);
            } catch (UnsupportedOperationException ex) {
                LOGGER.log(Level.WARNING, ex.getMessage() + "Read coverage without set any destination bands.");
            }
        }
    } else {
        srcBands = null;
        dstBands = null;
    }
    /*
         * Next, check if we should allow the image reader to add an offset to signed intergers
         * in order to make them unsigned. We will allow such offset if the SampleDimensions
         * declare unsigned range of sample values.
         */
    boolean usePaletteFactory = false;
    final SampleDimension[] bands = getSampleDimensions(index, supportBandSelection ? srcBands : null, supportBandSelection ? dstBands : null);
    if (imageParam instanceof SpatialImageReadParam) {
        final SpatialImageReadParam sp = (SpatialImageReadParam) imageParam;
        if (!isRangeSigned(bands)) {
            sp.setSampleConversionAllowed(SampleConversionType.SHIFT_SIGNED_INTEGERS, true);
        }
        sp.setSampleConversionAllowed(SampleConversionType.REPLACE_FILL_VALUES, true);
        /*
             * If the image does not have its own color palette, provides a palette factory
             * which will create the IndexColorModel (if needed) from the SampleDimension.
             */
        if (bands != null && imageReader instanceof SpatialImageReader)
            try {
                usePaletteFactory = !((SpatialImageReader) imageReader).hasColors(index);
            } catch (IOException e) {
                throw new CoverageStoreException(formatErrorMessage(e), e);
            }
        /*
             * If there is supplemental dimensions (over the usual 2 dimensions) and the subclass
             * implementation did not defined explicitely some dimension slices, then convert the
             * envelope bounds in those supplemental dimensions to slices index.
             *
             * TODO: there is some duplication between this code and the work done in the parent
             *       class. We need to refactor geodeticToPixelCoordinates(…) in a new helper
             *       class for making easier to divide the work in smaller parts.
             */
        if (param != null && !sp.hasDimensionSlices()) {
            final int gridDim = gridGeometry.getDimension();
            if (gridDim > 2) {
                // max(X_DIMENSION, Y_DIMENSION) + 1
                final CoordinateReferenceSystem crs = gridGeometry.getCoordinateReferenceSystem();
                final int geodeticDim = crs.getCoordinateSystem().getDimension();
                if (geodeticDim > 2) {
                    Envelope envelope = param.getEnvelope();
                    if (envelope != null && envelope.getDimension() > 2)
                        try {
                            if (crs instanceof CompoundCRS) {
                                envelope = CRSUtilities.appendMissingDimensions(envelope, (CompoundCRS) crs);
                            }
                            envelope = Envelopes.transform(envelope, crs);
                            final double[] median = new double[geodeticDim];
                            for (int i = 0; i < geodeticDim; i++) {
                                median[i] = envelope.getMedian(i);
                            }
                            final double[] indices = new double[gridDim];
                            gridGeometry.getGridToCRS(PixelInCell.CELL_CENTER).inverse().transform(median, 0, indices, 0, 1);
                            final GridExtent gridExtent;
                            if (crs instanceof GridGeometry) {
                                gridExtent = ((GridGeometry) crs).getExtent();
                            } else {
                                // We can not fallback on gridGeometry.getExtent(), because
                                // GridGeometry2D contract forces all extra dimensions to have
                                // a span of 1.
                                gridExtent = null;
                            }
                            for (int i = 0; i < gridDim; i++) {
                                if (i != gridDimensionX && i != gridDimensionY) {
                                    final double sliceIndex = indices[i];
                                    if (!Double.isNaN(sliceIndex)) {
                                        final DimensionSlice slice = sp.newDimensionSlice();
                                        slice.addDimensionId(i);
                                        slice.setSliceIndex((int) Math.round(Math.max(gridExtent != null ? gridExtent.getLow(i) : Integer.MIN_VALUE, Math.min(gridExtent != null ? gridExtent.getHigh(i) : Integer.MAX_VALUE, sliceIndex))));
                                    }
                                }
                            }
                        } catch (TransformException e) {
                            throw new CoverageStoreException(formatErrorMessage(e), e);
                        }
                }
            }
        }
    }
    checkAbortState();
    /*
         * Read the image using the ImageReader.read(...) method.  We could have used
         * ImageReader.readAsRenderedImage(...) instead in order to give the reader a
         * chance to return a tiled image,  but experience with some formats suggests
         * that it requires to keep the ImageReader with its input stream open.
         */
    final String name;
    RenderedImage image;
    try {
        if (usePaletteFactory) {
            SampleDimensionPalette.BANDS.set(bands);
            ((SpatialImageReadParam) imageParam).setPaletteFactory(SampleDimensionPalette.FACTORY);
        }
        if (param != null && param.isDeferred()) {
            image = new LargeRenderedImage(imageReader.getOriginatingProvider(), imageParam, imageReader.getInput(), index, null);
        } else {
            image = imageReader.read(index, imageParam);
        }
    } catch (IOException | IllegalArgumentException e) {
        throw new CoverageStoreException(formatErrorMessage(e), e);
    } finally {
        if (usePaletteFactory) {
            SampleDimensionPalette.BANDS.remove();
        }
    }
    /*
         * If the grid geometry changed as a result of subsampling or reading a smaller region,
         * update the grid geometry. The (xmin, ymin) values are usually (0,0), but we take
         * them in account anyway as a paranoiac safety (a previous version of this code used
         * the 'readAsRenderedImage(...)' method, which could have shifted the image).
         */
    if (param != null) {
        final Rectangle sourceRegion = imageParam.getSourceRegion();
        final AffineTransform change = AffineTransform.getTranslateInstance(sourceRegion.x, sourceRegion.y);
        change.scale(imageParam.getSourceXSubsampling(), imageParam.getSourceYSubsampling());
        final int xmin = image.getMinX();
        final int ymin = image.getMinY();
        final int xi = gridDimensionX;
        final int yi = gridDimensionY;
        final MathTransform gridToCRS = gridGeometry.getGridToCRS(PixelInCell.CELL_CORNER);
        MathTransform newGridToCRS = gridToCRS;
        if (!change.isIdentity()) {
            final int gridDimension = gridToCRS.getSourceDimensions();
            final Matrix matrix = Matrices.createIdentity(gridDimension + 1);
            matrix.setElement(xi, xi, change.getScaleX());
            matrix.setElement(yi, yi, change.getScaleY());
            matrix.setElement(xi, gridDimension, change.getTranslateX() - xmin);
            matrix.setElement(yi, gridDimension, change.getTranslateY() - ymin);
            newGridToCRS = MathTransforms.concatenate(MathTransforms.linear(matrix), gridToCRS);
        }
        final GridExtent gridExtent = gridGeometry.getExtent();
        final long[] low = GridGeometryIterator.getLow(gridExtent);
        final long[] high = GridGeometryIterator.getHigh(gridExtent);
        low[xi] = xmin;
        high[xi] = xmin + image.getWidth() - 1;
        low[yi] = ymin;
        high[yi] = ymin + image.getHeight() - 1;
        if (imageParam instanceof SpatialImageReadParam) {
            for (final DimensionSlice slice : ((SpatialImageReadParam) imageParam).getDimensionSlices()) {
                for (final Object id : slice.getDimensionIds()) {
                    if (id instanceof Integer) {
                        final int dim = (Integer) id;
                        low[dim] = high[dim] = slice.getSliceIndex();
                    }
                }
            }
        }
        final GridExtent newGridRange = new GridExtent(null, low, high, true);
        if (newGridToCRS != gridToCRS || !newGridRange.equals(gridExtent)) {
            gridGeometry = new GridGeometry(newGridRange, PixelInCell.CELL_CORNER, newGridToCRS, gridGeometry.getCoordinateReferenceSystem());
        }
    }
    final GridCoverage2D coverage = new org.apache.sis.coverage.grid.GridCoverage2D(gridGeometry, Arrays.asList(bands), image);
    if (loggingEnabled) {
        fullTime = System.nanoTime() - fullTime;
        final Level level = getLogLevel(fullTime);
        if (LOGGER.isLoggable(level)) {
            ImageCoverageStore.logOperation(level, locale, ImageCoverageReader.class, false, input, index, coverage, null, null, destToExtractedGrid, fullTime);
        }
    }
    return coverage;
}
Also used : GridExtent(org.apache.sis.coverage.grid.GridExtent) MathTransform(org.opengis.referencing.operation.MathTransform) CompoundCRS(org.opengis.referencing.crs.CompoundCRS) Rectangle(java.awt.Rectangle) Envelope(org.opengis.geometry.Envelope) ImageReadParam(javax.imageio.ImageReadParam) SpatialImageReadParam(org.geotoolkit.image.io.SpatialImageReadParam) Matrix(org.opengis.referencing.operation.Matrix) DimensionSlice(org.geotoolkit.image.io.DimensionSlice) SpatialImageReader(org.geotoolkit.image.io.SpatialImageReader) CoordinateReferenceSystem(org.opengis.referencing.crs.CoordinateReferenceSystem) ImageReader(javax.imageio.ImageReader) SpatialImageReader(org.geotoolkit.image.io.SpatialImageReader) SpatialImageReadParam(org.geotoolkit.image.io.SpatialImageReadParam) GridGeometry(org.apache.sis.coverage.grid.GridGeometry) DataStoreException(org.apache.sis.storage.DataStoreException) GridCoverage2D(org.apache.sis.coverage.grid.GridCoverage2D) TransformException(org.opengis.referencing.operation.TransformException) IOException(java.io.IOException) SampleDimension(org.apache.sis.coverage.SampleDimension) LargeRenderedImage(org.geotoolkit.image.io.large.LargeRenderedImage) AffineTransform(java.awt.geom.AffineTransform) Level(java.util.logging.Level) MathTransform2D(org.opengis.referencing.operation.MathTransform2D) LargeRenderedImage(org.geotoolkit.image.io.large.LargeRenderedImage) RenderedImage(java.awt.image.RenderedImage)

Aggregations

Rectangle (java.awt.Rectangle)1 AffineTransform (java.awt.geom.AffineTransform)1 RenderedImage (java.awt.image.RenderedImage)1 IOException (java.io.IOException)1 Level (java.util.logging.Level)1 ImageReadParam (javax.imageio.ImageReadParam)1 ImageReader (javax.imageio.ImageReader)1 SampleDimension (org.apache.sis.coverage.SampleDimension)1 GridCoverage2D (org.apache.sis.coverage.grid.GridCoverage2D)1 GridExtent (org.apache.sis.coverage.grid.GridExtent)1 GridGeometry (org.apache.sis.coverage.grid.GridGeometry)1 DataStoreException (org.apache.sis.storage.DataStoreException)1 DimensionSlice (org.geotoolkit.image.io.DimensionSlice)1 SpatialImageReadParam (org.geotoolkit.image.io.SpatialImageReadParam)1 SpatialImageReader (org.geotoolkit.image.io.SpatialImageReader)1 LargeRenderedImage (org.geotoolkit.image.io.large.LargeRenderedImage)1 Envelope (org.opengis.geometry.Envelope)1 CompoundCRS (org.opengis.referencing.crs.CompoundCRS)1 CoordinateReferenceSystem (org.opengis.referencing.crs.CoordinateReferenceSystem)1 MathTransform (org.opengis.referencing.operation.MathTransform)1