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: Consolidation of the code adding or removing EPSG codes after changes of axis order. More tests have been added.
Date Mon, 24 Dec 2018 21:23:39 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 e18babb  Consolidation of the code adding or removing EPSG codes after changes of
axis order. More tests have been added.
e18babb is described below

commit e18babbd63e018b1535aae5802535e59b7c51935
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Dec 24 22:22:26 2018 +0100

    Consolidation of the code adding or removing EPSG codes after changes of axis order.
    More tests have been added.
---
 .../org/apache/sis/referencing/cs/AbstractCS.java  |  20 +--
 .../sis/referencing/cs/CoordinateSystems.java      |  53 ++++---
 .../sis/referencing/cs/DefaultCartesianCS.java     |   8 -
 .../sis/referencing/cs/DefaultEllipsoidalCS.java   |   8 -
 .../org/apache/sis/referencing/cs/Normalizer.java  |  96 ++++++++----
 .../sis/referencing/cs/CoordinateSystemsTest.java  |  38 ++++-
 .../apache/sis/referencing/cs/HardCodedAxes.java   |  11 +-
 .../apache/sis/referencing/cs/NormalizerTest.java  | 172 +++++++++++++++++----
 8 files changed, 291 insertions(+), 115 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
index 2e0de29..92141c2 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
@@ -348,11 +348,9 @@ public class AbstractCS extends AbstractIdentifiedObject implements CoordinateSy
         if (cs == null) {
             cs = Normalizer.forConvention(this, convention);
             if (cs == null) {
-                cs = this;                                                          // This
coordinate system is already normalized.
-            } else if (convention != AxesConvention.POSITIVE_RANGE &&
-                    IdentifiedObjects.getIdentifier(this, Citations.EPSG) != null)  // See
resolveEPSG(…) for purpose of this check.
-            {
-                cs = cs.resolveEPSG(false);
+                cs = this;                                              // This coordinate
system is already normalized.
+            } else if (convention != AxesConvention.POSITIVE_RANGE) {
+                cs = cs.resolveEPSG(this);
             }
             /*
              * It happen often that the CRS created by RIGHT_HANDED, CONVENTIONALLY_ORIENTED
@@ -390,16 +388,12 @@ public class AbstractCS extends AbstractIdentifiedObject implements
CoordinateSy
      * Such CS gives more information (better name and remarks). This is a "would be nice"
      * feature; if we fail, we keep the CS built by {@link Normalizer}.
      *
-     * <p>Subclasses need to override this method in order to change the {@code enabled}
-     * argument value from {@code false} to {@code true}. This is required because we do
-     * not want to enable this resolving process to every CS types.</p>
-     *
-     * @param  enabled  if {@code false}, do nothing.
+     * @param  original  the coordinate system from which this CS is derived.
      * @return the resolved CS, or {@code this} if none.
      */
-    AbstractCS resolveEPSG(final boolean enabled) {
-        if (enabled) {
-            final Integer epsg = CoordinateSystems.getEpsgCode(axes);
+    private AbstractCS resolveEPSG(final AbstractCS original) {
+        if (IdentifiedObjects.getIdentifier(original, Citations.EPSG) != null) {
+            final Integer epsg = CoordinateSystems.getEpsgCode(getInterface(), axes);
             if (epsg != null) try {
                 final AuthorityFactory factory = CRS.getAuthorityFactory(Constants.EPSG);
                 if (factory instanceof CSAuthorityFactory) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java
index 555cba6..03fb76a 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/CoordinateSystems.java
@@ -24,6 +24,8 @@ import javax.measure.quantity.Length;
 import javax.measure.IncommensurableException;
 import org.opengis.referencing.cs.RangeMeaning;
 import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.cs.CartesianCS;
+import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.operation.Matrix;
@@ -529,6 +531,7 @@ public final class CoordinateSystems extends Static {
      *   <tr><td>4497</td> <td>Cartesian</td>   <td>east</td>
 <td>north</td> <td></td>   <td>US survey foot</td></tr>
      * </table>
      *
+     * @param  type  the type of coordinate system for which an EPSG code is desired, as
a GeoAPI interface.
      * @param  axes  axes for which a coordinate system EPSG code is desired.
      * @return EPSG codes for a coordinate system using the given axes (ignoring metadata),
or {@code null} if unknown
      *         to this method. Note that a null value does not mean that a more  extensive
search in the EPSG database
@@ -539,7 +542,8 @@ public final class CoordinateSystems extends Static {
      * @since 1.0
      */
     @SuppressWarnings("fallthrough")
-    public static Integer getEpsgCode(final CoordinateSystemAxis... axes) {
+    public static Integer getEpsgCode(final Class<? extends CoordinateSystem> type,
final CoordinateSystemAxis... axes) {
+        ArgumentChecks.ensureNonNull("type", type);
         ArgumentChecks.ensureNonNull("axes", axes);
 forDim: switch (axes.length) {
             case 3: {
@@ -549,26 +553,36 @@ forDim: switch (axes.length) {
             case 2: {
                 final Unit<?> unit = axes[0].getUnit();
                 if (unit != null && unit.equals(axes[1].getUnit())) {
-                    final AxisDirection[] directions = new AxisDirection[axes.length];
-                    for (int i=0; i<directions.length; i++) {
-                        final CoordinateSystemAxis axis = axes[i];
-                        ArgumentChecks.ensureNonNullElement("axes", i, axis);
-                        directions[i] = axis.getDirection();
-                        if (RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning()) &&
Units.isAngular(unit)) try {
-                            final UnitConverter uc = unit.getConverterToAny(Units.DEGREE);
-                            final double min = uc.convert(axis.getMinimumValue());
-                            final double max = uc.convert(axis.getMaximumValue());
-                            if ((min > Double.NEGATIVE_INFINITY && Math.abs(min
- Longitude.MIN_VALUE) > Formulas.ANGULAR_TOLERANCE) ||
-                                (max < Double.POSITIVE_INFINITY && Math.abs(max
- Longitude.MAX_VALUE) > Formulas.ANGULAR_TOLERANCE))
-                            {
+                    final boolean isAngular = Units.isAngular(unit);
+                    if ((isAngular && type.isAssignableFrom(EllipsoidalCS.class))
||
+                         Units.isLinear(unit) && type.isAssignableFrom(CartesianCS.class))
+                    {
+                        /*
+                         * Current implementation defines EPSG codes for EllipsoidalCS and
CartesianCS only.
+                         * Those two coordinate system types can be differentiated by the
unit of the two first axes.
+                         * If a future implementation supports more CS types, above condition
will need to be updated.
+                         */
+                        final AxisDirection[] directions = new AxisDirection[axes.length];
+                        for (int i=0; i<directions.length; i++) {
+                            final CoordinateSystemAxis axis = axes[i];
+                            ArgumentChecks.ensureNonNullElement("axes", i, axis);
+                            directions[i] = axis.getDirection();
+                            if (isAngular && RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning()))
try {
+                                final UnitConverter uc = unit.getConverterToAny(Units.DEGREE);
+                                final double min = uc.convert(axis.getMinimumValue());
+                                final double max = uc.convert(axis.getMaximumValue());
+                                if ((min > Double.NEGATIVE_INFINITY && Math.abs(min
- Longitude.MIN_VALUE) > Formulas.ANGULAR_TOLERANCE) ||
+                                    (max < Double.POSITIVE_INFINITY && Math.abs(max
- Longitude.MAX_VALUE) > Formulas.ANGULAR_TOLERANCE))
+                                {
+                                    break forDim;
+                                }
+                            } catch (IncommensurableException e) {      // Should never happen
since we checked that units are angular.
+                                Logging.unexpectedException(Logging.getLogger(Modules.REFERENCING),
CoordinateSystems.class, "getEpsgCode", e);
                                 break forDim;
                             }
-                        } catch (IncommensurableException e) {      // Should never happen
since we checked that units are angular.
-                            Logging.unexpectedException(Logging.getLogger(Modules.REFERENCING),
CoordinateSystems.class, "getEpsgCode", e);
-                            break forDim;
                         }
+                        return getEpsgCode(unit, directions);
                     }
-                    return getEpsgCode(unit, directions);
                 }
             }
         }
@@ -577,11 +591,14 @@ forDim: switch (axes.length) {
 
     /**
      * Returns the EPSG code of a coordinate system using the given unit and axis directions.
+     * This convenience method performs the work documented in {@link #getEpsgCode(Class,
CoordinateSystemAxis...)},
+     * but requiring only a frequently used subset of information.
      * If no suitable coordinate system is known to Apache SIS, then this method returns
{@code null}.
      *
      * <p>Current implementation uses a hard-coded list of known coordinate systems;
      * it does not yet scan the EPSG database (this may change in future Apache SIS version).
-     * The current list of known coordinate systems is documented in {@link #getEpsgCode(CoordinateSystemAxis...)}.</p>
+     * The current list of known coordinate systems is documented {@linkplain #getEpsgCode(Class,
+     * CoordinateSystemAxis...) above}.</p>
      *
      * @param  unit        desired unit of measurement. For three-dimensional ellipsoidal
coordinate system,
      *                     this is the unit for the horizontal axes only; the vertical axis
is in metres.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java
index b3d5a26..f4f4896 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCartesianCS.java
@@ -242,14 +242,6 @@ public class DefaultCartesianCS extends DefaultAffineCS implements CartesianCS
{
         }
     }
 
-    /**
-     * Overridden for enabling the search of equivalent CS in the EPSG database.
-     */
-    @Override
-    final AbstractCS resolveEPSG(final boolean enabled) {
-        return super.resolveEPSG(true);
-    }
-
 
 
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
index 4e7b8d0..edaa12e 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultEllipsoidalCS.java
@@ -252,14 +252,6 @@ public class DefaultEllipsoidalCS extends AbstractCS implements EllipsoidalCS
{
         }
     }
 
-    /**
-     * Overridden for enabling the search of equivalent CS in the EPSG database.
-     */
-    @Override
-    final AbstractCS resolveEPSG(final boolean enabled) {
-        return super.resolveEPSG(true);
-    }
-
 
 
 
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java
index f8564c4..5bae092 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/Normalizer.java
@@ -30,6 +30,7 @@ import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.SphericalCS;
 import org.opengis.referencing.cs.CylindricalCS;
 import org.opengis.referencing.cs.PolarCS;
+import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.metadata.AxisDirections;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.referencing.IdentifiedObjects;
@@ -86,7 +87,7 @@ import static org.apache.sis.internal.referencing.NilReferencingObject.UNNAMED;
  * This should be considered as an implementation details.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 1.0
  * @since   0.4
  * @module
  */
@@ -188,7 +189,7 @@ final class Normalizer implements Comparable<Normalizer> {
     }
 
     /**
-     * Sorts the specified axis in an attempt to create a right-handed system.
+     * Sorts the specified axes in an attempt to create a right-handed system.
      * The sorting is performed in place. This method returns {@code true} if
      * at least one axis moved as result of this method call.
      *
@@ -236,7 +237,7 @@ final class Normalizer implements Comparable<Normalizer> {
         final String abbreviation = axis.getAbbreviation();
         final String newAbbr = sameDirection ? abbreviation :
                 AxisDirections.suggestAbbreviation(axis.getName().getCode(), newDir, newUnit);
-        final Map<String,Object> properties = new HashMap<>();
+        final Map<String,Object> properties = new HashMap<>(8);
         if (newAbbr.equals(abbreviation)) {
             properties.putAll(IdentifiedObjects.getProperties(axis, EXCLUDES));
         } else {
@@ -280,21 +281,33 @@ final class Normalizer implements Comparable<Normalizer> {
      * @return the normalized coordinate system, or {@code null} if no normalization is needed.
      */
     static AbstractCS normalize(final CoordinateSystem cs, final AxisFilter changes, final
boolean reorder) {
-        boolean changed = false;
         final int dimension = cs.getDimension();
-        CoordinateSystemAxis[] axes = new CoordinateSystemAxis[dimension];
+        /*
+         * Get the axes to retain, without normalizing them yet. We keep a list of
+         * axes before normalization in order to detect which axes have been reused
+         * and whether reused axes are in the same order than before.
+         */
+        final CoordinateSystemAxis[] oldAxes = new CoordinateSystemAxis[dimension];
         int n = 0;
         for (int i=0; i<dimension; i++) {
-            CoordinateSystemAxis axis = cs.getAxis(i);
-            if (changes != null) {
-                if (!changes.accept(axis)) {
-                    continue;
-                }
-                changed |= (axis != (axis = normalize(axis, changes)));
+            final CoordinateSystemAxis axis = cs.getAxis(i);
+            if (changes == null || changes.accept(axis)) {
+                oldAxes[n++] = axis;
+            }
+        }
+        /*
+         * Normalize axis units and directions, without changing axis order yet.
+         * We need to normalize direction before to check for axis order because
+         * change in axis direction can change whether axes are right-handed.
+         */
+        final CoordinateSystemAxis[] newAxes = Arrays.copyOf(oldAxes, n);
+        boolean changed = false;
+        if (changes != null) {
+            for (int i=0; i<n; i++) {
+                newAxes[i] = normalize(newAxes[i], changes);
+                changed |= (newAxes[i] != oldAxes[i]);
             }
-            axes[n++] = axis;
         }
-        axes = ArraysExt.resize(axes, n);
         /*
          * Sort the axes in an attempt to create a right-handed system.
          * If nothing changed, return the given Coordinate System as-is.
@@ -303,21 +316,21 @@ final class Normalizer implements Comparable<Normalizer> {
             int angularUnitOrder = 0;
             if  (cs instanceof EllipsoidalCS || cs instanceof SphericalCS) angularUnitOrder
= -1;      // (λ,φ,h) order
             else if (cs instanceof CylindricalCS || cs instanceof PolarCS) angularUnitOrder
= +1;      // (r,θ) order
-            changed |= sort(axes, angularUnitOrder);
+            changed |= sort(newAxes, angularUnitOrder);
             if (angularUnitOrder == 1) {                            // Cylindrical or polar
                 /*
                  * Change (r,z,θ) to (r,θ,z) order in CylindricalCS. The check on unit
of
                  * measurements should be always true, but we verify as a paranoiac check.
                  */
-                if (axes.length == 3 && isLengthAndAngle(axes, 1)) {
-                    ArraysExt.swap(axes, 1, 2);
+                if (newAxes.length == 3 && isLengthAndAngle(newAxes, 1)) {
+                    ArraysExt.swap(newAxes, 1, 2);
                 }
                 /*
                  * If we were not allowed to normalize the axis direction, we may have a
                  * left-handed coordinate system here. If so, make it right-handed.
                  */
-                if (AxisDirections.CLOCKWISE.equals(axes[1].getDirection()) && isLengthAndAngle(axes,
0)) {
-                    ArraysExt.swap(axes, 0, 1);
+                if (AxisDirections.CLOCKWISE.equals(newAxes[1].getDirection()) &&
isLengthAndAngle(newAxes, 0)) {
+                    ArraysExt.swap(newAxes, 0, 1);
                 }
             }
         }
@@ -325,12 +338,29 @@ final class Normalizer implements Comparable<Normalizer> {
             return null;
         }
         /*
+         * Verify is some axes have been reused as-is but in different order. The EPSG database
uses different codes
+         * for the same axis at different index. If we changed the position of an axis, then
we remove its EPSG code
+         * since it is no longer correct. Actually we conservatively remove all identifiers,
not only EPSG ones, for
+         * simplicity, consistency with handling of identifiers elsewhere in this class and
because we don't know if
+         * other identifier namespaces depend on axis order like EPSG ones.
+         */
+        for (int i=0; i<n; i++) {
+            final CoordinateSystemAxis axis = newAxes[i];
+            if (!axis.getIdentifiers().isEmpty()) {             // If the axis has no identifier,
nothing to remove.
+                for (int j=0; j<n; j++) {
+                    if (j != i && axis == oldAxes[j]) {
+                        newAxes[i] = forRange(axis, axis.getMinimumValue(), axis.getMaximumValue());
+                    }
+                }
+            }
+        }
+        /*
          * Create a new coordinate system of the same type than the given one, but with the
given axes.
          * We need to change the Coordinate System name, since it is likely to not be valid
anymore.
          */
         final AbstractCS impl = castOrCopy(cs);
         final StringBuilder buffer = (StringBuilder) CharSequences.camelCaseToSentence(impl.getInterface().getSimpleName());
-        return impl.createForAxes(singletonMap(AbstractCS.NAME_KEY, AxisDirections.appendTo(buffer,
axes)), axes);
+        return impl.createForAxes(singletonMap(AbstractCS.NAME_KEY, AxisDirections.appendTo(buffer,
newAxes)), newAxes);
     }
 
     /**
@@ -342,7 +372,7 @@ final class Normalizer implements Comparable<Normalizer> {
     }
 
     /**
-     * Returns a coordinate system with the same axes than the given CS, except that the
wrapround axes
+     * Returns a coordinate system with the same axes than the given CS, except that the
wraparound axes
      * are shifted to a range of positive values. This method can be used in order to shift
between the
      * [-180 … +180]° and [0 … 360]° ranges of longitude values.
      *
@@ -359,23 +389,16 @@ final class Normalizer implements Comparable<Normalizer> {
         final CoordinateSystemAxis[] axes = new CoordinateSystemAxis[cs.getDimension()];
         for (int i=0; i<axes.length; i++) {
             CoordinateSystemAxis axis = cs.getAxis(i);
-            final RangeMeaning rangeMeaning = axis.getRangeMeaning();
-            if (RangeMeaning.WRAPAROUND.equals(rangeMeaning)) {
+            if (RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning())) {
                 double min = axis.getMinimumValue();
                 if (min < 0) {
                     double max = axis.getMaximumValue();
                     double offset = (max - min) / 2;
-                    offset *= Math.floor(min/offset + 1E-10);
+                    offset *= Math.floor(min/offset + Numerics.COMPARISON_THRESHOLD);
                     min -= offset;
                     max -= offset;
                     if (min < max) { // Paranoiac check, but also a way to filter NaN
values when offset is infinite.
-                        final Map<String,Object> properties = new HashMap<>();
-                        properties.putAll(IdentifiedObjects.getProperties(axis, EXCLUDES));
-                        properties.put(DefaultCoordinateSystemAxis.MINIMUM_VALUE_KEY, min);
-                        properties.put(DefaultCoordinateSystemAxis.MAXIMUM_VALUE_KEY, max);
-                        properties.put(DefaultCoordinateSystemAxis.RANGE_MEANING_KEY, rangeMeaning);
-                        axis = new DefaultCoordinateSystemAxis(properties,
-                                axis.getAbbreviation(), axis.getDirection(), axis.getUnit());
+                        axis = forRange(axis, min, max);
                         changed = true;
                     }
                 }
@@ -389,6 +412,19 @@ final class Normalizer implements Comparable<Normalizer> {
     }
 
     /**
+     * Returns a new axis with the same properties than the given axis except the identifiers
which are omitted,
+     * and the minimum and maximum values which are set to the given values.
+     */
+    private static CoordinateSystemAxis forRange(final CoordinateSystemAxis axis, final double
min, final double max) {
+        final Map<String,Object> properties = new HashMap<>(8);
+        properties.putAll(IdentifiedObjects.getProperties(axis, EXCLUDES));
+        properties.put(DefaultCoordinateSystemAxis.MINIMUM_VALUE_KEY, min);
+        properties.put(DefaultCoordinateSystemAxis.MAXIMUM_VALUE_KEY, max);
+        properties.put(DefaultCoordinateSystemAxis.RANGE_MEANING_KEY, axis.getRangeMeaning());
+        return new DefaultCoordinateSystemAxis(properties, axis.getAbbreviation(), axis.getDirection(),
axis.getUnit());
+    }
+
+    /**
      * Returns the given coordinate system as an {@code AbstractCS} instance. This method
performs an
      * {@code instanceof} check before to delegate to {@link AbstractCS#castOrCopy(CoordinateSystem)}
      * because there is no need to check for all interfaces before the implementation class
here.
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/CoordinateSystemsTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/CoordinateSystemsTest.java
index c300f3d..0efb380 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/CoordinateSystemsTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/CoordinateSystemsTest.java
@@ -23,6 +23,7 @@ import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.EllipsoidalCS;
+import org.opengis.referencing.cs.CartesianCS;
 import org.opengis.referencing.cs.VerticalCS;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.measure.Units;
@@ -44,8 +45,8 @@ import static org.apache.sis.test.Assert.*;
 /**
  * Tests the {@link CoordinateSystems} class.
  *
- * @author  Martin Desruisseaux (IRD)
- * @version 0.6
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @version 1.0
  * @since   0.4
  * @module
  */
@@ -311,4 +312,37 @@ public final strictfp class CoordinateSystemsTest extends TestCase {
         });
         assertEqualsIgnoreMetadata(targetCS, actualCS);
     }
+
+    /**
+     * Tests {@link CoordinateSystems#getEpsgCode(Class, CoordinateSystemAxis...)}
+     * with an ellipsoidal coordinate system.
+     */
+    @Test
+    public void testGetEpsgCodeForEllipsoidalCS() {
+        final Class<EllipsoidalCS> type = EllipsoidalCS.class;
+        final CoordinateSystemAxis φ = HardCodedAxes.GEODETIC_LATITUDE;
+        final CoordinateSystemAxis λ = HardCodedAxes.GEODETIC_LONGITUDE;
+        final CoordinateSystemAxis h = HardCodedAxes.ELLIPSOIDAL_HEIGHT;
+        assertEquals(Integer.valueOf(6422), CoordinateSystems.getEpsgCode(type, φ, λ));
+        assertEquals(Integer.valueOf(6423), CoordinateSystems.getEpsgCode(type, φ, λ, h));
+        assertEquals(Integer.valueOf(6424), CoordinateSystems.getEpsgCode(type, λ, φ));
+        assertEquals(Integer.valueOf(6426), CoordinateSystems.getEpsgCode(type, λ, φ, h));
+        assertNull(CoordinateSystems.getEpsgCode(type, HardCodedAxes.EASTING, HardCodedAxes.NORTHING));
+    }
+
+    /**
+     * Tests {@link CoordinateSystems#getEpsgCode(Class, CoordinateSystemAxis...)}
+     * with an ellipsoidal coordinate system.
+     */
+    @Test
+    public void testGetEpsgCodeForCartesianCS() {
+        final Class<CartesianCS> type = CartesianCS.class;
+        final CoordinateSystemAxis E = HardCodedAxes.EASTING;
+        final CoordinateSystemAxis W = HardCodedAxes.WESTING;
+        final CoordinateSystemAxis N = HardCodedAxes.NORTHING;
+        assertEquals(Integer.valueOf(4400), CoordinateSystems.getEpsgCode(type, E, N));
+        assertEquals(Integer.valueOf(4500), CoordinateSystems.getEpsgCode(type, N, E));
+        assertEquals(Integer.valueOf(4501), CoordinateSystems.getEpsgCode(type, N, W));
+        assertNull(CoordinateSystems.getEpsgCode(type, HardCodedAxes.GEODETIC_LATITUDE, HardCodedAxes.GEODETIC_LONGITUDE));
+    }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/HardCodedAxes.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/HardCodedAxes.java
index a577586..8f2a36b 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/HardCodedAxes.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/HardCodedAxes.java
@@ -28,6 +28,8 @@ import org.apache.sis.measure.Units;
 
 /**
  * Collection of axes for testing purpose.
+ * Note that EPSG codes of coordinate system axes depend on axis order.
+ * Consequently EPSG codes are not provided.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
@@ -42,7 +44,8 @@ public final strictfp class HardCodedAxes {
      * The ISO 19111 name is <cite>"geodetic longitude"</cite> and the abbreviation
is "λ" (lambda).
      *
      * <p>This axis is usually part of a {@link #GEODETIC_LONGITUDE}, {@link #GEODETIC_LATITUDE},
-     * {@link #ELLIPSOIDAL_HEIGHT} set.</p>
+     * {@link #ELLIPSOIDAL_HEIGHT} set. The EPSG code depends on axis order; consequently
it is
+     * not provided.</p>
      *
      * @see #SPHERICAL_LONGITUDE
      * @see #GEODETIC_LATITUDE
@@ -57,7 +60,8 @@ public final strictfp class HardCodedAxes {
      * The ISO 19111 name is <cite>"geodetic latitude"</cite> and the abbreviation
is "φ" (phi).
      *
      * <p>This axis is usually part of a {@link #GEODETIC_LONGITUDE}, {@link #GEODETIC_LATITUDE},
-     * {@link #ELLIPSOIDAL_HEIGHT} set.</p>
+     * {@link #ELLIPSOIDAL_HEIGHT} set. The EPSG code depends on axis order; consequently
it is
+     * not provided.</p>
      *
      * @see #SPHERICAL_LATITUDE
      * @see #GEODETIC_LONGITUDE
@@ -102,7 +106,8 @@ public final strictfp class HardCodedAxes {
      * The ISO 19111 name is <cite>"ellipsoidal height"</cite> and the abbreviation
is lower case <cite>"h"</cite>.
      *
      * <p>This axis is usually part of a {@link #GEODETIC_LONGITUDE}, {@link #GEODETIC_LATITUDE},
-     * {@link #ELLIPSOIDAL_HEIGHT} set.</p>
+     * {@link #ELLIPSOIDAL_HEIGHT} set. The EPSG code depends on axis order; consequently
it is
+     * not provided.</p>
      *
      * @see #ALTITUDE
      * @see #GEOCENTRIC_RADIUS
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/NormalizerTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/NormalizerTest.java
index 8cf72d2..27da9eb 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/NormalizerTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/NormalizerTest.java
@@ -18,8 +18,12 @@ package org.apache.sis.referencing.cs;
 
 import java.util.Map;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Collections;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.apache.sis.metadata.iso.ImmutableIdentifier;
+import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.measure.Units;
 import org.apache.sis.test.DependsOn;
@@ -35,7 +39,7 @@ import static org.apache.sis.test.ReferencingAssert.*;
  * Tests the {@link Normalizer} class.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.4
  * @module
  */
@@ -45,10 +49,11 @@ import static org.apache.sis.test.ReferencingAssert.*;
 })
 public final strictfp class NormalizerTest extends TestCase {
     /**
-     * Tests {@link Normalizer#sort(CoordinateSystemAxis[], int)}.
+     * Tests {@link Normalizer#sort(CoordinateSystemAxis[], int)}
+     * with axes of an ellipsoidal coordinate system.
      */
     @Test
-    public void testSort() {
+    public void testSortEllipsoidalAxes() {
         assertOrdered(new CoordinateSystemAxis[] {
             HardCodedAxes.GEODETIC_LONGITUDE,
             HardCodedAxes.GEODETIC_LATITUDE,
@@ -67,8 +72,14 @@ public final strictfp class NormalizerTest extends TestCase {
             HardCodedAxes.ELLIPSOIDAL_HEIGHT,
             HardCodedAxes.GEODETIC_LONGITUDE
         });
+    }
 
-        // A plausible CS.
+    /**
+     * Tests {@link Normalizer#sort(CoordinateSystemAxis[], int)}
+     * with axes of a Cartesian coordinate system.
+     */
+    @Test
+    public void testSortCartesianAxes() {
         assertOrdered(new AxisDirection[] {
             AxisDirection.EAST,                 // Right handed-rule
             AxisDirection.NORTH,                // Right handed-rule
@@ -78,24 +89,6 @@ public final strictfp class NormalizerTest extends TestCase {
             AxisDirection.UP,
             AxisDirection.EAST
         });
-
-        // A very dummy CS just for testing. The order of
-        // any non-compass direction should be unchanged.
-        assertOrdered(new AxisDirection[] {
-            AxisDirection.NORTH_EAST,           // Right handed-rule
-            AxisDirection.NORTH_NORTH_WEST,     // Right handed-rule
-            AxisDirection.GEOCENTRIC_X,
-            AxisDirection.GEOCENTRIC_Y,
-            AxisDirection.PAST
-        }, new AxisDirection[] {
-            AxisDirection.GEOCENTRIC_Y,
-            AxisDirection.NORTH_NORTH_WEST,
-            AxisDirection.GEOCENTRIC_X,
-            AxisDirection.NORTH_EAST,
-            AxisDirection.PAST
-        });
-
-        // An other plausible CS.
         assertOrdered(new AxisDirection[] {
             AxisDirection.WEST,                 // Right handed-rule
             AxisDirection.SOUTH,                // Right handed-rule
@@ -105,8 +98,6 @@ public final strictfp class NormalizerTest extends TestCase {
             AxisDirection.DOWN,
             AxisDirection.WEST
         });
-
-        // An other plausible CS.
         assertOrdered(new AxisDirection[] {
             AxisDirection.SOUTH,                // Right handed-rule
             AxisDirection.EAST,                 // Right handed-rule
@@ -116,8 +107,14 @@ public final strictfp class NormalizerTest extends TestCase {
             AxisDirection.DOWN,
             AxisDirection.EAST
         });
+    }
 
-        // Legacy (WKT 1) geocentric axes.
+    /**
+     * Tests {@link Normalizer#sort(CoordinateSystemAxis[], int)}
+     * with axes of legacy (WKT 1) axes.
+     */
+    @Test
+    public void testSortWKT1() {
         assertOrdered(new AxisDirection[] {
             AxisDirection.OTHER,
             AxisDirection.EAST,
@@ -130,6 +127,26 @@ public final strictfp class NormalizerTest extends TestCase {
     }
 
     /**
+     * Tests {@link Normalizer#sort(CoordinateSystemAxis[], int)} with axes of a dummy CS
just for testing.
+     */
+    @Test
+    public void testSortMixedAxes() {
+        assertOrdered(new AxisDirection[] {
+            AxisDirection.NORTH_EAST,           // Right handed-rule
+            AxisDirection.NORTH_NORTH_WEST,     // Right handed-rule
+            AxisDirection.GEOCENTRIC_X,
+            AxisDirection.GEOCENTRIC_Y,
+            AxisDirection.PAST
+        }, new AxisDirection[] {
+            AxisDirection.GEOCENTRIC_Y,
+            AxisDirection.NORTH_NORTH_WEST,
+            AxisDirection.GEOCENTRIC_X,
+            AxisDirection.NORTH_EAST,
+            AxisDirection.PAST
+        });
+    }
+
+    /**
      * Sorts the specified axis and compares against the expected result.
      */
     private static void assertOrdered(final CoordinateSystemAxis[] expected,
@@ -150,7 +167,7 @@ public final strictfp class NormalizerTest extends TestCase {
     }
 
     /**
-     * Creates axis from the specified directions.
+     * Creates axes from the specified directions.
      */
     private static CoordinateSystemAxis[] toAxes(final AxisDirection[] directions) {
         final Map<String,?> properties = singletonMap(NAME_KEY, "Temporary axis");
@@ -175,12 +192,25 @@ public final strictfp class NormalizerTest extends TestCase {
     }
 
     /**
-     * Tests {@link Normalizer#normalize(CoordinateSystemAxis, AxisFilter)}.
+     * Tests {@link Normalizer#normalize(CoordinateSystemAxis, AxisFilter)} for axis directions.
+     * Units are left unchanged.
      */
     @Test
-    public void testNormalizeAxis() {
-        // Execute twice, first without units normalization, then with units normalization.
+    public void testNormalizeAxisDirection() {
         assertSameAfterNormalization(AxesConvention.CONVENTIONALLY_ORIENTED);
+        /*
+         * Test a change of direction from West to East.
+         */
+        assertAxisEquals(Vocabulary.format(Vocabulary.Keys.Unnamed), "E",
+                AxisDirection.EAST, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Units.METRE,
null,
+                Normalizer.normalize(HardCodedAxes.WESTING, AxesConvention.NORMALIZED));
+    }
+
+    /**
+     * Tests {@link Normalizer#normalize(CoordinateSystemAxis, AxisFilter)} for axis units
and directions.
+     */
+    @Test
+    public void testNormalizeAxisUnitAndDirection() {
         assertSameAfterNormalization(AxesConvention.NORMALIZED);
         /*
          * Test a change of unit from centimetre to metre.
@@ -190,11 +220,87 @@ public final strictfp class NormalizerTest extends TestCase {
         assertAxisEquals("Height", "h", AxisDirection.UP,
                 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Units.METRE, null,
                 Normalizer.normalize(HardCodedAxes.HEIGHT_cm, AxesConvention.NORMALIZED));
+    }
+
+    /**
+     * Tests normalization of an ellipsoidal CS. The axes used in this test do not contain
any EPSG code.
+     * Consequently the {@link Normalizer#normalize(CoordinateSystem, AxisFilter, boolean)}
method should
+     * be able to reuse them as-is even if axis order changed.
+     */
+    @Test
+    public void testNormalize() {
+        final DefaultEllipsoidalCS cs = new DefaultEllipsoidalCS(
+                Collections.singletonMap(DefaultEllipsoidalCS.NAME_KEY, "lat lon height"),
+                HardCodedAxes.GEODETIC_LATITUDE,
+                HardCodedAxes.GEODETIC_LONGITUDE,
+                HardCodedAxes.ELLIPSOIDAL_HEIGHT);
+        final AbstractCS normalized = Normalizer.forConvention(cs, AxesConvention.RIGHT_HANDED);
+        assertEquals("name", "Ellipsoidal CS: East (°), North (°), Up (m).", String.valueOf(normalized.getName()));
         /*
-         * Test a change of direction from West to East.
+         * Longitude and latitude axes shall be interchanged. Since they have no EPSG code,
there
+         * is no need to create new CoordinateSystemAxis instances; the same ones can be
reused.
          */
-        assertAxisEquals(Vocabulary.format(Vocabulary.Keys.Unnamed), "E",
-                AxisDirection.EAST, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Units.METRE,
null,
-                Normalizer.normalize(HardCodedAxes.WESTING, AxesConvention.NORMALIZED));
+        assertSame("Latitude",  cs.getAxis(0), normalized.getAxis(1));
+        assertSame("Longitude", cs.getAxis(1), normalized.getAxis(0));
+        assertSame("Height",    cs.getAxis(2), normalized.getAxis(2));
+    }
+
+    /**
+     * Tests normalization of an ellipsoidal CS with EPSG codes. This test first creates
the axes
+     * of EPSG::6423 coordinate system, then reorder axes. Since axis EPSG codes differ depending
+     * on axis order, this test verifies that axis EPSG codes has been removed.
+     */
+    @Test
+    public void testIdentifierRemoval() {
+        final DefaultEllipsoidalCS cs = new DefaultEllipsoidalCS(           // EPSG::6423
+                Collections.singletonMap(DefaultEllipsoidalCS.NAME_KEY, "lat lon height"),
+                addIdentifier(HardCodedAxes.GEODETIC_LATITUDE,  (short) 108),
+                addIdentifier(HardCodedAxes.GEODETIC_LONGITUDE, (short) 109),
+                addIdentifier(HardCodedAxes.ELLIPSOIDAL_HEIGHT, (short) 110));
+        final AbstractCS normalized = Normalizer.forConvention(cs, AxesConvention.RIGHT_HANDED);
+        assertEquals("name", "Ellipsoidal CS: East (°), North (°), Up (m).", String.valueOf(normalized.getName()));
+        /*
+         * Longitude and latitude axes shall be interchanged. In addition of that, since
the EPSG codes
+         * need to be removed, new CoordinateSystemAxis instances shall have been created
except for
+         * ellipsoidal height, because its position did not changed.
+         */
+        assertIdentifierRemoved(cs.getAxis(1), normalized.getAxis(0));
+        assertIdentifierRemoved(cs.getAxis(0), normalized.getAxis(1));
+        assertSame("Height",    cs.getAxis(2), normalized.getAxis(2));
+        /*
+         * The HardCodedAxes constants have no EPSG identifiers, so we can compare the normalized
axes
+         * with those constants for equality.
+         */
+        assertEquals("Longitude", HardCodedAxes.GEODETIC_LONGITUDE, normalized.getAxis(0));
+        assertEquals("Latitude",  HardCodedAxes.GEODETIC_LATITUDE,  normalized.getAxis(1));
+    }
+
+    /**
+     * Creates an axis identical to the given one with an EPSG code added.
+     * This is a helper method for {@link #testIdentifierRemoval()}.
+     */
+    private static CoordinateSystemAxis addIdentifier(final CoordinateSystemAxis axis, final
short epsg) {
+        final Map<String,Object> properties = new HashMap<>(8);
+        properties.putAll(IdentifiedObjects.getProperties(axis));
+        properties.put(DefaultCoordinateSystemAxis.IDENTIFIERS_KEY,   new ImmutableIdentifier(null,
"EPSG", String.valueOf(epsg)));
+        properties.put(DefaultCoordinateSystemAxis.MINIMUM_VALUE_KEY, axis.getMinimumValue());
+        properties.put(DefaultCoordinateSystemAxis.MAXIMUM_VALUE_KEY, axis.getMaximumValue());
+        properties.put(DefaultCoordinateSystemAxis.RANGE_MEANING_KEY, axis.getRangeMeaning());
+        return new DefaultCoordinateSystemAxis(properties, axis.getAbbreviation(), axis.getDirection(),
axis.getUnit());
+    }
+
+    /**
+     * Verifies that an EPSG identifier added by {@link #addIdentifier(CoordinateSystemAxis,
short)} has been removed
+     * after the axes have been reordered.
+     *
+     * @param  original    the axis with EPSG identifier before normalization.
+     * @param  normalized  the axis after normalization, in which we expect the EPSG identifier
to have been removed.
+     */
+    private static void assertIdentifierRemoved(final CoordinateSystemAxis original, final
CoordinateSystemAxis normalized) {
+        assertNotSame  (original, normalized);
+        assertNotEquals(original, normalized);
+        assertFalse("identifiers.isEmpty()",   original.getIdentifiers().isEmpty());
+        assertTrue ("identifiers.isEmpty()", normalized.getIdentifiers().isEmpty());
+        assertEqualsIgnoreMetadata(original, normalized);
     }
 }


Mime
View raw message