sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Better filtering in CoordinateOperationRegistry about whether to extract the horizontal component of a 3D CRS. This change allows ResampledGridCoverageTest.crs4D_to_crs3D() to pass.
Date Sat, 30 May 2020 16:02:56 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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 658b0e8  Better filtering in CoordinateOperationRegistry about whether to extract
the horizontal component of a 3D CRS. This change allows ResampledGridCoverageTest.crs4D_to_crs3D()
to pass.
658b0e8 is described below

commit 658b0e8c498d5748c9acbff17bc9c6ebc3092cc3
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat May 30 18:00:29 2020 +0200

    Better filtering in CoordinateOperationRegistry about whether to extract the horizontal
component of a 3D CRS.
    This change allows ResampledGridCoverageTest.crs4D_to_crs3D() to pass.
---
 .../coverage/grid/ResampledGridCoverageTest.java   |   1 -
 .../operation/CoordinateOperationRegistry.java     | 177 ++++++++++++++++-----
 2 files changed, 136 insertions(+), 42 deletions(-)

diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
index 74047c4..c4a3156 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
@@ -534,7 +534,6 @@ public final strictfp class ResampledGridCoverageTest extends TestCase
{
      * @throws TransformException if some coordinates can not be transformed to the target
grid geometry.
      */
     @Test
-    @org.junit.Ignore("To debug")
     public void crs4D_to_crs3D() throws TransformException {
         final GridGeometry target3D = createGridGeometryND(HardCodedCRS.WGS84_3D, 0, 1, 2,
3, false);
         final GridCoverage source4D = createCoverageND(true);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index 5a0aadd..254a06a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -23,6 +23,7 @@ import java.util.ListIterator;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.Objects;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
@@ -102,7 +103,7 @@ import org.apache.sis.util.resources.Vocabulary;
  * then {@link CoordinateOperationFinder} will use its own fallback.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.7
  * @module
  */
@@ -159,6 +160,9 @@ class CoordinateOperationRegistry {
     /**
      * The object to use for finding authority codes, or {@code null} if none.
      * An instance is fetched at construction time from the {@link #registry} if possible.
+     *
+     * @see #authorityCodes
+     * @see #findCode(CoordinateReferenceSystem)
      */
     private final IdentifiedObjectFinder codeFinder;
 
@@ -211,6 +215,15 @@ class CoordinateOperationRegistry {
     private Predicate<CoordinateOperation> filter;
 
     /**
+     * Authority codes found for CRS. This is a cache for {@link #findCode(CoordinateReferenceSystem)}.
+     * This map may be non-empty only if {@link #codeFinder} is non-null.
+     *
+     * @see #codeFinder
+     * @see #findCode(CoordinateReferenceSystem)
+     */
+    private final Map<CoordinateReferenceSystem, List<String>> authorityCodes;
+
+    /**
      * Creates a new instance for the given factory and context.
      *
      * @param  registry  the factory to use for creating operations as defined by authority.
@@ -227,6 +240,7 @@ class CoordinateOperationRegistry {
         this.factory  = factory;
         factorySIS    = (factory instanceof DefaultCoordinateOperationFactory)
                         ? (DefaultCoordinateOperationFactory) factory : CoordinateOperations.factory();
+        Map<CoordinateReferenceSystem, List<String>> authorityCodes = Collections.emptyMap();
         IdentifiedObjectFinder codeFinder = null;
         if (registry != null) {
             if (registry instanceof GeodeticAuthorityFactory) {
@@ -238,9 +252,11 @@ class CoordinateOperationRegistry {
             }
             if (codeFinder != null) {
                 codeFinder.setIgnoringAxes(true);
+                authorityCodes = new IdentityHashMap<>(10);
             }
         }
-        this.codeFinder = codeFinder;
+        this.codeFinder     = codeFinder;
+        this.authorityCodes = authorityCodes;
         if (context != null) {
             areaOfInterest  = context.getAreaOfInterest();
             desiredAccuracy = context.getDesiredAccuracy();
@@ -270,26 +286,98 @@ class CoordinateOperationRegistry {
      * This method does not trust the code given by the user in its CRS - we verify it.
      * This method may return codes even if the axis order does not match;
      * it will be caller's responsibility to make necessary adjustments.
+     *
+     * @return authority codes for the given CRS, or an empty list if none.
+     *         <b>Do not modify</b> since this list is cached.
      */
     private List<String> findCode(final CoordinateReferenceSystem crs) throws FactoryException
{
-        final List<String> codes = new ArrayList<>();
-        if (codeFinder != null) {
-            for (final IdentifiedObject candidate : codeFinder.find(crs)) {
-                final Identifier identifier = IdentifiedObjects.getIdentifier(candidate,
registry.getAuthority());
-                if (identifier != null) {
-                    final String code = identifier.getCode();
-                    if (Utilities.deepEquals(candidate, crs, ComparisonMode.APPROXIMATE))
{
-                        codes.add(0, code);     // If axis order match, give precedence to
that CRS.
-                    } else {
-                        codes.add(code);
+        List<String> codes = authorityCodes.get(crs);
+        if (codes == null) {
+            codes = new ArrayList<>();
+            if (codeFinder != null) {
+                for (final IdentifiedObject candidate : codeFinder.find(crs)) {
+                    final Identifier identifier = IdentifiedObjects.getIdentifier(candidate,
registry.getAuthority());
+                    if (identifier != null) {
+                        final String code = identifier.getCode();
+                        if (Utilities.deepEquals(candidate, crs, ComparisonMode.APPROXIMATE))
{
+                            codes.add(0, code);     // If axis order match, give precedence
to that CRS.
+                        } else {
+                            codes.add(code);
+                        }
                     }
                 }
+                authorityCodes.put(crs, codes);
             }
         }
         return codes;
     }
 
     /**
+     * Strategies for decomposing a three-dimensional CRS into components that may be present
in EPSG database.
+     * The database may have no entries for a coordinate operation between a pair of 3D CRS
with (x,y,z) axes,
+     * while entries for the horizontal components (2D CRS with (x,y) axes) may exist. This
class enumerates
+     * which decompositions to apply on the 3D CRS before searching in EPSG database. Those
decompositions will
+     * be applied in the order they are enumerated.
+     */
+    private enum Decomposition {
+        /**
+         * Search for a coordinate operation with the CRS as specified, without any reduction.
+         * It should be the first strategy to try before anything else.
+         */
+        NONE(false, false),
+
+        /**
+         * If the target CRS is three-dimensional, make it two-dimensional and search again
in database.
+         * The source CRS is left as-is; it may be two- or three-dimensional.
+         * This strategy avoid the last of source coordinates since they may be used in calculation.
+         */
+        HORIZONTAL_TARGET(false, true),
+
+        /**
+         * Make both source and target CRS two-dimensional and search again in the database.
+         * This strategy should not be applied before {@link #HORIZONTAL_TARGET} because
we
+         * want to use the <var>z</var> coordinate value to be used if possible.
+         */
+        HORIZONTAL(true, true),
+
+        /**
+         * If the source CRS is three-dimensional, make it two-dimensional and search again
in database.
+         * The target CRS is left as-is; it may be two- or three-dimensional. This strategy
is rarely
+         * applicable because it would cause the <var>z</var> coordinate value
to appear from nowhere,
+         * but we nevertheless test it as a matter of principle.
+         */
+        HORIZONTAL_SOURCE(true, false);
+
+        /**
+         * Whether to extract the horizontal component of the source or target CRS.
+         */
+        final boolean source, target;
+
+        /**
+         * Creates a new decomposition strategy.
+         */
+        private Decomposition(final boolean source, final boolean target) {
+            this.source = source;
+            this.target = target;
+        }
+
+        /**
+         * Returns the horizontal component to search in the database, or {@code null} if
no search should be done.
+         * This method returns {@code null} if the horizontal component is not different
then the given CRS,
+         * in which case searching that CRS in the database would be redundant with previous
searches.
+         * It also returns {@code null} if the CRS is a compound CRS, in which case the the
separation
+         * in single CRS should be done by the {@link CoordinateOperationFinder} subclass.
+         */
+        static SingleCRS horizontal(final CoordinateReferenceSystem crs) {
+            if (crs instanceof SingleCRS) {
+                final SingleCRS sep = CRS.getHorizontalComponent(crs);
+                if (sep != crs) return sep;             // May be null.
+            }
+            return null;
+        }
+    }
+
+    /**
      * Finds or infers operations for conversions or transformations between two coordinate
reference systems.
      * {@code CoordinateOperationRegistry} implements the <cite>late-binding</cite>
approach (see definition
      * of terms in class javadoc) by extracting the authority codes from the supplied {@code
sourceCRS} and
@@ -312,27 +400,32 @@ class CoordinateOperationRegistry {
                                                       final CoordinateReferenceSystem targetCRS)
             throws FactoryException
     {
-        CoordinateReferenceSystem source = sourceCRS;
-        CoordinateReferenceSystem target = targetCRS;
-        for (int combine=0; ; combine++) {
+        SingleCRS source2D = null;
+        SingleCRS target2D = null;
+        boolean sourceCached = false;
+        boolean targetCached = false;
+        for (final Decomposition decompose : Decomposition.values()) {
             /*
              * First, try directly the provided (sourceCRS, targetCRS) pair. If that pair
does not work,
              * try to use different combinations of user-provided CRS and two-dimensional
components of
              * those CRS. The code below assumes that the user-provided CRS are three-dimensional,
but
-             * actually it works for other kind of CRS too without testing twice the same
combinations.
+             * actually it works for two-dimensional CRS too without testing twice the same
combinations.
              */
-            switch (combine) {
-                case 0:  break;                                                         //
3D → 3D
-                case 1:  target = CRS.getHorizontalComponent(targetCRS);                //
3D → 2D
-                         if (target == targetCRS) continue;
-                         break;
-                case 2:  source = CRS.getHorizontalComponent(sourceCRS);                //
2D → 2D
-                         if (source == sourceCRS) continue;
-                         break;
-                case 3:  if (source == sourceCRS || target == targetCRS) continue;
-                         target = targetCRS;                                            //
2D → 3D
-                         break;
-                default: return Collections.emptyList();
+            CoordinateReferenceSystem source = sourceCRS;
+            CoordinateReferenceSystem target = targetCRS;
+            if (decompose.source) {
+                if (!sourceCached) {
+                    sourceCached = true;
+                    source2D = Decomposition.horizontal(sourceCRS);
+                }
+                source = source2D;
+            }
+            if (decompose.target) {
+                if (!targetCached) {
+                    targetCached = true;
+                    target2D = Decomposition.horizontal(targetCRS);
+                }
+                target = target2D;
             }
             if (source != null && target != null) try {
                 final List<CoordinateOperation> operations = search(source, target);
@@ -341,11 +434,10 @@ class CoordinateOperationRegistry {
                      * Found an operation. If we had to extract the horizontal part of some
3D CRS, then we
                      * need to modify the coordinate operation in order to match the new
number of dimensions.
                      */
-                    if (combine != 0) {
+                    if (decompose != Decomposition.NONE) {
                         for (int i=operations.size(); --i >= 0;) {
                             CoordinateOperation operation = operations.get(i);
-                            operation = propagateVertical(sourceCRS, source != sourceCRS,
-                                                          targetCRS, target != targetCRS,
operation);
+                            operation = propagateVertical(sourceCRS, targetCRS, operation,
decompose);
                             if (operation != null) {
                                 operation = complete(operation, sourceCRS, targetCRS);
                                 operations.set(i, operation);
@@ -367,6 +459,7 @@ class CoordinateOperationRegistry {
                 throw new FactoryException(message, e);
             }
         }
+        return Collections.emptyList();
     }
 
     /**
@@ -866,19 +959,21 @@ class CoordinateOperationRegistry {
      * <cite>best effort</cite> basis. In any cases, the {@link #complete} method
should be invoked
      * after this one in order to ensure that the source and target CRS are the expected
ones.</p>
      *
-     * @param  sourceCRS the potentially three-dimensional source CRS
-     * @param  source3D  {@code true} for adding ellipsoidal height in source coordinates.
-     * @param  targetCRS the potentially three-dimensional target CRS
-     * @param  target3D  {@code true} for adding ellipsoidal height in target coordinates.
-     * @param  operation the original (typically two-dimensional) coordinate operation.
+     * @param  sourceCRS  the potentially three-dimensional source CRS
+     * @param  targetCRS  the potentially three-dimensional target CRS
+     * @param  operation  the original (typically two-dimensional) coordinate operation.
+     * @param  decompose  the decomposition which has been applied.
+     *         If {@code decompose.source} is {@code true}, ellipsoidal height will be added
to source coordinates.
+     *         If {@code decompose.target} is {@code true}, ellipsoidal height will be added
to target coordinates.
      * @return a coordinate operation with the source and/or target coordinates made 3D,
      *         or {@code null} if this method does not know how to create the operation.
      * @throws IllegalArgumentException if the operation method can not have the desired
number of dimensions.
      * @throws FactoryException if an error occurred while creating the coordinate operation.
      */
-    private CoordinateOperation propagateVertical(final CoordinateReferenceSystem sourceCRS,
final boolean source3D,
-                                                  final CoordinateReferenceSystem targetCRS,
final boolean target3D,
-                                                  final CoordinateOperation operation)
+    private CoordinateOperation propagateVertical(final CoordinateReferenceSystem sourceCRS,
+                                                  final CoordinateReferenceSystem targetCRS,
+                                                  final CoordinateOperation operation,
+                                                  final Decomposition decompose)
             throws IllegalArgumentException, FactoryException
     {
         final List<CoordinateOperation> operations = new ArrayList<>();
@@ -887,8 +982,8 @@ class CoordinateOperationRegistry {
         } else {
             operations.add(operation);
         }
-        if ((source3D && !propagateVertical(sourceCRS, targetCRS, operations.listIterator(),
true)) ||
-            (target3D && !propagateVertical(sourceCRS, targetCRS, operations.listIterator(operations.size()),
false)))
+        if ((decompose.source && !propagateVertical(sourceCRS, targetCRS, operations.listIterator(),
true)) ||
+            (decompose.target && !propagateVertical(sourceCRS, targetCRS, operations.listIterator(operations.size()),
false)))
         {
             return null;
         }


Mime
View raw message