This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 472b2c0 More robust determination of whether an axis should be handled as a wraparound
axis.
472b2c0 is described below
commit 472b2c0f87d467b6e85c94fc12c86dc5ef2f23a2
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Sep 15 21:05:23 2020 +0200
More robust determination of whether an axis should be handled as a wraparound axis.
---
.../coverage/grid/CoordinateOperationFinder.java | 83 ++++++++++---------
.../sis/geometry/AbstractDirectPosition.java | 34 +++++++-
.../org/apache/sis/geometry/AbstractEnvelope.java | 5 --
.../internal/referencing/DirectPositionView.java | 22 -----
.../internal/referencing/WraparoundAdjustment.java | 6 +-
.../internal/referencing/WraparoundTransform.java | 93 ++++++++++++----------
.../referencing/WraparoundTransformTest.java | 10 +--
7 files changed, 133 insertions(+), 120 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/CoordinateOperationFinder.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/CoordinateOperationFinder.java
index c92cc6e..eab1616 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/CoordinateOperationFinder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/CoordinateOperationFinder.java
@@ -24,7 +24,6 @@ import org.opengis.geometry.Envelope;
import org.opengis.util.FactoryException;
import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.datum.PixelInCell;
-import org.opengis.referencing.cs.RangeMeaning;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.CoordinateOperation;
@@ -35,8 +34,8 @@ import org.apache.sis.internal.referencing.CoordinateOperations;
import org.apache.sis.internal.referencing.WraparoundTransform;
import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.geometry.AbstractDirectPosition;
import org.apache.sis.geometry.Envelopes;
-import org.apache.sis.geometry.GeneralDirectPosition;
import org.apache.sis.image.ImageProcessor;
import org.apache.sis.measure.Quantities;
import org.apache.sis.measure.Units;
@@ -308,18 +307,15 @@ final class CoordinateOperationFinder implements Supplier<double[]>
{
* which may be identity. A wraparound may be applied for keeping target coordinates
inside the expected
* target domain.
*/
- if (forwardOp == null) {
+apply: if (forwardOp == null) {
forwardOp = operation.getMathTransform();
final CoordinateSystem cs = operation.getTargetCRS().getCoordinateSystem();
-wraparound: if (mayRequireWraparound(cs)) {
- DirectPosition median = median(target);
- if (median == null) {
- median = median(source);
- if (median == null) break wraparound;
- median = forwardOp.transform(median, null);
- }
- forwardOp = WraparoundTransform.forDomainOfUse(forwardOp, cs, median);
+ DirectPosition median = median(target, null);
+ if (median == null) {
+ median = median(source, forwardOp);
+ if (median == null) break apply;
}
+ forwardOp = WraparoundTransform.forDomainOfUse(forwardOp, cs, median);
}
return MathTransforms.concatenate(tr, forwardOp);
}
@@ -415,48 +411,51 @@ check: if (gridToCRS != null) {
if (!isWraparoundApplied) {
isWraparoundApplied = true;
final CoordinateSystem cs = operation.getSourceCRS().getCoordinateSystem();
- if (mayRequireWraparound(cs)) {
- final DirectPosition median = median(source);
- if (median != null) {
- inverseOp = WraparoundTransform.forDomainOfUse(inverseOp, cs, median);
- crsToGrid = MathTransforms.concatenate(inverseOp, tr);
- }
+ final DirectPosition median = median(source, null);
+ if (median != null) {
+ inverseOp = WraparoundTransform.forDomainOfUse(inverseOp, cs, median);
+ crsToGrid = MathTransforms.concatenate(inverseOp, tr);
}
}
return crsToGrid;
}
/**
- * Returns {@code true} if a transform to the specified target coordinate system may
require handling
- * of wraparound axes. A {@code true} return value does not mean that wraparounds actually
happen;
- * it only means that more expensive checks will be required.
- */
- private static boolean mayRequireWraparound(final CoordinateSystem cs) {
- final int dimension = cs.getDimension();
- for (int i=0; i<dimension; i++) {
- if (RangeMeaning.WRAPAROUND.equals(cs.getAxis(i).getRangeMeaning())) {
- return true;
- }
- }
- return false;
- }
-
- /**
* Returns the point of interest converted to the Coordinate Reference System.
* If the grid does not define a point of interest or does not define a CRS,
* then this method returns {@code null}.
+ *
+ * @param grid the source or target grid providing the point of interest.
+ * @param forwardOp transform from source CRS to target CRS, or {@code null} if none.
*/
- private static DirectPosition median(final GridGeometry grid) throws TransformException
{
- if (grid.isDefined(GridGeometry.EXTENT | GridGeometry.GRID_TO_CRS)) {
- final double[] poi = grid.getExtent().getPointOfInterest();
- if (poi != null) {
- final MathTransform tr = grid.getGridToCRS(PixelInCell.CELL_CENTER);
- final GeneralDirectPosition median = new GeneralDirectPosition(tr.getTargetDimensions());
- tr.transform(poi, 0, median.coordinates, 0, 1);
- return median;
- }
+ private static DirectPosition median(final GridGeometry grid, final MathTransform forwardOp)
throws TransformException {
+ if (!grid.isDefined(GridGeometry.EXTENT | GridGeometry.GRID_TO_CRS)) {
+ return null;
}
- return null;
+ return new AbstractDirectPosition() {
+ /** The coordinates, computed when first needed. */
+ private double[] coordinates;
+
+ @Override public int getDimension() {return coordinates().length;}
+ @Override public double getOrdinate(int i) {return coordinates()[i];}
+
+ /** Returns the coordinate tuple. */
+ @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ private double[] coordinates() {
+ if (coordinates == null) try {
+ final double[] poi = grid.getExtent().getPointOfInterest();
+ MathTransform tr = grid.getGridToCRS(PixelInCell.CELL_CENTER);
+ if (forwardOp != null) {
+ tr = MathTransforms.concatenate(tr, forwardOp);
+ }
+ coordinates = new double[tr.getTargetDimensions()];
+ tr.transform(poi, 0, coordinates, 0, 1);
+ } catch (TransformException e) {
+ throw new BackingStoreException(e);
+ }
+ return coordinates;
+ }
+ };
}
/**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
index da831a5..2271ac2 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractDirectPosition.java
@@ -57,7 +57,7 @@ import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;
* serializable, is left to subclasses.</p>
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.3
* @module
*/
@@ -98,6 +98,21 @@ public abstract class AbstractDirectPosition extends FormattableObject
implement
}
/**
+ * Returns the coordinate reference system in which the coordinate tuple is given.
+ * May be {@code null} if this particular {@code DirectPosition} is included in a larger
object
+ * with such a reference to a {@linkplain CoordinateReferenceSystem coordinate reference
system}.
+ *
+ * <p>The default implementation returns {@code null}.
+ * Subclasses should override this method if the CRS can be provided.</p>
+ *
+ * @return the coordinate reference system, or {@code null}.
+ */
+ @Override
+ public CoordinateReferenceSystem getCoordinateReferenceSystem() {
+ return null;
+ }
+
+ /**
* Returns a sequence of numbers that hold the coordinate of this position in its reference
system.
*
* @return the coordinates.
@@ -112,6 +127,23 @@ public abstract class AbstractDirectPosition extends FormattableObject
implement
}
/**
+ * Sets the coordinate value along the specified dimension.
+ *
+ * <p>The default implementation throws {@link UnsupportedOperationException}.
+ * Subclasses need to override this method if this direct position is mutable.</p>
+ *
+ * @param dimension the dimension for the coordinate of interest.
+ * @param value the coordinate value of interest.
+ * @throws IndexOutOfBoundsException if the given index is negative or is equals or greater
+ * than the {@linkplain #getDimension() position dimension}.
+ * @throws UnsupportedOperationException if this direct position is immutable.
+ */
+ @Override
+ public void setOrdinate(int dimension, double value) {
+ throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1,
getClass()));
+ }
+
+ /**
* Sets this direct position to the given position. If the given position is
* {@code null}, then all coordinate values are set to {@link Double#NaN NaN}.
*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
index 2a40240..077acaa 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
@@ -1306,11 +1306,6 @@ public abstract class AbstractEnvelope extends FormattableObject implements
Enve
@Override public double getOrdinate(final int dimension) throws IndexOutOfBoundsException
{
return getMedian(dimension);
}
-
- /** Unsupported operation. */
- @Override public void setOrdinate(int dimension, double value) {
- throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1,
getClass()));
- }
}
/**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/DirectPositionView.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/DirectPositionView.java
index 1562c5d..1c115bd 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/DirectPositionView.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/DirectPositionView.java
@@ -17,7 +17,6 @@
package org.apache.sis.internal.referencing;
import java.util.Arrays;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.geometry.AbstractDirectPosition;
@@ -54,16 +53,6 @@ public abstract class DirectPositionView extends AbstractDirectPosition
{
}
/**
- * Returns {@code null} since there is no CRS associated with this position.
- *
- * @return {@code null}.
- */
- @Override
- public final CoordinateReferenceSystem getCoordinateReferenceSystem() {
- return null;
- }
-
- /**
* Returns the dimension given at construction time.
*
* @return number of dimensions.
@@ -74,17 +63,6 @@ public abstract class DirectPositionView extends AbstractDirectPosition
{
}
/**
- * Do not allow any change.
- *
- * @param dimension ignored.
- * @param value ignored.
- */
- @Override
- public final void setOrdinate(final int dimension, final double value) {
- throw new UnsupportedOperationException();
- }
-
- /**
* The double-precision version of {@link DirectPositionView}.
*/
public static final class Double extends DirectPositionView {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundAdjustment.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundAdjustment.java
index a84d79e..3f94331 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundAdjustment.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundAdjustment.java
@@ -116,9 +116,9 @@ public final class WraparoundAdjustment {
}
/**
- * Returns the range (maximum - minimum) of the given axis if it has wraparound meaning,
- * or {@link Double#NaN} otherwise. This method implements a fallback for the longitude
- * axis if it does not declare the minimum and maximum values as expected.
+ * Returns the range (maximum - minimum) of axis in specified dimension if it has wraparound
meaning,
+ * or {@link Double#NaN} otherwise. This method implements a fallback for longitude axis
if it does
+ * not declare the minimum and maximum values as expected.
*
* @param cs the coordinate system for which to get wraparound range.
* @param dimension dimension of the axis to test.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundTransform.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundTransform.java
index 19411d6..84439db 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundTransform.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundTransform.java
@@ -21,9 +21,7 @@ import java.util.function.Supplier;
import java.util.function.IntToDoubleFunction;
import org.opengis.util.FactoryException;
import org.opengis.geometry.DirectPosition;
-import org.opengis.referencing.cs.RangeMeaning;
import org.opengis.referencing.cs.CoordinateSystem;
-import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
@@ -37,9 +35,7 @@ import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.internal.system.Modules;
import org.apache.sis.internal.util.Numerics;
import org.apache.sis.io.wkt.Formatter;
-import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.collection.BackingStoreException;
@@ -144,13 +140,13 @@ public final class WraparoundTransform extends AbstractMathTransform
{
*
* @param op the coordinate operation for which to get the math transform.
* @return the math transform for the given coordinate operation.
+ * @throws TransformException if a coordinate can not be computed.
*/
- public static MathTransform forTargetCRS(final CoordinateOperation op) {
+ public static MathTransform forTargetCRS(final CoordinateOperation op) throws TransformException
{
MathTransform tr = op.getMathTransform();
final CoordinateSystem targetCS = op.getTargetCRS().getCoordinateSystem();
for (final int wraparoundDimension : CoordinateOperations.wrapAroundChanges(op))
{
- tr = MathTransforms.concatenate(tr, create(targetCS.getDimension(),
- wraparoundDimension, targetCS.getAxis(wraparoundDimension), Double.NaN));
+ tr = concatenate(tr, wraparoundDimension, targetCS, null);
}
return tr;
}
@@ -170,59 +166,72 @@ public final class WraparoundTransform extends AbstractMathTransform
{
* @param targetCS the target coordinate system.
* @param median the coordinates to put at the center of new ranges.
* @return the math transform with wraparound if needed.
+ * @throws TransformException if a coordinate can not be computed.
*/
- public static MathTransform forDomainOfUse(MathTransform tr, final CoordinateSystem targetCS,
final DirectPosition median) {
+ public static MathTransform forDomainOfUse(MathTransform tr, final CoordinateSystem targetCS,
+ final DirectPosition median) throws TransformException
+ {
final int dimension = targetCS.getDimension();
for (int i=0; i<dimension; i++) {
- final CoordinateSystemAxis axis = targetCS.getAxis(i);
- if (RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning())) {
- tr = MathTransforms.concatenate(tr,
- create(dimension, i, targetCS.getAxis(i), median.getOrdinate(i)));
- }
+ tr = concatenate(tr, i, targetCS, median);
}
return tr;
}
/**
- * Creates a transform with a "wrap around" behavior in the given dimension.
- * The wraparound is implemented by a concatenation of affine transform before
- * and after the {@link WraparoundTransform} instance.
+ * Concatenates the given transform with a "wrap around" transform if applicable.
+ * The wraparound is implemented by concatenations of affine transforms before and
+ * after the {@link WraparoundTransform} instance.
+ * If there is no wraparound to apply, then this method returns {@code tr} unchanged.
*
- * @param dimension the number of source and target dimensions.
- * @param wraparoundDimension the dimension where "wrap around" behavior apply.
- * @param axis the coordinate system axis in the "wrap around" dimension.
+ * @param tr the transform to concatenate with a wraparound transform.
+ * @param wraparoundDimension the dimension where "wrap around" behavior may apply.
+ * @param targetCS the target coordinate system.
* @param median the coordinate to put at the center of new range,
- * or {@link Double#NaN} for standard center of given axis.
+ * or {@code null} for standard axis center.
* @return the math transform with "wrap around" behavior in the specified dimension.
+ * @throws TransformException if a coordinate can not be computed.
*/
- private static MathTransform create(final int dimension, final int wraparoundDimension,
- final CoordinateSystemAxis axis, final double median)
+ private static MathTransform concatenate(final MathTransform tr, final int wraparoundDimension,
+ final CoordinateSystem targetCS, final DirectPosition median) throws TransformException
{
- ArgumentChecks.ensureStrictlyPositive("dimension", dimension);
- ArgumentChecks.ensureBetween("wraparoundDimension", 0, dimension - 1, wraparoundDimension);
- NoninvertibleTransformException cause = null;
- final double minimum = axis.getMinimumValue();
- final double maximum = axis.getMaximumValue();
- final double span = maximum - minimum;
- if (span > 0 && span != Double.POSITIVE_INFINITY) {
- final MatrixSIS m = Matrices.createIdentity(dimension + 1);
- m.setElement(wraparoundDimension, wraparoundDimension, span);
- m.setElement(wraparoundDimension, dimension, Double.isNaN(median) ? minimum :
median - span/2);
- final MathTransform denormalize = MathTransforms.linear(m);
- final WraparoundTransform wraparound = create(dimension, wraparoundDimension);
+ final double span = WraparoundAdjustment.range(targetCS, wraparoundDimension);
+ if (!(span > 0 && span != Double.POSITIVE_INFINITY)) {
+ return tr;
+ }
+ double start;
+ if (median != null) {
try {
+ start = median.getOrdinate(wraparoundDimension) + -0.5 * span;
+ } catch (BackingStoreException e) {
+ // Some implementations compute coordinates only when first needed.
+ throw e.unwrapOrRethrow(TransformException.class);
+ }
+ if (!Double.isFinite(start)) return tr;
+ } else {
+ start = targetCS.getAxis(wraparoundDimension).getMinimumValue();
+ if (!Double.isFinite(start)) {
/*
- * Do not use the 3-arguments method because we need to
- * control the order in which concatenation is applied.
+ * May happen if `WraparoundAdjustment.range(…)` recognized a longitude
axis
+ * despite the `CoordinateSystemAxis` not declarining minimum/maximum values.
+ * Use 0 as the range center (e.g. center of [-180 … 180]° longitude range).
*/
- return MathTransforms.concatenate(denormalize.inverse(),
- MathTransforms.concatenate(wraparound, denormalize));
- } catch (NoninvertibleTransformException e) {
- // Matrix is non-invertible only if the range given in argument is illegal.
- cause = e;
+ start = -0.5 * span;
}
}
- throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalRange_2, minimum,
maximum), cause);
+ final int dimension = tr.getTargetDimensions();
+ final MatrixSIS m = Matrices.createIdentity(dimension + 1);
+ m.setElement(wraparoundDimension, wraparoundDimension, span);
+ m.setElement(wraparoundDimension, dimension, start);
+ final MathTransform denormalize = MathTransforms.linear(m);
+ MathTransform wraparound = create(dimension, wraparoundDimension);
+ /*
+ * Do not use the 3-arguments method because we need to
+ * control the order in which concatenation is applied.
+ */
+ wraparound = MathTransforms.concatenate(denormalize.inverse(),
+ MathTransforms.concatenate(wraparound, denormalize));
+ return MathTransforms.concatenate(tr, wraparound);
}
/**
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WraparoundTransformTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WraparoundTransformTest.java
index f860c10..0aad7e3 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WraparoundTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/WraparoundTransformTest.java
@@ -20,13 +20,13 @@ import java.util.List;
import java.util.Collections;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.crs.HardCodedCRS;
import org.apache.sis.referencing.cs.AxesConvention;
import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.referencing.operation.matrix.Matrix3;
import org.apache.sis.referencing.operation.matrix.Matrix4;
-import org.apache.sis.referencing.operation.matrix.NoninvertibleMatrixException;
import org.apache.sis.test.TestCase;
import org.junit.Test;
@@ -65,10 +65,10 @@ public final strictfp class WraparoundTransformTest extends TestCase {
/**
* Tests wraparound on one axis.
*
- * @throws NoninvertibleMatrixException if the expected matrix can not be inverted.
+ * @throws TransformException if a coordinate can not be computed.
*/
@Test
- public void testOneAxis() throws NoninvertibleMatrixException {
+ public void testOneAxis() throws TransformException {
final AbstractCoordinateOperation op = new AbstractCoordinateOperation(
Collections.singletonMap(AbstractCoordinateOperation.NAME_KEY, "Wrapper"),
HardCodedCRS.WGS84_φλ.forConvention(AxesConvention.POSITIVE_RANGE),
@@ -108,10 +108,10 @@ public final strictfp class WraparoundTransformTest extends TestCase
{
* transform between them. The absence of separation between the two {@link WraparoundTransform}s
is an
* indirect test of {@link WraparoundTransform#tryConcatenate(boolean, MathTransform,
MathTransformFactory)}.
*
- * @throws NoninvertibleMatrixException if the expected matrix can not be inverted.
+ * @throws TransformException if a coordinate can not be computed.
*/
@Test
- public void testTwoAxes() throws NoninvertibleMatrixException {
+ public void testTwoAxes() throws TransformException {
final AbstractCoordinateOperation op = new AbstractCoordinateOperation(
Collections.singletonMap(AbstractCoordinateOperation.NAME_KEY, "Wrapper"),
HardCodedCRS.WGS84_3D_TIME.forConvention(AxesConvention.POSITIVE_RANGE),
|