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);
}
}
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;
}
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);
}
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;
}
Aggregations