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 005c183  Allow `GridEvaluator` to handle wraparound axes.
005c183 is described below

commit 005c183b5fb071c314c0180793e8712f491aeaae
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Mon Feb 21 11:23:55 2022 +0100

    Allow `GridEvaluator` to handle wraparound axes.
---
 .../coverage/grid/FractionalGridCoordinates.java   |   2 +-
 .../apache/sis/coverage/grid/GridEvaluator.java    | 215 +++++++++++++++++++--
 .../sis/coverage/grid/GridCoverage2DTest.java      |  37 +++-
 .../apache/sis/geometry/WraparoundAdjustment.java  |  26 +--
 .../internal/referencing/WraparoundApplicator.java |   2 +-
 .../internal/referencing/WraparoundAxesFinder.java |  86 +++++++++
 6 files changed, 333 insertions(+), 35 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/FractionalGridCoordinates.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/FractionalGridCoordinates.java
index accd273..f243986 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/FractionalGridCoordinates.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/FractionalGridCoordinates.java
@@ -448,7 +448,7 @@ public class FractionalGridCoordinates implements 
GridCoordinates, Serializable
         long   validMin = 0;
         long   validMax = 0;
         double distance = 0;
-        for (int i=bounds.getDimension(); --i >= 0;) {
+        for (int i = bounds.getDimension(); --i >= 0;) {
             final long low  = bounds.getLow(i);
             final long high = bounds.getHigh(i);
             final double c  = coordinates[i];
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java
index b8ddb4b..f246d44 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridEvaluator.java
@@ -16,10 +16,12 @@
  */
 package org.apache.sis.coverage.grid;
 
+import java.util.Arrays;
 import java.awt.image.RenderedImage;
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.CoordinateOperation;
@@ -28,9 +30,13 @@ import org.opengis.coverage.CannotEvaluateException;
 import org.opengis.coverage.PointOutsideCoverageException;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
-import org.apache.sis.referencing.CRS;
+import org.apache.sis.internal.referencing.DirectPositionView;
+import org.apache.sis.internal.referencing.WraparoundAxesFinder;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.internal.system.Modules;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.logging.Logging;
 
 
 /**
@@ -70,10 +76,10 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
      * 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 crsToGrid;
+    private MathTransform sourceToGrid;
 
     /**
-     * Grid coordinates after {@link #crsToGrid} conversion.
+     * Grid coordinates after {@link #sourceToGrid} conversion.
      *
      * @see #toGridPosition(DirectPosition)
      */
@@ -93,6 +99,35 @@ public class GridEvaluator implements GridCoverage.Evaluator 
{
      */
     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 sometime the same transform than {@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;
+
     /**
      * Creates a new evaluator for the given coverage. This constructor is 
protected for allowing
      * {@link GridCoverage} subclasses to provide their own {@code 
GridEvaluator} implementations.
@@ -118,6 +153,80 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
     }
 
     /**
+     * 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.
+     *
+     * @since 1.2
+     */
+    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.
+     *
+     * @since 1.2
+     */
+    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()));
+                /*
+                 * `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.
@@ -172,7 +281,7 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
     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 plan, then interpolate. We would keep a 
value of 1 in the
+         * 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;
@@ -253,7 +362,7 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
      */
     final FractionalGridCoordinates.Position toGridPosition(final 
DirectPosition point) throws TransformException {
         final CoordinateReferenceSystem crs = 
point.getCoordinateReferenceSystem();
-        if (crs != sourceCRS || crsToGrid == null) {
+        if (crs != sourceCRS || sourceToGrid == null) {
             final GridGeometry gridGeometry = coverage.getGridGeometry();
             MathTransform tr = 
gridGeometry.getGridToCRS(PixelInCell.CELL_CENTER).inverse();
             if (crs != null) try {
@@ -264,15 +373,99 @@ public class GridEvaluator implements 
GridCoverage.Evaluator {
             } catch (FactoryException e) {
                 throw new TransformException(e.getMessage(), e);
             }
-            position  = new 
FractionalGridCoordinates.Position(tr.getTargetDimensions());
-            crsToGrid = tr;
-            sourceCRS = crs;
+            position     = new 
FractionalGridCoordinates.Position(tr.getTargetDimensions());
+            sourceCRS    = crs;
+            sourceToGrid = tr;
         }
-        final DirectPosition result = crsToGrid.transform(point, position);
+        /*
+         * 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 = sourceToGrid.transform(point, position);
         if (result != position) {
-            final double[] coordinates = result.getCoordinate();
-            System.arraycopy(coordinates, 0, position.coordinates, 0, 
position.coordinates.length);
+            // Should not happen, but be paranoiac.
+            final double[] coordinates = position.coordinates;
+            System.arraycopy(result.getCoordinate(), 0, coordinates, 0, 
coordinates.length);
+        }
+        /*
+         * If 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.
+         * The first step is to get the point closest to 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])) {
+                    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 than the coverage CRS or the source CRS, 
but not necessarily.
+             * Current version does not try to optimize by checking if `point` 
argument can be reused.
+             */
+            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 amount of 
periods. Modify the original
+                         * coordinate by applying that modified translation.
+                         */
+                        final int oi = i + s;
+                        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);
+                System.arraycopy(coordinates, 0, position.coordinates, 0, 
position.coordinates.length);
+            }
         }
         return position;
     }
+
+    /**
+     * 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(Logging.getLogger(Modules.RASTER), 
GridEvaluator.class, caller, exception);
+    }
 }
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
index 6a8be0a..bf19bde 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridCoverage2DTest.java
@@ -29,6 +29,7 @@ import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.coverage.PointOutsideCoverageException;
 import org.opengis.referencing.operation.MathTransform1D;
+import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.datum.PixelInCell;
 import org.apache.sis.coverage.SampleDimension;
 import org.apache.sis.geometry.DirectPosition2D;
@@ -36,7 +37,9 @@ import org.apache.sis.internal.coverage.j2d.RasterFactory;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
 import org.apache.sis.referencing.crs.HardCodedCRS;
+import org.apache.sis.referencing.operation.matrix.Matrix3;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -71,8 +74,16 @@ public strictfp class GridCoverage2DTest extends TestCase {
      * }
      */
     private GridCoverage createTestCoverage() {
+        return createTestCoverage(MathTransforms.identity(2));
+    }
+
+    /**
+     * Sames as {@link #createTestCoverage()} except that the "grid to CRS" 
transform can be specified.
+     * The domain of source grid indices is the [0 … 1] range in all 
dimensions.
+     */
+    private GridCoverage createTestCoverage(final MathTransform gridToCRS) {
         final GridGeometry grid = new GridGeometry(new GridExtent(GRID_SIZE, 
GRID_SIZE),
-                PixelInCell.CELL_CENTER, MathTransforms.identity(2), 
HardCodedCRS.WGS84);
+                PixelInCell.CELL_CENTER, gridToCRS, HardCodedCRS.WGS84);
 
         final MathTransform1D toUnits = (MathTransform1D) 
MathTransforms.linear(0.5, 100);
         final SampleDimension sd = new SampleDimension.Builder().setName("Some 
kind of height")
@@ -216,6 +227,30 @@ public strictfp class GridCoverage2DTest extends TestCase {
     }
 
     /**
+     * Tests {@link GridEvaluator#apply(DirectPosition)} with a wraparound on 
the longitude axis.
+     * This method tests a coordinate that would be outside the grid if 
wraparound was not applied.
+     *
+     * @todo Not yet implemented. One potential place where to implement this 
functionality could be
+     *       {@link GridEvaluator#toGridPosition(DirectPosition)}.
+     */
+    @Test
+    @DependsOnMethod("testEvaluator")
+    public void testEvaluatorWithWraparound() {
+        final Matrix3 gridToCRS = new Matrix3();
+        gridToCRS.m00 = 100;        // Scale
+        gridToCRS.m02 = 100;        // Offset
+        final GridEvaluator evaluator = 
createTestCoverage(MathTransforms.linear(gridToCRS)).evaluator();
+        evaluator.setWraparoundEnabled(true);
+        assertArrayEquals(new double[] {2}, evaluator.apply(new 
DirectPosition2D(100, 0)), STRICT);
+        assertArrayEquals(new double[] {5}, evaluator.apply(new 
DirectPosition2D(200, 0)), STRICT);
+        /*
+         * Following tests fail if wraparound is not applied by 
`GridEvaluator`.
+         */
+        assertArrayEquals(new double[] {5}, evaluator.apply(new 
DirectPosition2D(200 - 360, 0)), STRICT);
+        assertArrayEquals(new double[] {2}, evaluator.apply(new 
DirectPosition2D(100 - 360, 0)), STRICT);
+    }
+
+    /**
      * Verifies that calling {@link GridCoverage#render(GridExtent)} with a 
sub-extent (crop operation)
      * returns precisely the requested area, not a smaller or bigger one.
      */
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/geometry/WraparoundAdjustment.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/geometry/WraparoundAdjustment.java
index 94aed8e..505b5f5 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/geometry/WraparoundAdjustment.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/geometry/WraparoundAdjustment.java
@@ -19,9 +19,7 @@ package org.apache.sis.geometry;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.Envelope;
 import org.opengis.util.FactoryException;
-import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.operation.CoordinateOperation;
@@ -31,7 +29,7 @@ import org.apache.sis.referencing.CRS;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.internal.metadata.ReferencingServices;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
-import org.apache.sis.internal.referencing.WraparoundApplicator;
+import org.apache.sis.internal.referencing.WraparoundAxesFinder;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.ArgumentChecks;
@@ -262,24 +260,10 @@ public class WraparoundAdjustment {
                  * Replace the input CRS by an intermediate CRS where 
wraparound axes can be found.
                  * We try to select a CRS as close as possible (simplest 
transform) to the input.
                  */
-                if (crs instanceof ProjectedCRS) {
-                    final ProjectedCRS p = (ProjectedCRS) crs;
-                    crs = p.getBaseCRS();       // Geographic, so a wraparound 
axis certainly exists.
-                    inputToShiftable = 
p.getConversionFromBase().getMathTransform().inverse();
-                } else {
-                    // TODO: we should handle the case of CompoundCRS before 
to fallback on identity.
-                    inputToShiftable = 
MathTransforms.identity(ReferencingUtilities.getDimension(crs));
-                }
-                final CoordinateSystem cs = crs.getCoordinateSystem();
-                for (int i = cs.getDimension(); --i >= 0;) {
-                    final double period = WraparoundApplicator.range(cs, i);
-                    if (period > 0) {
-                        if (periods == null) {
-                            transformDomain(crs);
-                            periods = new double[i + 1];
-                        }
-                        periods[i] = period;
-                    }
+                final WraparoundAxesFinder f = new WraparoundAxesFinder(crs);
+                inputToShiftable = f.preferredToSpecified.inverse();
+                if ((periods = f.periods()) != null) {
+                    transformDomain(f.preferredCRS);
                 }
             }
         }
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundApplicator.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundApplicator.java
index 411f3bb..9f6a5c6 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundApplicator.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundApplicator.java
@@ -178,7 +178,7 @@ public final class WraparoundApplicator {
      * @param  dimension  dimension of the axis to test.
      * @return the wraparound range, or {@link Double#NaN} if none.
      */
-    public static double range(final CoordinateSystem cs, final int dimension) 
{
+    static double range(final CoordinateSystem cs, final int dimension) {
         final CoordinateSystemAxis axis = cs.getAxis(dimension);
         if (axis != null && 
RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning())) {
             double period = axis.getMaximumValue() - axis.getMinimumValue();
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundAxesFinder.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundAxesFinder.java
new file mode 100644
index 0000000..b381f50
--- /dev/null
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/WraparoundAxesFinder.java
@@ -0,0 +1,86 @@
+/*
+ * 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.internal.referencing;
+
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.operation.MathTransform;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+
+
+/**
+ * Finds the axes where wraparound may happen in a CRS. The search may be 
indirect.
+ * For example if the given CRS is projected, this class will search in 
geographic CRS.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+public final class WraparoundAxesFinder {
+    /**
+     * The CRS that may contain wraparound axes. Geographic CRS are preferred,
+     * but will be the CRS specified at construction time if we found nothing 
better.
+     */
+    public final CoordinateReferenceSystem preferredCRS;
+
+    /**
+     * The transform from {@link #preferredCRS} to the CRS specified at 
construction time.
+     * Never null but may be the identity transform.
+     */
+    public final MathTransform preferredToSpecified;
+
+    /**
+     * Searches wraparound axes in the specified CRS or its base CRS (if any).
+     *
+     * @param  crs  the CRS where to search for wraparound axes.
+     */
+    public WraparoundAxesFinder(CoordinateReferenceSystem crs) {
+        if (crs instanceof ProjectedCRS) {
+            final ProjectedCRS p = (ProjectedCRS) crs;
+            crs = p.getBaseCRS();       // Geographic, so a wraparound axis 
certainly exists.
+            preferredToSpecified = 
p.getConversionFromBase().getMathTransform();
+        } else {
+            // TODO: we should handle the case of CompoundCRS before to 
fallback on identity.
+            preferredToSpecified = 
MathTransforms.identity(ReferencingUtilities.getDimension(crs));
+        }
+        preferredCRS = crs;
+    }
+
+    /**
+     * Returns the range (maximum - minimum) of wraparound axes. For 
non-wraparound axes, the value is set to 0.
+     * The length of this array is the smallest length necessary for handing 
all wraparound axes.
+     * It may be smaller than the CRS dimension.
+     *
+     * @return periods of axes (0 for non-wraparound axes), or {@code null} if 
none.
+     */
+    public double[] periods() {
+        double[] periods = null;
+        final CoordinateSystem cs = preferredCRS.getCoordinateSystem();
+        for (int i = cs.getDimension(); --i >= 0;) {
+            final double period = WraparoundApplicator.range(cs, i);
+            if (period > 0) {
+                if (periods == null) {
+                    periods = new double[i + 1];
+                }
+                periods[i] = period;
+            }
+        }
+        return periods;
+    }
+}

Reply via email to