Search in sources :

Example 1 with CannotEvaluateException

use of org.apache.sis.coverage.CannotEvaluateException in project sis by apache.

the class GridCoverage2D method render.

/**
 * Returns a grid data region as a rendered image. The {@code sliceExtent} argument
 * specifies the area of interest and may be {@code null} for requesting the whole image.
 * The coordinates given by {@link RenderedImage#getMinX()} and {@link RenderedImage#getMinY() getMinY()}
 * will be the image location <em>relative to</em> the location specified in {@code sliceExtent}
 * {@linkplain GridExtent#getLow(int) low coordinates} (see super-class javadoc for more discussion).
 * The {@linkplain RenderedImage#getWidth() image width} and {@linkplain RenderedImage#getHeight() height} will be
 * the {@code sliceExtent} {@linkplain GridExtent#getSize(int) sizes} if this method can honor exactly the request,
 * but this method is free to return a smaller or larger image if doing so reduce the amount of data to create or copy.
 * This implementation returns a view as much as possible, without copying sample values.
 *
 * @param  sliceExtent  area of interest, or {@code null} for the whole image.
 * @return the grid slice as a rendered image. Image location is relative to {@code sliceExtent}.
 * @throws MismatchedDimensionException if the given extent does not have the same number of dimensions than this coverage.
 * @throws DisjointExtentException if the given extent does not intersect this grid coverage.
 * @throws CannotEvaluateException if this method can not produce the rendered image for another reason.
 *
 * @see BufferedImage#getSubimage(int, int, int, int)
 */
@Override
@SuppressWarnings("AssertWithSideEffects")
public RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException {
    final GridExtent extent = gridGeometry.extent;
    if (sliceExtent == null) {
        if (extent == null || (data.getMinX() == 0 && data.getMinY() == 0)) {
            return data;
        }
        sliceExtent = extent;
    } else {
        final int expected = gridGeometry.getDimension();
        final int dimension = sliceExtent.getDimension();
        if (expected != dimension) {
            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "sliceExtent", expected, dimension));
        }
    }
    if (extent != null) {
        final int n = min(sliceExtent.getDimension(), extent.getDimension());
        for (int i = 0; i < n; i++) {
            if (sliceExtent.getHigh(i) < extent.getLow(i) || sliceExtent.getLow(i) > extent.getHigh(i)) {
                throw new DisjointExtentException(extent, sliceExtent, i);
            }
        }
    }
    try {
        /*
             * Convert the coordinates from this grid coverage coordinate system to the image coordinate system.
             * The coverage coordinates may require 64 bits integers, but after translation the (x,y) coordinates
             * should be in 32 bits integers range. Do not cast to 32 bits now however; this will be done later.
             */
        final long xmin = addExact(sliceExtent.getLow(xDimension), gridToImageX);
        final long ymin = addExact(sliceExtent.getLow(yDimension), gridToImageY);
        final long xmax = addExact(sliceExtent.getHigh(xDimension), gridToImageX);
        final long ymax = addExact(sliceExtent.getHigh(yDimension), gridToImageY);
        /*
             * BufferedImage.getSubimage() returns a new image with upper-left coordinate at (0,0),
             * which is exactly what this method contract is requesting provided that the requested
             * upper-left point is inside the image.
             */
        if (data instanceof BufferedImage) {
            BufferedImage result = (BufferedImage) data;
            /*
                 * BufferedImage origin should be (0, 0). But for consistency with image API,
                 * we consider it as variable.
                 */
            final long ix = result.getMinX();
            final long iy = result.getMinY();
            if (xmin >= ix && ymin >= iy) {
                final int width = result.getWidth();
                final int height = result.getHeight();
                /*
                     * Result of `ix + width` requires at most 33 bits for any `ix` value (same for y axis).
                     * Subtractions by `xmin` and `ymin` never overflow if `ix` and `iy` are zero or positive,
                     * which should always be the case with BufferedImage. The +1 is applied after subtraction
                     * instead of on `xmax` and `ymax` for avoiding overflow, since the result of `min(…)`
                     * uses at most 33 bits.
                     */
                final int nx = toIntExact(min(xmax, ix + width - 1) - xmin + 1);
                final int ny = toIntExact(min(ymax, iy + height - 1) - ymin + 1);
                if ((xmin | ymin) != 0 || nx != width || ny != height) {
                    result = result.getSubimage(toIntExact(xmin), toIntExact(ymin), nx, ny);
                }
                /*
                     * Workaround for https://bugs.openjdk.java.net/browse/JDK-8166038
                     * If BufferedImage can not be used, fallback on ReshapedImage
                     * at the cost of returning an image larger than necessary.
                     * This workaround can be removed on JDK17.
                     */
                if (org.apache.sis.internal.coverage.j2d.TilePlaceholder.PENDING_JDK_FIX) {
                    if (result.getTileGridXOffset() == ix && result.getTileGridYOffset() == iy) {
                        return result;
                    }
                }
            }
        }
        /*
             * Return the backing image almost as-is (with potentially just a wrapper) for avoiding to copy data.
             * As per method contract, we shall set the (x,y) location to the difference between requested region
             * and actual region of the returned image. For example if the user requested an image starting at
             * (5,5) but the image to return starts at (1,1), then we need to set its location to (-4,-4).
             *
             * Note: we could do a special case when the result has only one tile and create a BufferedImage
             * with Raster.createChild(…), but that would force us to invoke RenderedImage.getTile(…) which
             * may force data loading earlier than desired.
             */
        final ReshapedImage result = new ReshapedImage(data, xmin, ymin, xmax, ymax);
        return result.isIdentity() ? data : result;
    } catch (ArithmeticException e) {
        throw new CannotEvaluateException(e.getMessage(), e);
    }
}
Also used : MismatchedDimensionException(org.opengis.geometry.MismatchedDimensionException) BufferedImage(java.awt.image.BufferedImage) CannotEvaluateException(org.apache.sis.coverage.CannotEvaluateException)

Example 2 with CannotEvaluateException

use of org.apache.sis.coverage.CannotEvaluateException in project sis by apache.

the class GridEvaluator method apply.

/**
 * Returns a sequence of double values for a given point in the coverage.
 * The CRS of the given point may be any coordinate reference system;
 * coordinate conversions will be applied as needed.
 * If the CRS of the point is undefined, then it is assumed to be the {@linkplain #getCoverage() coverage} CRS.
 * The returned sequence includes a value for each {@linkplain SampleDimension sample dimension}.
 *
 * <p>The default interpolation type used when accessing grid values for points which fall between
 * grid cells is nearest neighbor. This default interpolation method may change in future version.</p>
 *
 * <p>The default implementation invokes {@link GridCoverage#render(GridExtent)} for a small region
 * around the point. Subclasses should override with more efficient implementation.</p>
 *
 * @param  point   the coordinate point where to evaluate.
 * @return the sample values at the specified point, or {@code null} if the point is outside the coverage.
 *         For performance reason, this method may return the same array
 *         on every method call by overwriting previous values.
 *         Callers should not assume that the array content stay valid for a long time.
 * @throws PointOutsideCoverageException if the evaluation failed because the input point
 *         has invalid coordinates and the {@link #isNullIfOutside()} flag is {@code false}.
 * @throws CannotEvaluateException if the values can not be computed at the specified coordinates
 *         for another reason. This exception may be thrown if the coverage data type can not be
 *         converted to {@code double} by an identity or widening conversion.
 *         Subclasses may relax this constraint if appropriate.
 */
@Override
public double[] apply(final DirectPosition point) throws CannotEvaluateException {
    /*
         * TODO: instead of restricting to a single point, keep the automatic size (1 or 2),
         * invoke render for each slice, then interpolate. We would keep a value of 1 in the
         * size array if we want to disable interpolation in some particular axis (e.g. time).
         */
    final GridGeometry gridGeometry = coverage.gridGeometry;
    final long[] size = new long[gridGeometry.getDimension()];
    java.util.Arrays.fill(size, 1);
    try {
        final FractionalGridCoordinates gc = toGridPosition(point);
        try {
            final GridExtent subExtent = gc.toExtent(gridGeometry.extent, size, nullIfOutside);
            if (subExtent != null) {
                return evaluate(coverage.render(subExtent), 0, 0);
            }
        } catch (ArithmeticException | IndexOutOfBoundsException | DisjointExtentException ex) {
            if (!nullIfOutside) {
                throw (PointOutsideCoverageException) new PointOutsideCoverageException(gc.pointOutsideCoverage(gridGeometry.extent)).initCause(ex);
            }
        }
    } catch (PointOutsideCoverageException ex) {
        throw ex;
    } catch (RuntimeException | TransformException ex) {
        throw new CannotEvaluateException(ex.getMessage(), ex);
    }
    // May reach this point only if `nullIfOutside` is true.
    return null;
}
Also used : PointOutsideCoverageException(org.apache.sis.coverage.PointOutsideCoverageException) TransformException(org.opengis.referencing.operation.TransformException) CannotEvaluateException(org.apache.sis.coverage.CannotEvaluateException)

Example 3 with CannotEvaluateException

use of org.apache.sis.coverage.CannotEvaluateException in project sis by apache.

the class ResampledGridCoverage method render.

/**
 * Returns a two-dimensional slice of resampled grid data as a rendered image.
 *
 * @throws CannotEvaluateException if this method can not produce the rendered image.
 */
@Override
public RenderedImage render(GridExtent sliceExtent) {
    if (sliceExtent == null) {
        sliceExtent = gridGeometry.getExtent();
    }
    // Bounds (in pixel coordinates) of resampled image.
    final int width, height;
    // From resampled image pixels to source image pixels.
    final MathTransform toSource;
    GridExtent sourceExtent;
    try {
        // Compute now for getting exception early if `sliceExtent` is invalid.
        final int[] resampledDimensions = sliceExtent.getSubspaceDimensions(BIDIMENSIONAL);
        width = Math.toIntExact(sliceExtent.getSize(resampledDimensions[0]));
        height = Math.toIntExact(sliceExtent.getSize(resampledDimensions[1]));
        /*
             * Convert the given `sliceExtent` (in units of this grid) to units of the source grid.
             * If a dimension can not be converted (e.g. because a `gridToCRS` transform has a NaN
             * factor in that dimension), the corresponding source grid coordinates will be copied.
             */
        final GeneralEnvelope sourceBounds = sliceExtent.toCRS(toSourceCorner, toSourceCenter, null);
        final int dimension = sourceBounds.getDimension();
        if (sourceBounds.isEmpty()) {
            final GridExtent se = source.gridGeometry.getExtent();
            for (int i = 0; i < dimension; i++) {
                double min = sourceBounds.getMinimum(i);
                double max = sourceBounds.getMaximum(i);
                if (Double.isNaN(min))
                    min = se.getLow(i);
                if (Double.isNaN(max))
                    max = se.getHigh(i);
                sourceBounds.setRange(i, min, max);
            }
        }
        /*
             * If the given `sliceExtent` has more than 2 dimensions, some dimensions must have a size of 1.
             * But the converted size may become greater than 1 after conversion to source coordinate space.
             * The following code forces the corresponding source dimensions to a thin slice in the middle.
             * This is necessary for avoiding a `SubspaceNotSpecifiedException` when requesting the slice of
             * source data.
             */
        int sourceDimX = 0, sourceDimY = 1;
        if (toSourceDimensions != null) {
            long mask = 0;
            for (final int i : resampledDimensions) {
                mask |= toSourceDimensions[i];
            }
            sourceDimX = Long.numberOfTrailingZeros(mask);
            sourceDimY = Long.numberOfTrailingZeros(mask & ~(1L << sourceDimX));
            if (sourceDimY >= dimension) {
                /*
                     * `mask` selected less than 2 dimensions. Unconditionally add
                     * 1 or 2 dimensions chosen among the first unused dimensions.
                     */
                if (sourceDimX >= dimension)
                    sourceDimX = 0;
                sourceDimY = (sourceDimX != 0) ? 0 : 1;
                mask = (1L << sourceDimX) | (1L << sourceDimY);
            }
            /*
                 * Modify the envelope by forcing to a thin slice
                 * all dimensions not used for interpolations.
                 */
            mask = ~mask;
            int i;
            while ((i = Long.numberOfTrailingZeros(mask)) < dimension) {
                final double median = sourceBounds.getMedian(i);
                if (Double.isFinite(median)) {
                    sourceBounds.setRange(i, median, median);
                }
                mask &= ~(1L << i);
            }
        }
        /*
             * Convert floating point values to long integers with a margin only in the dimensions
             * where interpolations will happen. All other dimensions should have a span of zero,
             * so the `ENCLOSING` rounding mode should assign a size of exactly 1 in those dimensions.
             */
        final int[] margin = new int[dimension];
        margin[sourceDimX] = supportSizeX;
        margin[sourceDimY] = supportSizeY;
        sourceExtent = new GridExtent(sourceBounds, GridRoundingMode.ENCLOSING, null, margin, null, null, null);
        /*
             * The transform inputs must be two-dimensional (outputs may be more flexible). If this is not the case,
             * try to extract a two-dimensional part operating only on the slice dimensions having an extent larger
             * than one cell. The choice of dimensions may vary between different calls to this `render(…)` method,
             * depending on `sliceExtent` value.
             */
        final TransformSeparator sep = new TransformSeparator(toSourceCenter);
        sep.addSourceDimensions(resampledDimensions);
        sep.addTargetDimensions(sourceDimX, sourceDimY);
        sep.setSourceExpandable(true);
        MathTransform toSourceSlice = sep.separate();
        final int[] requiredSources = sep.getSourceDimensions();
        if (requiredSources.length > BIDIMENSIONAL) {
            /*
                 * If we enter in this block, TransformSeparator can not create a MathTransform with only the 2
                 * requested source dimensions; it needs more sources. In such case, if coordinates in missing
                 * dimensions can be set to constant values (grid low == grid high), create a transform which
                 * will add new dimensions with coordinates set to those constant values. The example below
                 * passes the two first dimensions as-is and set the third dimensions to constant value 7:
                 *
                 *     ┌   ┐   ┌         ┐┌   ┐
                 *     │ x │   │ 1  0  0 ││ x │
                 *     │ y │ = │ 0  1  0 ││ y │
                 *     │ z │   │ 0  0  7 ││ 1 │
                 *     │ 1 │   │ 0  0  1 │└   ┘
                 *     └   ┘   └         ┘
                 */
            final MatrixSIS m = Matrices.createZero(requiredSources.length + 1, BIDIMENSIONAL + 1);
            m.setElement(requiredSources.length, BIDIMENSIONAL, 1);
            for (int j = 0; j < requiredSources.length; j++) {
                final int r = requiredSources[j];
                final int i = Arrays.binarySearch(resampledDimensions, r);
                if (i >= 0) {
                    m.setElement(j, i, 1);
                } else {
                    final long low = sliceExtent.getLow(r);
                    if (low == sliceExtent.getHigh(r)) {
                        m.setElement(j, BIDIMENSIONAL, low);
                    } else {
                        throw new CannotEvaluateException(Resources.format(Resources.Keys.TransformDependsOnDimension_1, sliceExtent.getAxisIdentification(r, r)));
                    }
                }
            }
            toSourceSlice = MathTransforms.concatenate(MathTransforms.linear(m), toSourceSlice);
        }
        /*
             * Current `toSource` is a transform from source cell coordinates to target cell coordinates.
             * We need a transform from source pixel coordinates to target pixel coordinates (in images).
             * An offset may exist between cell coordinates and pixel coordinates.
             */
        final MathTransform resampledToGrid = MathTransforms.translation(sliceExtent.getLow(resampledDimensions[0]), sliceExtent.getLow(resampledDimensions[1]));
        final MathTransform gridToSource = MathTransforms.translation(Math.negateExact(sourceExtent.getLow(sourceDimX)), Math.negateExact(sourceExtent.getLow(sourceDimY)));
        toSource = MathTransforms.concatenate(resampledToGrid, toSourceSlice, gridToSource);
    } catch (FactoryException | TransformException | ArithmeticException e) {
        throw new CannotEvaluateException(e.getLocalizedMessage(), e);
    }
    /*
         * Following call is potentially costly, depending on `source` implementation.
         * For example it may cause loading of tiles from a file. For this reason we
         * call this method only here, when remaining operations are unlikely to fail.
         */
    final RenderedImage values = source.render(sourceExtent);
    return imageProcessor.resample(values, new Rectangle(width, height), toSource);
}
Also used : MathTransform(org.opengis.referencing.operation.MathTransform) FactoryException(org.opengis.util.FactoryException) TransformException(org.opengis.referencing.operation.TransformException) Rectangle(java.awt.Rectangle) GeneralEnvelope(org.apache.sis.geometry.GeneralEnvelope) RenderedImage(java.awt.image.RenderedImage) TransformSeparator(org.apache.sis.referencing.operation.transform.TransformSeparator) MatrixSIS(org.apache.sis.referencing.operation.matrix.MatrixSIS) CannotEvaluateException(org.apache.sis.coverage.CannotEvaluateException)

Example 4 with CannotEvaluateException

use of org.apache.sis.coverage.CannotEvaluateException in project sis by apache.

the class TiledGridCoverage method render.

/**
 * Returns a two-dimensional slice of grid data as a rendered image.
 *
 * @param  sliceExtent  a subspace of this grid coverage extent, or {@code null} for the whole image.
 * @return the grid slice as a rendered image. Image location is relative to {@code sliceExtent}.
 */
@Override
public RenderedImage render(GridExtent sliceExtent) {
    final GridExtent available = getGridGeometry().getExtent();
    final int dimension = available.getDimension();
    if (sliceExtent == null) {
        sliceExtent = available;
    } else {
        final int sd = sliceExtent.getDimension();
        if (sd != dimension) {
            throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "sliceExtent", dimension, sd));
        }
    }
    final int[] selectedDimensions = sliceExtent.getSubspaceDimensions(BIDIMENSIONAL);
    if (selectedDimensions[1] != 1) {
        // TODO
        throw new UnsupportedOperationException("Non-horizontal slices not yet implemented.");
    }
    final RenderedImage image;
    try {
        // Indices of first tile to read, inclusive.
        final int[] tileLower = new int[dimension];
        // Indices of last tile to read, exclusive.
        final int[] tileUpper = new int[dimension];
        // Pixel offset compared to Area Of Interest.
        final int[] offsetAOI = new int[dimension];
        // Subsampled image size.
        final int[] imageSize = new int[dimension];
        for (int i = 0; i < dimension; i++) {
            // Lowest valid coordinate in subsampled image.
            final long min = available.getLow(i);
            // Highest valid coordinate, inclusive.
            final long max = available.getHigh(i);
            // Requested coordinate in subsampled image.
            final long aoiMin = sliceExtent.getLow(i);
            final long aoiMax = sliceExtent.getHigh(i);
            final long tileUp = incrementExact(toTileMatrixCoordinate(Math.min(aoiMax, max), i));
            final long tileLo = toTileMatrixCoordinate(Math.max(aoiMin, min), i);
            if (tileUp <= tileLo) {
                final String message = Errors.getResources(getLocale()).getString(Errors.Keys.IllegalRange_2, aoiMin, aoiMax);
                if (aoiMin > aoiMax) {
                    throw new IllegalArgumentException(message);
                } else {
                    throw new DisjointExtentException(message);
                }
            }
            // Lower and upper coordinates in subsampled image, rounded to integer number of tiles and clipped to available data.
            final long lower = /* inclusive */
            Math.max(toSubsampledPixel(/* inclusive */
            multiplyExact(tileLo, tileSize[i]), i), min);
            final long upper = incrementExact(Math.min(toSubsampledPixel(decrementExact(multiplyExact(tileUp, tileSize[i])), i), max));
            imageSize[i] = toIntExact(subtractExact(upper, lower));
            offsetAOI[i] = toIntExact(subtractExact(lower, aoiMin));
            tileLower[i] = toIntExact(subtractExact(tileLo, tmcOfFirstTile[i]));
            tileUpper[i] = toIntExact(subtractExact(tileUp, tmcOfFirstTile[i]));
        }
        /*
             * Prepare an iterator over all tiles to read, together with the following properties:
             *    - Two-dimensional conversion from pixel coordinates to "real world" coordinates.
             */
        final AOI iterator = new AOI(tileLower, tileUpper, offsetAOI, dimension);
        final Map<String, Object> properties = DeferredProperty.forGridGeometry(getGridGeometry(), selectedDimensions);
        if (deferredTileReading) {
            image = new TiledDeferredImage(imageSize, tileLower, properties, iterator);
        } else {
            /*
                 * If the loading strategy is not `RasterLoadingStrategy.AT_GET_TILE_TIME`, get all tiles
                 * in the area of interest now. I/O operations, if needed, happen in `readTiles(…)` call.
                 */
            final Raster[] result = readTiles(iterator);
            image = new TiledImage(properties, colors, imageSize[X_DIMENSION], imageSize[Y_DIMENSION], tileLower[X_DIMENSION], tileLower[Y_DIMENSION], result);
        }
    } catch (Exception e) {
        // Too many exception types for listing them all.
        throw new CannotEvaluateException(Resources.forLocale(getLocale()).getString(Resources.Keys.CanNotRenderImage_1, getDisplayName()), e);
    }
    return image;
}
Also used : DisjointExtentException(org.apache.sis.coverage.grid.DisjointExtentException) GridExtent(org.apache.sis.coverage.grid.GridExtent) Raster(java.awt.image.Raster) MismatchedDimensionException(org.opengis.geometry.MismatchedDimensionException) Point(java.awt.Point) MismatchedDimensionException(org.opengis.geometry.MismatchedDimensionException) DataStoreException(org.apache.sis.storage.DataStoreException) IOException(java.io.IOException) CannotEvaluateException(org.apache.sis.coverage.CannotEvaluateException) DisjointExtentException(org.apache.sis.coverage.grid.DisjointExtentException) RenderedImage(java.awt.image.RenderedImage) TiledImage(org.apache.sis.internal.coverage.j2d.TiledImage) CannotEvaluateException(org.apache.sis.coverage.CannotEvaluateException)

Aggregations

CannotEvaluateException (org.apache.sis.coverage.CannotEvaluateException)4 RenderedImage (java.awt.image.RenderedImage)2 MismatchedDimensionException (org.opengis.geometry.MismatchedDimensionException)2 TransformException (org.opengis.referencing.operation.TransformException)2 Point (java.awt.Point)1 Rectangle (java.awt.Rectangle)1 BufferedImage (java.awt.image.BufferedImage)1 Raster (java.awt.image.Raster)1 IOException (java.io.IOException)1 PointOutsideCoverageException (org.apache.sis.coverage.PointOutsideCoverageException)1 DisjointExtentException (org.apache.sis.coverage.grid.DisjointExtentException)1 GridExtent (org.apache.sis.coverage.grid.GridExtent)1 GeneralEnvelope (org.apache.sis.geometry.GeneralEnvelope)1 TiledImage (org.apache.sis.internal.coverage.j2d.TiledImage)1 MatrixSIS (org.apache.sis.referencing.operation.matrix.MatrixSIS)1 TransformSeparator (org.apache.sis.referencing.operation.transform.TransformSeparator)1 DataStoreException (org.apache.sis.storage.DataStoreException)1 MathTransform (org.opengis.referencing.operation.MathTransform)1 FactoryException (org.opengis.util.FactoryException)1