sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/02: Add a `DISPLAY` enumeration value in `GridOrientation`.
Date Thu, 24 Sep 2020 14:44:54 GMT
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

commit dceffce01b58f1901345ca9ca6bb112726b3c02a
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Sep 24 16:44:23 2020 +0200

    Add a `DISPLAY` enumeration value in `GridOrientation`.
---
 .../org/apache/sis/coverage/grid/GridExtent.java   |  2 +-
 .../org/apache/sis/coverage/grid/GridGeometry.java | 55 +++++++++++++++++++---
 .../apache/sis/coverage/grid/GridOrientation.java  | 29 ++++++++++--
 .../apache/sis/coverage/grid/GridExtentTest.java   | 10 ++++
 .../apache/sis/coverage/grid/GridGeometryTest.java | 35 ++++++++++++++
 .../sis/internal/referencing/AxisDirections.java   | 24 +++++-----
 .../internal/referencing/AxisDirectionsTest.java   | 38 ++++++++++++++-
 7 files changed, 170 insertions(+), 23 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 6b8f174..51177bc 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -1367,7 +1367,7 @@ public class GridExtent implements GridEnvelope, LenientComparable,
Serializable
                 scale.set(coordinates[i + srcDim]);
                 scale.subtract(offset);
                 scale.add(1);                                   // == getSize(j) but without
overflow.
-                scale.inverseDivideGuessError(env.getSpan(i));  // == (envelope span) / (grid
size).
+                scale.inverseDivideGuessError(env.getSpan(j));  // == (envelope span) / (grid
size).
                 if (flip) scale.negate();
                 if (!offset.isZero()) {                         // Use `if` for keeping the
value if scale is NaN.
                     offset.multiply(scale);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index 5ae3434..d5c52e1 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -45,6 +45,8 @@ import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.geometry.ImmutableEnvelope;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.referencing.crs.AbstractCRS;
+import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
@@ -52,6 +54,7 @@ import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.PassThroughTransform;
 import org.apache.sis.internal.referencing.DirectPositionView;
 import org.apache.sis.internal.referencing.TemporalAccessor;
+import org.apache.sis.internal.referencing.AxisDirections;
 import org.apache.sis.internal.metadata.ReferencingServices;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.feature.Resources;
@@ -586,6 +589,11 @@ public class GridGeometry implements LenientComparable, Serializable
{
      * For grid geometries describing preexisting data, it is safer and more flexible to
use one of
      * the constructors expecting a {@link MathTransform} argument.</p>
      *
+     * <h4>Dimension order</h4>
+     * The given envelope shall always declare dimensions in same order than the given extent.
+     * This constructor may reorder CRS axes if {@code orientation} is {@link GridOrientation#DISPLAY},
+     * but in such case this constructor will derive itself an envelope and CRS with the
reordered axes.
+     *
      * @param  extent       the valid extent of grid coordinates, or {@code null} if unknown.
      * @param  envelope     the envelope together with CRS of the "real world" coordinates,
or {@code null} if unknown.
      * @param  orientation  high-level description of desired characteristics of the {@code
gridToCRS} transform.
@@ -599,19 +607,53 @@ public class GridGeometry implements LenientComparable, Serializable
{
         this.extent = extent;
         nonLinears  = 0;
         /*
+         * Potentially change axis order and orientation according the given `GridOrientation`
(which may be null).
+         * Current code assumes that units of measurement are unchanged, which should be
true for CRSs built using
+         * AxisConvention.DISPLAY_ORIENTED.
+         */
+        long flip = 0;                      // Bitmask specifying whether to reverse axis
in each dimension.
+        ImmutableEnvelope target = null;    // May have different axis order than the specified
`envelope` CRS.
+        int[] sourceDimensions = null;      // Indices in source envelope of axes colinear
with the target envelope.
+        if (envelope != null && orientation == GridOrientation.DISPLAY) {
+            final AbstractCRS sourceCRS = AbstractCRS.castOrCopy(envelope.getCoordinateReferenceSystem());
+            if (sourceCRS != null) {
+                final AbstractCRS targetCRS = sourceCRS.forConvention(AxesConvention.DISPLAY_ORIENTED);
+                if (targetCRS != sourceCRS) {
+                    final CoordinateSystem sourceCS = sourceCRS.getCoordinateSystem();
+                    final CoordinateSystem targetCS = targetCRS.getCoordinateSystem();
+                    sourceDimensions = AxisDirections.indicesOfColinear(sourceCS, targetCS);
+                    if (sourceDimensions != null) {
+                        final double[] lowerCorner = new double[sourceDimensions.length];
+                        final double[] upperCorner = new double[sourceDimensions.length];
+                        for (int i=0; i < sourceDimensions.length; i++) {
+                            final int s = sourceDimensions[i];
+                            lowerCorner[i] = envelope.getMinimum(s);
+                            upperCorner[i] = envelope.getMaximum(s);
+                            if (sourceCS.getAxis(s).getDirection() != targetCS.getAxis(i).getDirection())
{
+                                flip |= Numerics.bitmask(i);
+                            }
+                        }
+                        target = new ImmutableEnvelope(lowerCorner, upperCorner, targetCRS);
+                    }
+                }
+            }
+        }
+        if (target == null) {
+            target = ImmutableEnvelope.castOrCopy(envelope);
+        }
+        /*
          * If the envelope contains no useful information, then the grid extent is mandatory.
          * We do that for forcing grid geometries to contain at least one information.
          */
         boolean nilEnvelope = true;
-        final ImmutableEnvelope env = ImmutableEnvelope.castOrCopy(envelope);
-        if (env == null || ((nilEnvelope = env.isAllNaN()) && env.getCoordinateReferenceSystem()
== null)) {
-            if (env != null && nilEnvelope) {
+        if (target == null || ((nilEnvelope = target.isAllNaN()) && target.getCoordinateReferenceSystem()
== null)) {
+            if (target != null && nilEnvelope) {
                 throw new NullArgumentException(Errors.format(Errors.Keys.UnspecifiedCRS));
             }
             ArgumentChecks.ensureNonNull("extent", extent);
             this.envelope = null;
         } else {
-            this.envelope = env;
+            this.envelope = target;
             if (extent != null && !nilEnvelope) {
                 /*
                  * If we have both the extent and an envelope with at least one non-NaN coordinates,
@@ -620,13 +662,14 @@ public class GridGeometry implements LenientComparable, Serializable
{
                  * matrix multiplication. Use double-double arithmetic everywhere.
                  */
                 ArgumentChecks.ensureNonNull("orientation", orientation);
-                final MatrixSIS affine = extent.cornerToCRS(env, orientation.flip, null);
+                final MatrixSIS affine = extent.cornerToCRS(target, orientation.flip ^ flip,
sourceDimensions);
                 cornerToCRS = MathTransforms.linear(affine);
                 final int srcDim = cornerToCRS.getSourceDimensions();       // Translation
column in matrix.
                 final int tgtDim = cornerToCRS.getTargetDimensions();       // Number of
matrix rows before last row.
                 resolution = new double[tgtDim];
                 for (int j=0; j<tgtDim; j++) {
-                    final DoubleDouble scale  = (DoubleDouble) affine.getNumber(j, j);
+                    final int i = (sourceDimensions != null) ? sourceDimensions[j] : j;
+                    final DoubleDouble scale  = (DoubleDouble) affine.getNumber(j, i);
                     final DoubleDouble offset = (DoubleDouble) affine.getNumber(j, srcDim);
                     resolution[j] = Math.abs(scale.doubleValue());
                     scale.multiply(0.5);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridOrientation.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridOrientation.java
index cff41ae..3d10a4d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridOrientation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridOrientation.java
@@ -18,6 +18,7 @@ package org.apache.sis.coverage.grid;
 
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.datum.PixelInCell;
+import org.apache.sis.referencing.cs.AxesConvention;
 
 
 /**
@@ -29,7 +30,10 @@ import org.opengis.referencing.datum.PixelInCell;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
- * @since   1.1
+ *
+ * @see GridGeometry#GridGeometry(GridExtent, Envelope, GridOrientation)
+ *
+ * @since 1.1
  * @module
  */
 public enum GridOrientation {
@@ -66,11 +70,28 @@ public enum GridOrientation {
      * instead of down as commonly expected with rendered images.
      * This {@code REFLECTION_Y} value matches the common usage for grids backed by images.</p>
      */
-    REFLECTION_Y(2);
+    REFLECTION_Y(2),
 
-    /*
-     * TODO: add DISPLAY. The difference compared to REFLECTION_Y is that it may change CRS
axes.
+    /**
+     * CRS axes are reordered and oriented toward directions commonly used for displaying
purpose.
+     * This orientation can be used for deriving a coordinate reference system with the
+     * <i>(<var>longitude</var>, <var>latitude</var>)</i>
or <i>(<var>x</var>,<var>y</var>)</i> axis order.
+     * Grid axes order is unchanged.
+     * An example of {@code gridToCRS} transform obtained by this orientation is as below
+     * (the exact matrix depends on the CRS axes):
+     *
+     * {@preformat math
+     *   ┌                 ┐
+     *   │  0   Sx  0   Tx │
+     *   │ −Sy  0   0   Ty │
+     *   │  0   0   Sz  Tz │
+     *   │  0   0   0   1  │
+     *   └                 ┘
+     * }
+     *
+     * @see AxesConvention#DISPLAY_ORIENTED
      */
+    DISPLAY(2);
 
     /**
      * Bitmask of axes to flip.
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
index 1a67174..b1cc369 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -295,6 +295,16 @@ public final strictfp class GridExtentTest extends TestCase {
                 0.5,  0,   50,
                 0,   -2,   20,
                 0,    0,    1), extent.cornerToCRS(aoi, 2, null), STRICT);
+        /*
+         * Swap axis order. The {1,0} indices apply to grid dimensions, not to CRS dimensions.
+         * Verification:  x  =  0.375 × −25 + 49.375  =  40  (the minimum value declared
in envelope).
+         *                y  =  2.667 × −20 + 43.333  ≈ −10  (idem).
+         */
+        assertMatrixEquals("cornerToCRS", new Matrix3(
+                0,                   0.375,   49.375,
+                2.6666666666666667,  0,       43.333333333333333,
+                0,                   0,        1),
+                extent.cornerToCRS(aoi, 0, new int[] {1,0}), 1E-15);
     }
 
     /**
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
index 42acf36..07ebd7e 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridGeometryTest.java
@@ -369,6 +369,41 @@ public final strictfp class GridGeometryTest extends TestCase {
         assertArrayEquals("resolution", new double[] {0.5, 2}, grid.getResolution(false),
STRICT);
         assertTrue("isConversionLinear", grid.isConversionLinear(0, 1));
         verifyGridToCRS(grid);
+        /*
+         * The use of `DISPLAY` mode in this particular case should be equivalent ro `REFLECTION_Y`.
+         */
+        assertEquals(grid, new GridGeometry(extent, aoi, GridOrientation.DISPLAY));
+    }
+
+    /**
+     * Tests the construction from a geospatial envelope and an extent with reordering of
axes
+     * for matching display convention.
+     *
+     * @see GridExtentTest#testCornerToCRS()
+     */
+    @Test
+    public void testFromExtentAndDisplayEnvelope() {
+        final GeneralEnvelope aoi = new GeneralEnvelope(HardCodedCRS.WGS84_φλ);
+        aoi.setRange(1,  40, 55);
+        aoi.setRange(0, -10, 70);
+        final GridExtent extent = new GridExtent(null,
+                new long[] {-25, -20},
+                new long[] { 15,  10}, false);
+        /*
+         * Same case than the one tested by `testFromExtentAndEnvelope()`,
+         * but with axis order swapped.
+         */
+        GridGeometry grid = new GridGeometry(extent, aoi, GridOrientation.DISPLAY);
+        Matrix matrix = MathTransforms.getMatrix(grid.getGridToCRS(PixelInCell.CELL_CORNER));
+        assertMatrixEquals("cornerToCRS", new Matrix3(
+                0,  0.5,   50,
+               -2,    0,   20,
+                0,    0,    1), matrix, STRICT);
+
+        // Verify other computed properties.
+        assertArrayEquals("resolution", new double[] {0.5, 2}, grid.getResolution(false),
STRICT);
+        assertTrue("isConversionLinear", grid.isConversionLinear(0, 1));
+        verifyGridToCRS(grid);
     }
 
     /**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
index dfbdeb4..9f512dc 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AxisDirections.java
@@ -29,6 +29,7 @@ import org.apache.sis.internal.metadata.NameToIdentifier;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Characters;
 import org.apache.sis.util.Utilities;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.measure.Units;
@@ -492,10 +493,10 @@ public final class AxisDirections extends Static {
      *   <li>Otherwise if a sequence of {@code cs} axes have similar names than the
{@code subCS} axes (as determined
      *       by {@linkplain org.apache.sis.referencing.IdentifiedObjects#isHeuristicMatchForName
heuristic match},
      *       that sequence has precedence.</li>
-     *   <li>Otherwise the index of the first sequence if returned, regardless axis
names.</li>
+     *   <li>Otherwise the index of the first sequence is returned, regardless axis
names.</li>
      * </ol>
      *
-     * Note that colinear axes are normally not allowed, except if the case of {@link org.opengis.referencing.crs.TemporalCRS}
+     * Note that colinear axes are normally not allowed, except in the case of {@link org.opengis.referencing.crs.TemporalCRS}
      * when one time axis is the runtime (the date where a numerical model has been executed)
and the other time axis is the
      * forecast time (the date at which a prevision is made).
      *
@@ -551,20 +552,21 @@ next:       for (int i=0; i <= limit; i++) {
      *
      * @param  cs     the coordinate system which contains all axes, or {@code null}.
      * @param  subCS  the coordinate system to search into {@code cs}.
-     * @return the sequence of axes colinear with {@code subCS} axes, or {@code null} if
none.
+     * @return indices in {@code cs} of axes colinear with {@code subCS} axes in the order
they appear in {@code subCS},
+     *         or {@code null} if at least one {@code subCS} axis can not be mapped to a
{@code cs} axis.
      *
      * @since 1.1
      */
     public static int[] indicesOfColinear(final CoordinateSystem cs, final CoordinateSystem
subCS) {
-        final int[] indices = new int[subCS.getDimension()];
+        final int dim = subCS.getDimension();
         final int index = indexOfColinear(cs, subCS);           // More robust than fallback
below.
-        for (int i=0; i<indices.length; i++) {
-            if (index >= 0) {
-                indices[i] = index + i;
-            } else {
-                if ((indices[i] = indexOfColinear(cs, subCS.getAxis(i).getDirection())) <
0) {
-                    return null;
-                }
+        if (index >= 0) {
+            return ArraysExt.range(index, index + dim);
+        }
+        final int[] indices = new int[dim];
+        for (int i=0; i<dim; i++) {
+            if ((indices[i] = indexOfColinear(cs, subCS.getAxis(i).getDirection())) <
0) {
+                return null;
             }
         }
         return indices;
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java
index c711d32..5df9414 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/AxisDirectionsTest.java
@@ -43,7 +43,7 @@ import static org.apache.sis.internal.referencing.AxisDirections.COUNTER_CLOCKWI
  * {@code sis-referencing} module because those tests use {@link HardCodedAxes} constants.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.4
  * @module
  */
@@ -421,6 +421,42 @@ public final strictfp class AxisDirectionsTest extends TestCase {
     }
 
     /**
+     * Tests {@link AxisDirections#indicesOfColinear(CoordinateSystem, CoordinateSystem)}.
+     *
+     * @since 1.1
+     */
+    @Test
+    public void testIndicesOfColinear() {
+        assertArrayEquals(new int[] {0, 1}, AxisDirections.indicesOfColinear(
+                HardCodedCS.GEODETIC_3D,
+                HardCodedCS.GEODETIC_2D));
+
+        assertArrayEquals(new int[] {1, 0}, AxisDirections.indicesOfColinear(
+                HardCodedCS.GEODETIC_φλ,
+                HardCodedCS.GEODETIC_2D));
+
+        assertArrayEquals(new int[] {1, 0}, AxisDirections.indicesOfColinear(
+                HardCodedCS.GEODETIC_3D,
+                HardCodedCS.GEODETIC_φλ));
+
+        assertArrayEquals(new int[] {2}, AxisDirections.indicesOfColinear(
+                HardCodedCS.GEODETIC_3D,
+                HardCodedCS.ELLIPSOIDAL_HEIGHT));
+
+        assertArrayEquals(new int[] {2}, AxisDirections.indicesOfColinear(
+                HardCodedCS.GEODETIC_3D,
+                HardCodedCS.DEPTH));
+
+        assertArrayEquals(null, AxisDirections.indicesOfColinear(
+                HardCodedCS.GEODETIC_3D,
+                HardCodedCS.DAYS));
+
+        assertArrayEquals(null, AxisDirections.indicesOfColinear(
+                HardCodedCS.GEODETIC_2D,
+                HardCodedCS.GEODETIC_3D));
+    }
+
+    /**
      * Tests {@link AxisDirections#suggestAbbreviation(String, AxisDirection, Unit)}.
      *
      * @since 0.6


Mime
View raw message