You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2018/12/24 21:23:39 UTC
[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.
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 <ma...@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);
}
}