| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sis.coverage.grid; |
| |
| import java.util.Map; |
| import java.util.Arrays; |
| import java.util.TreeMap; |
| import java.util.Objects; |
| import java.awt.image.RenderedImage; |
| import org.opengis.util.FactoryException; |
| import org.opengis.geometry.DirectPosition; |
| import org.opengis.metadata.extent.GeographicBoundingBox; |
| import org.opengis.referencing.operation.Matrix; |
| import org.opengis.referencing.operation.MathTransform; |
| import org.opengis.referencing.operation.TransformException; |
| import org.opengis.referencing.operation.CoordinateOperation; |
| import org.opengis.referencing.operation.NoninvertibleTransformException; |
| import org.opengis.referencing.crs.CoordinateReferenceSystem; |
| import org.apache.sis.coverage.SampleDimension; |
| import org.apache.sis.feature.internal.Resources; |
| import org.apache.sis.util.ArraysExt; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.privy.CollectionsExt; |
| import org.apache.sis.coverage.privy.ImageUtilities; |
| import org.apache.sis.referencing.CRS; |
| import org.apache.sis.referencing.privy.DirectPositionView; |
| import org.apache.sis.referencing.privy.WraparoundAxesFinder; |
| import org.apache.sis.referencing.operation.matrix.Matrices; |
| import org.apache.sis.referencing.operation.matrix.MatrixSIS; |
| import org.apache.sis.referencing.operation.transform.MathTransforms; |
| import org.apache.sis.referencing.operation.transform.TransformSeparator; |
| import org.apache.sis.util.logging.Logging; |
| |
| // Specific to the geoapi-3.1 and geoapi-4.0 branches: |
| import org.opengis.coverage.CannotEvaluateException; |
| import org.opengis.coverage.PointOutsideCoverageException; |
| |
| |
| /** |
| * Default implementation of {@link GridCoverage.Evaluator} for interpolating values at given positions. |
| * Values are computed by calls to {@link #apply(DirectPosition)} and are returned as {@code double[]}. |
| * |
| * <h2>Multi-threading</h2> |
| * Evaluators are not thread-safe. An instance of {@code DefaultEvaluator} should be created |
| * for each thread that need to compute sample values. |
| * |
| * <h2>Limitations</h2> |
| * Current implementation performs nearest-neighbor sampling only. A future version will provide interpolations. |
| * |
| * @author Johann Sorel (Geomatys) |
| * @author Martin Desruisseaux (Geomatys) |
| * |
| * @see GridCoverage#evaluator() |
| */ |
| class DefaultEvaluator implements GridCoverage.Evaluator { |
| /** |
| * The coverage in which to evaluate sample values. |
| */ |
| private final GridCoverage coverage; |
| |
| /** |
| * The coordinate reference system of input points given to this converter, |
| * or {@code null} if assumed the same as the coverage CRS. |
| * This is used by {@link #toGridPosition(DirectPosition)} for checking if {@link #inputToGrid} needs |
| * to be recomputed. As long at the evaluated points have the same CRS, the same transform is reused. |
| */ |
| private CoordinateReferenceSystem inputCRS; |
| |
| /** |
| * The transform from {@link #inputCRS} to grid coordinates. |
| * This is cached for avoiding the costly process of fetching a coordinate operation |
| * in the common case where the coordinate reference systems did not changed. |
| */ |
| private MathTransform inputToGrid; |
| |
| /** |
| * Grid coordinates after {@link #inputToGrid} conversion. |
| * |
| * @see #toGridPosition(DirectPosition) |
| */ |
| private FractionalGridCoordinates.Position position; |
| |
| /** |
| * Array where to store sample values computed by {@link #apply(DirectPosition)}. |
| * For performance reasons, the same array may be recycled on every method call. |
| */ |
| double[] values; |
| |
| /** |
| * Whether to return {@code null} instead of throwing an exception if given point |
| * is outside coverage bounds. |
| * |
| * @see #isNullIfOutside() |
| */ |
| private boolean nullIfOutside; |
| |
| |
| // ―――――――― Following fields are for the handling of wraparound axes. ――――――――――――――――――――― |
| |
| /** |
| * A bitmask of grid dimensions that need to be verified for wraparound axes. |
| */ |
| private long wraparoundAxes; |
| |
| /** |
| * Coverage extent converted to floating point numbers, only for the grid dimensions having |
| * a bit set to 1 in {@link #wraparoundAxes} bitmask. The length of this array is the number |
| * of bits set in {@link #wraparoundAxes} multiplied by 2. Elements are (lower, upper) tuples. |
| */ |
| private double[] wraparoundExtent; |
| |
| /** |
| * Transform from grid coordinates to the CRS where wraparound axes may exist. |
| * It is sometimes the same transform as {@code gridToCRS} but not always. |
| * It may differ for example if a projected CRS has been replaced by a geographic CRS. |
| */ |
| private MathTransform gridToWraparound; |
| |
| /** |
| * The span (maximum - minimum) of wraparound axes, with 0 value for axes that are not wraparound. |
| * The length of this array may be shorter than the CRS number of dimensions if all remaining axes |
| * are not wraparound axes. |
| */ |
| private double[] periods; |
| |
| /** |
| * The slice where to perform evaluation, or {@code null} if not yet computed. |
| * This information allows to specify for example two-dimensional points for |
| * evaluating in a three-dimensional data cube. This is used for completing |
| * the missing coordinate values. |
| * |
| * @see #getDefaultSlice() |
| * @see #setDefaultSlice(Map) |
| */ |
| private Map<Integer,Long> slice; |
| |
| /** |
| * Creates a new evaluator for the given coverage. This constructor is protected for allowing |
| * {@link GridCoverage} subclasses to provide their own {@code DefaultEvaluator} implementations. |
| * For using an evaluator, invoke {@link GridCoverage#evaluator()} instead. |
| * |
| * @param coverage the coverage for which to create an evaluator. |
| * |
| * @see GridCoverage#evaluator() |
| */ |
| protected DefaultEvaluator(final GridCoverage coverage) { |
| this.coverage = Objects.requireNonNull(coverage); |
| } |
| |
| /** |
| * Returns the coverage from which this evaluator is fetching sample values. |
| * This is the coverage on which the {@link GridCoverage#evaluator()} method has been invoked. |
| * |
| * @return the source of sample values for this evaluator. |
| */ |
| @Override |
| public final GridCoverage getCoverage() { |
| return coverage; |
| } |
| |
| /** |
| * Returns the default slice where to perform evaluation, or an empty map if unspecified. |
| * Keys are dimensions from 0 inclusive to {@link GridGeometry#getDimension()} exclusive, |
| * and values are the grid coordinates of the slice in the dimension specified by the key. |
| * |
| * <p>This information allows to invoke {@link #apply(DirectPosition)} with for example two-dimensional points |
| * even if the underlying coverage is three-dimensional. The missing coordinate values are replaced by the |
| * values provided in the map.</p> |
| * |
| * @return the default slice where to perform evaluation, or an empty map if unspecified. |
| */ |
| @Override |
| @SuppressWarnings("ReturnOfCollectionOrArrayField") // Because the map is unmodifiable. |
| public final Map<Integer,Long> getDefaultSlice() { |
| if (slice == null) { |
| final GridExtent extent = coverage.getGridGeometry().getExtent(); |
| slice = CollectionsExt.unmodifiableOrCopy(extent.getSliceCoordinates()); |
| } |
| return slice; |
| } |
| |
| /** |
| * Sets the default slice where to perform evaluation when the points do not have enough dimensions. |
| * A {@code null} argument restores the default value, which is to infer the slice from the coverage |
| * grid geometry. |
| * |
| * @param slice the default slice where to perform evaluation, or an empty map if none. |
| * @throws IllegalArgumentException if the map contains an illegal dimension or grid coordinate value. |
| * |
| * @see GridExtent#getSliceCoordinates() |
| */ |
| @Override |
| @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter") |
| public void setDefaultSlice(Map<Integer,Long> slice) { |
| if (!Objects.equals(this.slice, slice)) { |
| if (slice != null) { |
| slice = CollectionsExt.unmodifiableOrCopy(new TreeMap<>(slice)); |
| final GridExtent extent = coverage.getGridGeometry().getExtent(); |
| final int max = extent.getDimension() - 1; |
| for (final Map.Entry<Integer,Long> entry : slice.entrySet()) { |
| final int dim = entry.getKey(); |
| ArgumentChecks.ensureBetween("slice.key", 0, max, dim); |
| ArgumentChecks.ensureBetween(extent.getAxisIdentification(dim, dim).toString(), |
| extent.getLow(dim), extent.getHigh(dim), entry.getValue()); |
| } |
| } |
| this.slice = slice; |
| inputCRS = null; |
| inputToGrid = null; |
| } |
| } |
| |
| /** |
| * Returns {@code true} if this evaluator is allowed to wraparound coordinates that are outside the grid. |
| * The initial value is {@code false}. This method may continue to return {@code false} even after a call |
| * to {@code setWraparoundEnabled(true)} if no wraparound axis has been found in the coverage CRS. |
| * |
| * @return {@code true} if this evaluator may wraparound coordinates that are outside the grid. |
| */ |
| @Override |
| public boolean isWraparoundEnabled() { |
| return (wraparoundAxes != 0); |
| } |
| |
| /** |
| * Specifies whether this evaluator is allowed to wraparound coordinates that are outside the grid. |
| * If {@code true} and if a given coordinate is outside the grid, then this evaluator may translate |
| * the point along a wraparound axis in an attempt to get the point inside the grid. For example, if |
| * the coverage CRS has a longitude axis, then the evaluator may translate the longitude value by a |
| * multiple of 360°. |
| * |
| * @param allow whether to allow wraparound of coordinates that are outside the grid. |
| */ |
| @Override |
| public void setWraparoundEnabled(final boolean allow) { |
| wraparoundAxes = 0; |
| if (allow) try { |
| final WraparoundAxesFinder f = new WraparoundAxesFinder(coverage.getCoordinateReferenceSystem()); |
| if ((periods = f.periods()) != null) { |
| final GridGeometry gridGeometry = coverage.getGridGeometry(); |
| final GridExtent extent = gridGeometry.getExtent(); |
| MathTransform gridToCRS = gridGeometry.getGridToCRS(PixelInCell.CELL_CENTER); |
| gridToWraparound = MathTransforms.concatenate(gridToCRS, f.preferredToSpecified.inverse()); |
| final Matrix m = gridToWraparound.derivative(new DirectPositionView.Double(extent.getPointOfInterest(PixelInCell.CELL_CENTER))); |
| /* |
| * `gridToWraparound` is the transform from grid coordinates to a CRS where wraparound axes exist. |
| * It may be the coverage CRS or its base CRS. The wraparound axes are identified by `periods`. |
| * The Jacobian matrix tells us which grid axes are dependencies of the wraparound axes. |
| * |
| * Note: the use of this matrix is not fully reliable with non-linear `gridToCRS` transforms |
| * because it is theoretically possible that a coefficient is zero at the point of interest |
| * by pure coincidence but would be non-zero at another location, |
| * But we think that it would require a transform with unlikely properties |
| * (e.g. transforming parallels to vertical straight lines at some places). |
| */ |
| for (int j = periods.length; --j >= 0;) { |
| if (periods[j] > 0) { // Find target dimensions (CRS) having wraparound axis. |
| for (int i = Math.min(m.getNumCol(), Long.SIZE); --i >= 0;) { |
| if (m.getElement(j, i) != 0) { |
| wraparoundAxes |= (1L << i); // Mark sources (grid dimensions) dependent of target (CRS dimensions). |
| } |
| } |
| } |
| } |
| /* |
| * Get the grid extent only for the grid axes that are connected to wraparound CRS axes. |
| * There is at least one such axis, otherwise `periods` would have been null. |
| */ |
| wraparoundExtent = new double[Long.bitCount(wraparoundAxes) << 1]; |
| long axes = wraparoundAxes; |
| int j = 0; |
| do { |
| final int i = Long.numberOfTrailingZeros(axes); |
| wraparoundExtent[j++] = extent.getLow(i); |
| wraparoundExtent[j++] = extent.getHigh(i); |
| axes &= ~(1L << i); |
| } while (axes != 0); |
| assert wraparoundExtent.length == j : j; |
| } |
| } catch (TransformException e) { |
| recoverableException("setWraparoundEnabled", e); |
| } |
| } |
| |
| /** |
| * Returns whether to return {@code null} instead of throwing an exception if a point is outside coverage bounds. |
| * The default value is {@code false}, which means that the default {@link #apply(DirectPosition)} behavior is to |
| * throw {@link PointOutsideCoverageException} for points outside bounds. |
| * |
| * @return whether {@link #apply(DirectPosition)} return {@code null} for points outside coverage bounds. |
| */ |
| @Override |
| public boolean isNullIfOutside() { |
| return nullIfOutside; |
| } |
| |
| /** |
| * Sets whether to return {@code null} instead of throwing an exception if a point is outside coverage bounds. |
| * The default value is {@code false}. Setting this flag to {@code true} may improve performances if the caller |
| * expects that many points will be outside coverage bounds, since it reduces the number of exceptions to be |
| * created. |
| * |
| * @param flag whether {@link #apply(DirectPosition)} should use {@code null} return value instead of |
| * {@link PointOutsideCoverageException} for signaling that a point is outside coverage bounds. |
| */ |
| @Override |
| public void setNullIfOutside(final boolean flag) { |
| nullIfOutside = flag; |
| } |
| |
| /** |
| * 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 position 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 cannot be computed at the specified coordinates |
| * for another reason. This exception may be thrown if the coverage data type cannot 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). |
| * |
| * See https://issues.apache.org/jira/browse/SIS-576 |
| */ |
| final GridGeometry gridGeometry = coverage.gridGeometry; |
| final long[] size = new long[gridGeometry.getDimension()]; |
| 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), point).initCause(ex); |
| } |
| } |
| } catch (PointOutsideCoverageException ex) { |
| ex.setOffendingLocation(point); |
| throw ex; |
| } catch (RuntimeException | FactoryException | TransformException ex) { |
| throw new CannotEvaluateException(ex.getMessage(), ex); |
| } |
| return null; // May reach this point only if `nullIfOutside` is true. |
| } |
| |
| /** |
| * Gets sample values from the given image at the given index. This method does not verify |
| * explicitly if the coordinates are out of bounds; we rely on the checks performed by the |
| * image and sample model implementations. |
| * |
| * @param data the data from which to get the sample values. |
| * @param x column index of the value to get. |
| * @param y row index of the value to get. |
| * @return the sample values. The same array may be recycled on every method call. |
| * @throws ArithmeticException if an integer overflow occurred while computing indices. |
| * @throws IndexOutOfBoundsException if a coordinate is out of bounds. |
| */ |
| final double[] evaluate(final RenderedImage data, final int x, final int y) { |
| final int tx = ImageUtilities.pixelToTileX(data, x); |
| final int ty = ImageUtilities.pixelToTileY(data, y); |
| return values = data.getTile(tx, ty).getPixel(x, y, values); |
| } |
| |
| /** |
| * Converts the specified geospatial position to grid coordinates. |
| * If the given position is associated to a non-null coordinate reference system (CRS) different than the |
| * {@linkplain #getCoverage() coverage} CRS, then this method automatically transforms that position to the |
| * {@linkplain GridCoverage#getCoordinateReferenceSystem() coverage CRS} before to compute grid coordinates. |
| * |
| * <p>This method does not put any restriction on the grid coordinates result. |
| * The result may be outside the {@linkplain GridGeometry#getExtent() grid extent} |
| * if the {@linkplain GridGeometry#getGridToCRS(PixelInCell) grid to CRS} transform allows it.</p> |
| * |
| * @param point geospatial coordinates (in arbitrary CRS) to transform to grid coordinates. |
| * @return the grid coordinates for the given geospatial coordinates. |
| * @throws IncompleteGridGeometryException if the {@linkplain GridCoverage#getGridGeometry() grid geometry} |
| * does not define a "grid to CRS" transform, or if the given point has a non-null CRS but the |
| * coverage does not {@linkplain GridCoverage#getCoordinateReferenceSystem() have a CRS}. |
| * @throws TransformException if the given coordinates cannot be transformed. |
| * |
| * @see FractionalGridCoordinates#toPosition(MathTransform) |
| */ |
| @Override |
| public FractionalGridCoordinates toGridCoordinates(final DirectPosition point) throws TransformException { |
| try { |
| return new FractionalGridCoordinates(toGridPosition(Objects.requireNonNull(point))); |
| } catch (FactoryException e) { |
| throw new TransformException(e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Updates the grid {@linkplain #position} with the given geospatial position. |
| * This is the implementation of {@link #toGridCoordinates(DirectPosition)} except |
| * that it avoid creating a new {@link FractionalGridCoordinates} on each method call. |
| * |
| * @param point the geospatial position. |
| * @return the given position converted to grid coordinates (possibly out of grid bounds). |
| * @throws FactoryException if no operation is found form given point CRS to coverage CRS. |
| * @throws TransformException if the given position cannot be converted. |
| */ |
| final FractionalGridCoordinates.Position toGridPosition(final DirectPosition point) |
| throws FactoryException, TransformException |
| { |
| /* |
| * If the `inputToGrid` transform has not yet been computed or is outdated, compute now. |
| * The result will be cached and reused as long as the `inputCRS` is the same. |
| */ |
| final CoordinateReferenceSystem crs = point.getCoordinateReferenceSystem(); |
| if (crs != inputCRS || inputToGrid == null) { |
| setInputCRS(crs); |
| } |
| /* |
| * Transform geospatial coordinates to grid coordinates. Result is unconditionally stored |
| * in the `position` object, which will be copied by the caller if needed for public API. |
| */ |
| final DirectPosition result = inputToGrid.transform(point, position); |
| if (result != position) { |
| // Should not happen, but be paranoiac. |
| final double[] coordinates = position.coordinates; |
| System.arraycopy(result.getCoordinates(), 0, coordinates, 0, coordinates.length); |
| } |
| /* |
| * In most cases, the work of this method ends here. The remaining code in this method |
| * is for handling wraparound axes. If a coordinate is outside the coverage extent, |
| * check if a wraparound on some axes would bring the coordinates inside the extent. |
| */ |
| long axes = wraparoundAxes; |
| if (axes != 0) { |
| double[] coordinates = position.coordinates; |
| long outsideAxes = 0; |
| int j = 0; |
| do { |
| final int i = Long.numberOfTrailingZeros(axes); |
| final double c = coordinates[i]; |
| double border; |
| if (c < (border = wraparoundExtent[j++]) || c > (border = wraparoundExtent[j])) { |
| /* |
| * Detected that the point is outside the grid extent along an axis where wraparound is possible. |
| * The first time that we find such axis, expand the coordinates array for storing two points. |
| * The two points will have the same coordinates, except on all axes where the point is outside. |
| * On those axes, the coordinate of the first point is set to the closest border of the grid. |
| */ |
| if (outsideAxes == 0) { |
| final int n = coordinates.length; |
| coordinates = Arrays.copyOf(coordinates, 2*Math.max(n, gridToWraparound.getTargetDimensions())); |
| System.arraycopy(coordinates, 0, coordinates, n, n); |
| } |
| coordinates[i] = border; |
| outsideAxes |= (1L << i); |
| } |
| j++; // Outside above `if (…)` statement because increment needs to be unconditional. |
| axes &= ~(1L << i); |
| } while (axes != 0); |
| assert wraparoundExtent.length == j : j; |
| /* |
| * If a coordinate was found outside the grid, transform to a CRS where we can apply shift. |
| * It may be the same CRS as the coverage CRS or the source CRS, but not necessarily. |
| * For example if the CRS is projected, then we need to use a geographic intermediate CRS. |
| * In the common case where the source CRS is already geographic, the second point in the |
| * `coordinates` array after `transform(…)` will contain the same coordinates as `point`, |
| * but potentially with more dimensions. |
| */ |
| if (outsideAxes != 0) { |
| gridToWraparound.transform(coordinates, 0, coordinates, 0, 2); |
| final int s = gridToWraparound.getTargetDimensions(); |
| for (int i = periods.length; --i >= 0;) { |
| final double period = periods[i]; |
| if (period > 0) { |
| /* |
| * Compute the shift that was necessary for moving the point inside the grid, |
| * then round that shift to an integer number of periods. Modify the original |
| * coordinate by applying that modified translation. |
| */ |
| final int oi = i + s; // Index of original coordinates. |
| double shift = coordinates[i] - coordinates[oi]; |
| shift = Math.copySign(Math.ceil(Math.abs(shift) / period), shift) * period; |
| coordinates[oi] += shift; |
| } |
| } |
| /* |
| * Convert back the shifted point to grid coordinates, then check again if the new point |
| * is inside the grid extent. If this is not the case, we will return the old position |
| * on the assumption that it will be less confusing to the user. |
| */ |
| gridToWraparound.inverse().transform(coordinates, s, coordinates, 0, 1); |
| j = 0; |
| do { |
| final int i = Long.numberOfTrailingZeros(outsideAxes); |
| final double c = coordinates[i]; |
| if (c < wraparoundExtent[j++] || c > wraparoundExtent[j++]) { |
| return position; |
| } |
| outsideAxes &= ~(1L << i); |
| } while (outsideAxes != 0); |
| /* |
| * Copy shifted coordinate values to the final `FractionalGridCoordinates`, except the NaN values. |
| * NaN values may exist if the given `point` has less dimensions than the grid geometry, in which |
| * case missing values have been replaced by `slice` values in the `target` array but not in the |
| * `coordinates` array. We want to keep the `slice` values in the `target` array. |
| * |
| * TODO: to be strict, we should skip the copy only if `slice.containsKey(i)` is true, because it |
| * could happen that a transform resulted in NaN values in other dimensions. But that check would |
| * be costly, so we avoid it for now. |
| */ |
| final double[] target = position.coordinates; |
| for (int i = target.length; --i >= 0;) { |
| final double value = coordinates[i]; |
| if (!Double.isNaN(value)) { |
| target[i] = value; |
| } |
| } |
| } |
| } |
| return position; |
| } |
| |
| /** |
| * Recomputes the {@link #inputToGrid} field. This method should be invoked when the transform |
| * has not yet been computed or became outdated because {@link #inputCRS} needs to be changed. |
| * |
| * @param crs the new value to assign to {@link #inputCRS}. |
| */ |
| private void setInputCRS(final CoordinateReferenceSystem crs) |
| throws FactoryException, NoninvertibleTransformException |
| { |
| final GridGeometry gridGeometry = coverage.getGridGeometry(); |
| MathTransform gridToCRS = gridGeometry.getGridToCRS(PixelInCell.CELL_CENTER); |
| MathTransform crsToGrid = gridToCRS.inverse(); |
| if (crs != null) { |
| final CoordinateReferenceSystem stepCRS = coverage.getCoordinateReferenceSystem(); |
| final GeographicBoundingBox areaOfInterest = gridGeometry.geographicBBox(); |
| try { |
| CoordinateOperation op = CRS.findOperation(crs, stepCRS, areaOfInterest); |
| crsToGrid = MathTransforms.concatenate(op.getMathTransform(), crsToGrid); |
| } catch (FactoryException main) { |
| /* |
| * Above block tried to compute a "CRS to grid" transform in the most direct way. |
| * It covers the usual case where the point has the required number of dimensions, |
| * and works better if the point has more dimensions (extra dimensions are ignored). |
| * The following block covers the opposite case, where the point does not have enough |
| * dimensions. We try to fill missing dimensions with the help of the `slice` map. |
| */ |
| final Map<Integer,Long> slice = getDefaultSlice(); |
| try { |
| CoordinateOperation op = CRS.findOperation(stepCRS, crs, areaOfInterest); |
| gridToCRS = MathTransforms.concatenate(gridToCRS, op.getMathTransform()); |
| final TransformSeparator ts = new TransformSeparator(gridToCRS); |
| final int crsDim = gridToCRS.getTargetDimensions(); |
| final int gridDim = gridToCRS.getSourceDimensions(); |
| int[] mandatory = new int[gridDim]; |
| int n = 0; |
| for (int i=0; i<gridDim; i++) { |
| if (!slice.containsKey(i)) { |
| mandatory[n++] = i; |
| } |
| } |
| mandatory = ArraysExt.resize(mandatory, n); |
| ts.addSourceDimensions(mandatory); // Retain grid dimensions having no default value. |
| ts.setSourceExpandable(true); // Retain more grid dimensions if they are required. |
| ts.addTargetDimensionRange(0, crsDim); // Force retention of all CRS dimensions. |
| gridToCRS = ts.separate(); |
| crsToGrid = gridToCRS.inverse(); // With less source dimensions, may be invertible now. |
| mandatory = ts.getSourceDimensions(); // Output grid dimensions computed by `crsToGrid`. |
| final int valueColumn = mandatory.length; // Matrix column where to write default values. |
| final MatrixSIS m = Matrices.createZero(gridDim+1, valueColumn+1); |
| m.setElement(gridDim, valueColumn, 1); |
| n = 0; |
| for (int j=0; j<gridDim; j++) { |
| if (Arrays.binarySearch(mandatory, j) >= 0) { |
| m.setElement(j, n++, 1); // Computed value to pass through. |
| } else { |
| final Long value = slice.get(j); |
| if (value == null) { |
| final GridExtent extent = gridGeometry.extent; |
| throw new FactoryException(Resources.format(Resources.Keys.NoNDimensionalSlice_3, |
| crsDim, extent.getAxisIdentification(j, j), extent.getSize(j))); |
| } |
| m.setElement(j, valueColumn, value); |
| } |
| } |
| crsToGrid = MathTransforms.concatenate(crsToGrid, MathTransforms.linear(m)); |
| } catch (RuntimeException | FactoryException | NoninvertibleTransformException ex) { |
| main.addSuppressed(ex); |
| throw main; |
| } |
| } |
| } |
| // Modify fields only after everything else succeeded. |
| position = new FractionalGridCoordinates.Position(crsToGrid.getTargetDimensions()); |
| inputCRS = crs; |
| inputToGrid = crsToGrid; |
| } |
| |
| /** |
| * Invoked when a recoverable exception occurred. |
| * Those exceptions must be minor enough that they can be silently ignored in most cases. |
| * |
| * @param caller the method where exception occurred. |
| * @param exception the exception that occurred. |
| */ |
| private static void recoverableException(final String caller, final TransformException exception) { |
| Logging.recoverableException(GridExtent.LOGGER, DefaultEvaluator.class, caller, exception); |
| } |
| } |