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 2e3f2c2370 Add a conversion from geographic to spherical coordinates.
https://issues.apache.org/jira/browse/SIS-302
2e3f2c2370 is described below
commit 2e3f2c2370c0624f37ff32acb9befc9c2307fd9c
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri May 30 01:16:13 2025 +0200
Add a conversion from geographic to spherical coordinates.
https://issues.apache.org/jira/browse/SIS-302
---
.../DefaultCoordinateOperationFactory.java | 11 +-
.../operation/transform/CartesianToPolar.java | 6 +-
.../operation/transform/CartesianToSpherical.java | 6 +-
.../transform/CoordinateSystemTransform.java | 31 ++
.../transform/EllipsoidToCentricTransform.java | 364 ++++++++++++++-------
.../operation/transform/PolarToCartesian.java | 6 +-
.../operation/transform/SphericalToCartesian.java | 6 +-
.../transform/EllipsoidToCentricTransformTest.java | 86 +++--
.../EllipsoidToSphericalTransformTest.java | 146 +++++++++
.../operation/transform/MathTransformTestCase.java | 4 +-
.../operation/transform/MathTransformWrapper.java | 4 +-
11 files changed, 505 insertions(+), 165 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
index 9a09d6cb98..7f12980e6b 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
@@ -706,9 +706,14 @@ next: for (SingleCRS component :
CRS.getSingleComponents(targetCRS)) {
}
try {
if (handler == null || (op = handler.peek()) == null) {
- final AuthorityFactory registry = USE_EPSG_FACTORY ?
CRS.getAuthorityFactory(Constants.EPSG) : null;
- op = createOperationFinder((registry instanceof
CoordinateOperationAuthorityFactory) ?
- (CoordinateOperationAuthorityFactory) registry : null,
context).createOperation(sourceCRS, targetCRS);
+ CoordinateOperationAuthorityFactory registry = null;
+ if (USE_EPSG_FACTORY) {
+ final AuthorityFactory candidate =
CRS.getAuthorityFactory(Constants.EPSG);
+ if (candidate instanceof
CoordinateOperationAuthorityFactory) {
+ registry = (CoordinateOperationAuthorityFactory)
candidate;
+ }
+ }
+ op = createOperationFinder(registry,
context).createOperation(sourceCRS, targetCRS);
}
} finally {
if (handler != null) {
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CartesianToPolar.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CartesianToPolar.java
index 032f75d7ad..529766f248 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CartesianToPolar.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CartesianToPolar.java
@@ -54,10 +54,10 @@ final class CartesianToPolar extends
CoordinateSystemTransform implements Serial
static final CartesianToPolar INSTANCE = new CartesianToPolar();
/**
- * Returns the singleton instance on deserialization.
+ * Returns the proxy to serialize instead of this class.
*/
- private Object readResolve() throws ObjectStreamException {
- return INSTANCE;
+ private Object writeReplace() throws ObjectStreamException {
+ return new Proxy(CartesianToPolar.class);
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CartesianToSpherical.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CartesianToSpherical.java
index 70a734b869..87e4b1d466 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CartesianToSpherical.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CartesianToSpherical.java
@@ -47,10 +47,10 @@ final class CartesianToSpherical extends
CoordinateSystemTransform implements Se
static final CartesianToSpherical INSTANCE = new CartesianToSpherical();
/**
- * Returns the singleton instance on deserialization.
+ * Returns the proxy to serialize instead of this class.
*/
- private Object readResolve() throws ObjectStreamException {
- return INSTANCE;
+ private Object writeReplace() throws ObjectStreamException {
+ return new Proxy(CartesianToSpherical.class);
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CoordinateSystemTransform.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CoordinateSystemTransform.java
index 583becacfb..6c6b2f844d 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CoordinateSystemTransform.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/CoordinateSystemTransform.java
@@ -17,6 +17,9 @@
package org.apache.sis.referencing.operation.transform;
import java.util.Map;
+import java.io.Serializable;
+import java.io.InvalidClassException;
+import java.io.ObjectStreamException;
import org.opengis.util.FactoryException;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.operation.MathTransform;
@@ -251,4 +254,32 @@ concat: if (info.isLinear(-linearTransformPosition, true))
{
}
super.tryConcatenate(info);
}
+
+ /**
+ * The object to use as a proxy during serialization.
+ * For avoiding complain about the lack of default constructor in the
super class.
+ */
+ static final class Proxy implements Serializable {
+ /** For cross-version compatibility. */
+ private static final long serialVersionUID = -2177879597869330855L;
+
+ /** The type of transform to reconstitute at deserialization. */
+ private final Class<? extends CoordinateSystemTransform> type;
+
+ /** Creates a new proxy. */
+ Proxy(final Class<? extends CoordinateSystemTransform> type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns the singleton instance on deserialization.
+ */
+ private Object readResolve() throws ObjectStreamException {
+ if (type == CartesianToSpherical.class) return
CartesianToSpherical.INSTANCE;
+ if (type == SphericalToCartesian.class) return
SphericalToCartesian.INSTANCE;
+ if (type == PolarToCartesian.class) return
PolarToCartesian.INSTANCE;
+ if (type == CartesianToPolar.class) return
CartesianToPolar.INSTANCE;
+ throw new InvalidClassException(type.getCanonicalName(),
type.toString());
+ }
+ }
}
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
index 9377fca6d7..1fe9a70238 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransform.java
@@ -83,6 +83,12 @@ import static
org.apache.sis.referencing.operation.provider.GeocentricAffineBetw
* <li>distance from Earth center on the Y axis (toward the intersection
of 90°E meridian and equator),</li>
* <li>distance from Earth center on the Z axis (toward North pole).</li>
* </ol>
+ * </li><li>In the spherical case:
+ * <ol>
+ * <li>spherical longitude (same as the geodetic longitude given in
input),</li>
+ * <li>spherical latitude (slightly different than the geodetic
latitude),</li>
+ * <li>distance from Earth center (radius).</li>
+ * </ol>
* </li>
* </ul>
*
@@ -102,26 +108,53 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
/**
* Serial number for inter-operability with different versions.
*/
- private static final long serialVersionUID = -3352045463953828140L;
+ private static final long serialVersionUID = 4154231361541573350L;
/**
- * Whether the output coordinate system is Cartesian or Spherical.
+ * Whether the output coordinate system is Cartesian or spherical.
*
- * <p><b>TODO:</b> The spherical case is not yet implemented.
- * We could also consider supporting the cylindrical case, but its
usefulness is not obvious.
- * See <a
href="http://issues.apache.org/jira/browse/SIS-302">SIS-302</a>.</p>
+ * @todo We could also consider supporting the cylindrical case if useful
+ * (<a href="http://issues.apache.org/jira/browse/SIS-302">SIS-302</a>).
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.5
* @since 0.7
*/
public enum TargetType {
/**
- * Indicates conversions from
- * {@linkplain org.apache.sis.referencing.cs.DefaultEllipsoidalCS
ellipsoidal} to
- * {@linkplain org.apache.sis.referencing.cs.DefaultCartesianCS
Cartesian} coordinate system.
+ * Indicates conversions from ellipsoidal to Cartesian coordinate
system.
+ * Axis order is:
+ *
+ * <ul>
+ * <li>Geocentric <var>X</var> (toward prime meridian)</li>
+ * <li>Geocentric <var>Y</var> (toward 90° east)</li>
+ * <li>Geocentric <var>Z</var> (toward north pole)</li>
+ * </ul>
+ *
+ * @see org.opengis.referencing.cs.EllipsoidalCS
+ * @see org.opengis.referencing.cs.CartesianCS
+ */
+ CARTESIAN,
+
+ /**
+ * Indicates conversions from ellipsoidal to spherical coordinate
system.
+ * Axis order is as below (note that this is <em>not</em> the
convention
+ * used neither in physics (ISO 80000-2:2009) or in mathematics).
+ *
+ * <ul>
+ * <li>Spherical longitude (θ), also noted Ω or λ.</li>
+ * <li>Spherical latitude (Ω), also noted θ or φ′.</li>
+ * <li>Spherical radius (r).</li>
+ * </ul>
+ *
+ * The spherical latitude is related to geodetic latitude φ by
{@literal Ω(φ) = atan((1-ℯ²)⋅tan(φ))}.
+ *
+ * @see org.opengis.referencing.cs.EllipsoidalCS
+ * @see org.opengis.referencing.cs.SphericalCS
+ *
+ * @since 1.5
*/
- CARTESIAN
+ SPHERICAL
}
/**
@@ -149,9 +182,10 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
private static final double ECCENTRICITY_THRESHOLD = 0.16;
/**
- * The square of eccentricity: ℯ² = (a²-b²)/a² where
- * <var>a</var> is the <i>semi-major</i> axis length and
- * <var>b</var> is the <i>semi-minor</i> axis length.
+ * The square of the eccentricity.
+ * This is defined as ℯ² = (a²-b²)/a² where
+ * <var>a</var> is the <dfn>semi-major</dfn> axis length and
+ * <var>b</var> is the <dfn>semi-minor</dfn> axis length.
*/
protected final double eccentricitySquared;
@@ -190,15 +224,27 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
private transient boolean useIterations;
/**
- * {@code true} if ellipsoidal coordinates include an ellipsoidal height
(i.e. are 3-D).
- * If {@code false}, then the input coordinates are expected to be
two-dimensional and
- * the ellipsoidal height is assumed to be 0.
+ * Whether the ellipsoidal coordinates include an ellipsoidal height (3D
case).
+ * If {@code false}, then the input coordinates are expected to be
two-dimensional
+ * and the ellipsoidal height is assumed to be 0.
+ *
+ * @see #getSourceDimensions()
+ * @since 1.5
+ */
+ protected final boolean withHeight;
+
+ /**
+ * Whether the target coordinate system is spherical rather than Cartesian.
+ * If {@code true}, then axes are <var>longitude</var> in radians,
+ * <var>latitude</var> in radians and <var>radius</var> in metres.
+ *
+ * @see #getTargetType()
*/
- final boolean withHeight;
+ final boolean toSphericalCS;
/**
* The parameters used for creating this conversion.
- * They are used for formatting <i>Well Known Text</i> (WKT) and error
messages.
+ * They are used for formatting <i>Well Known Text</i> (<abbr>WKT</abbr>)
and error messages.
*
* @see #getContextualParameters()
*/
@@ -218,8 +264,7 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
/**
* Creates a transform from angles in radians on ellipsoid having a
semi-major axis length of 1.
- * More specifically {@code EllipsoidToCentricTransform} instances expect
input coordinates
- * as below:
+ * More specifically {@code EllipsoidToCentricTransform} instances expect
input coordinates as below:
*
* <ol>
* <li>longitudes in <strong>radians</strong> relative to the prime
meridian (usually Greenwich),</li>
@@ -227,13 +272,20 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
* <li>optionally heights above the ellipsoid, in units of an ellipsoid
having a semi-major axis length of 1.</li>
* </ol>
*
- * Output coordinates are as below, in units of an ellipsoid having a
semi-major axis length of 1:
+ * Output coordinates depends on the {@linkplain #getTargetType() target
type}.
+ * For a Cartesian coordinate system, the output is as below and
+ * in units of an ellipsoid having a semi-major axis length of 1:
* <ol>
* <li>distance from Earth center on the X axis (toward the intersection
of prime meridian and equator),</li>
* <li>distance from Earth center on the Y axis (toward the intersection
of 90°E meridian and equator),</li>
* <li>distance from Earth center on the Z axis (toward North pole).</li>
* </ol>
*
+ * For a spherical coordinate system, the output are spherical longitude,
spherical latitude and radius.
+ * Latitudes are in radians. Longitudes can be in any unit of measurement
since they are copied verbatim
+ * without being used in calculations.
+ * It is okay to keep longitudes in degrees for avoiding rounding errors
during conversions.
+ *
* <h4>Geographic to geocentric conversions</h4>
* For converting geographic coordinates to geocentric coordinates, {@code
EllipsoidToCentricTransform}
* instances need to be concatenated with the following affine transforms:
@@ -258,7 +310,7 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
* @param unit the unit of measurement for the semi-axes and the
ellipsoidal height.
* @param withHeight {@code true} if source geographic coordinates
include an ellipsoidal height
* (i.e. are 3-D), or {@code false} if they are only
2-D.
- * @param target whether the target coordinate shall be Cartesian or
Spherical.
+ * @param target whether the target coordinate system shall be
Cartesian or spherical.
*
* @see #createGeodeticConversion(MathTransformFactory, double, double,
Unit, boolean, TargetType)
*/
@@ -268,6 +320,7 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
ArgumentChecks.ensureStrictlyPositive("semiMajor", semiMajor);
ArgumentChecks.ensureStrictlyPositive("semiMinor", semiMinor);
ArgumentChecks.ensureNonNull("target", target);
+ toSphericalCS = (target == TargetType.SPHERICAL);
axisRatio = semiMinor / semiMajor;
eccentricitySquared = 1 - (axisRatio * axisRatio);
useIterations = (eccentricitySquared >= ECCENTRICITY_THRESHOLD *
ECCENTRICITY_THRESHOLD);
@@ -288,17 +341,28 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
*
* - A "normalization" transform for converting degrees to radians
and normalizing the height,
* - A "denormalization" transform for scaling (X,Y,Z) to the
semi-major axis length.
+ *
+ * In the spherical case, the above step are modified as below:
+ *
+ * - Normalize only the latitude. Longitude does not need
normalization as it will pass through.
+ * - Denormalization needs to also convert radians to degrees.
*/
- context.normalizeGeographicInputs(0);
- final DoubleDouble a = DoubleDouble.of(semiMajor, true);
+ final MatrixSIS normalize;
final MatrixSIS denormalize =
context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
- for (int i=0; i<3; i++) {
- denormalize.convertAfter(i, a, null);
+ if (toSphericalCS) {
+ normalize =
context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
+ normalize .convertBefore(1, DoubleDouble.DEGREES_TO_RADIANS,
null);
+ denormalize.convertAfter (1, DoubleDouble.RADIANS_TO_DEGREES,
null);
+ } else {
+ normalize = context.normalizeGeographicInputs(0);
}
+ final DoubleDouble a = DoubleDouble.of(semiMajor, true);
if (withHeight) {
- final MatrixSIS normalize =
context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
normalize.convertBefore(2, a.inverse(), null); // Divide
ellipsoidal height by a.
}
+ for (int i = toSphericalCS ? 2 : 0; i < 3; i++) {
+ denormalize.convertAfter(i, a, null);
+ }
inverse = new Inverse(this);
}
@@ -340,7 +404,7 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
* @param unit the unit of measurement for the semi-axes and the
ellipsoidal height.
* @param withHeight {@code true} if source geographic coordinates
include an ellipsoidal height
* (i.e. are 3-D), or {@code false} if they are only
2-D.
- * @param target whether the target coordinate shall be Cartesian or
Spherical.
+ * @param target whether the target coordinate system shall be
Cartesian or spherical.
* @return the conversion from geographic to geocentric coordinates.
* @throws FactoryException if an error occurred while creating a
transform.
*/
@@ -348,21 +412,26 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
final double semiMajor, final double semiMinor, final Unit<Length>
unit,
final boolean withHeight, final TargetType target) throws
FactoryException
{
- if (Math.abs(semiMajor - semiMinor) <= semiMajor *
(Formulas.LINEAR_TOLERANCE / ReferencingServices.AUTHALIC_RADIUS)) {
- /*
- * If semi-major axis length is almost equal to semi-minor axis
length, uses spherical equations instead.
- * We need to add the sphere radius to the elevation before to
perform spherical to Cartesian conversion.
- */
- final MatrixSIS translate = Matrices.createDiagonal(4, withHeight
? 4 : 3);
- translate.setElement(2, withHeight ? 3 : 2, semiMajor);
- if (!withHeight) {
- translate.setElement(3, 2, 1);
- }
- final MathTransform tr =
SphericalToCartesian.INSTANCE.completeTransform(factory);
- return
factory.createConcatenatedTransform(factory.createAffineTransform(translate),
tr);
+ if (Math.abs(semiMajor - semiMinor) > semiMajor *
(Formulas.LINEAR_TOLERANCE / ReferencingServices.AUTHALIC_RADIUS)) {
+ var tr = new EllipsoidToCentricTransform(semiMajor, semiMinor,
unit, withHeight, target);
+ return tr.context.completeTransform(factory, tr);
+ }
+ /*
+ * If semi-major axis length is almost equal to semi-minor axis
length, uses spherical equations instead.
+ * We need to add the sphere radius to the elevation before to perform
spherical to Cartesian conversion.
+ */
+ final MatrixSIS translate = Matrices.createDiagonal(4, withHeight ? 4
: 3);
+ translate.setElement(2, withHeight ? 3 : 2, semiMajor);
+ if (!withHeight) {
+ translate.setElement(3, 2, 1);
}
- EllipsoidToCentricTransform tr = new
EllipsoidToCentricTransform(semiMajor, semiMinor, unit, withHeight, target);
- return tr.context.completeTransform(factory, tr);
+ final MathTransform tr;
+ switch (target) {
+ case CARTESIAN: tr =
SphericalToCartesian.INSTANCE.completeTransform(factory); break;
+ case SPHERICAL: tr = IdentityTransform.create(3); break;
+ default: throw new AssertionError(target);
+ }
+ return
factory.createConcatenatedTransform(factory.createAffineTransform(translate),
tr);
}
/**
@@ -397,7 +466,7 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
* Returns the parameters used for creating the complete conversion. Those
parameters describe a sequence
* of <i>normalize</i> → {@code this} → <i>denormalize</i> transforms,
<strong>not</strong>
* including {@linkplain
org.apache.sis.referencing.cs.CoordinateSystems#swapAndScaleAxes axis swapping}.
- * Those parameters are used for formatting <i>Well Known Text</i> (WKT)
and error messages.
+ * Those parameters are used for formatting <i>Well Known Text</i>
(<abbr>WKT</abbr>) and error messages.
*
* @return the parameter values for the sequence of
* <i>normalize</i> → {@code this} → <i>denormalize</i> transforms.
@@ -409,7 +478,8 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
/**
* Returns a copy of internal parameter values of this {@code
EllipsoidToCentricTransform} transform.
- * The returned group contains parameter values for the number of
dimensions and the eccentricity.
+ * The returned group contains parameter values for the number of
dimensions, the eccentricity and
+ * the target type (Cartesian or spherical).
*
* <h4>Usage note</h4>
* This method is mostly for {@linkplain
org.apache.sis.io.wkt.Convention#INTERNAL debugging purposes}
@@ -471,12 +541,12 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
}
/**
- * Returns whether the target coordinate system is Cartesian or Spherical.
+ * Returns whether the target coordinate system is Cartesian or spherical.
*
- * @return whether the target coordinate system is Cartesian or Spherical.
+ * @return whether the target coordinate system is Cartesian or spherical.
*/
public final TargetType getTargetType() {
- return TargetType.CARTESIAN;
+ return toSphericalCS ? TargetType.SPHERICAL : TargetType.CARTESIAN;
}
/**
@@ -528,10 +598,10 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
* Implementation of {@link #transform(double[], int, double[], int,
boolean)}
* with possibility to override the {@link #withHeight} value.
*
- * @param λ longitude (radians).
+ * @param λ longitude (radians, except in the spherical case
which keep degrees).
* @param φ latitude (radians).
* @param h height above the ellipsoid divided by the length of
semi-major axis.
- * @param dstPts the array into which the transformed coordinate is
returned.
+ * @param dstPts the array into which the transformed coordinates are
returned.
* May be {@code null} if only the derivative matrix is
desired.
* @param dstOff the offset to the location of the transformed point
that is stored in the destination array.
* @param derivate {@code true} for computing the derivative, or {@code
false} if not needed.
@@ -541,41 +611,72 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
final double[] dstPts, final int dstOff,
final boolean derivate, final boolean wh)
{
- final double cosλ = cos(λ);
- final double sinλ = sin(λ);
- final double cosφ = cos(φ);
- final double sinφ = sin(φ);
- final double ν2 = 1 / (1 - eccentricitySquared*(sinφ*sinφ)); //
Square of ν (see below)
- final double ν = sqrt(ν2); //
Prime vertical radius of curvature at latitude φ
- final double r = ν + h;
- final double νℯ = ν * (1 - eccentricitySquared);
+ final double cosφ = cos(φ);
+ final double sinφ = sin(φ);
+ final double ν2 = 1 / (1 - eccentricitySquared*(sinφ*sinφ)); //
Square of ν (see below)
+ final double ν = sqrt(ν2); //
Prime vertical radius of curvature at latitude φ
+ final double νℯ = ν * (1 - eccentricitySquared);
+ final double r = ν + h;
+ final double rcosφ = r * cosφ;
+ final double Z = (νℯ+h) * sinφ;
+ if (!toSphericalCS) {
+ final double cosλ = cos(λ);
+ final double sinλ = sin(λ);
+ if (dstPts != null) {
+ dstPts[dstOff ] = rcosφ * cosλ; // X: Toward prime
meridian
+ dstPts[dstOff+1] = rcosφ * sinλ; // Y: Toward 90° east
+ dstPts[dstOff+2] = Z; // Z: Toward north pole
+ }
+ if (!derivate) {
+ return null;
+ }
+ final double sdφ = νℯ * ν2 + h;
+ final double dX_dh = cosφ * cosλ;
+ final double dY_dh = cosφ * sinλ;
+ final double dX_dλ = -r * dY_dh;
+ final double dY_dλ = r * dX_dh;
+ final double dX_dφ = -sdφ * (sinφ * cosλ);
+ final double dY_dφ = -sdφ * (sinφ * sinλ);
+ final double dZ_dφ = sdφ * cosφ;
+ if (wh) {
+ return new Matrix3(dX_dλ, dX_dφ, dX_dh,
+ dY_dλ, dY_dφ, dY_dh,
+ 0, dZ_dφ, sinφ);
+ } else {
+ return Matrices.create(3, 2, new double[] {
+ dX_dλ, dX_dφ,
+ dY_dλ, dY_dφ,
+ 0, dZ_dφ});
+ }
+ }
+ /*
+ * Case of spherical output coordinates.
+ * This case is less common than above Cartesian case.
+ */
+ final double R = hypot(Z, rcosφ);
if (dstPts != null) {
- final double rcosφ = r * cosφ;
- dstPts[dstOff ] = rcosφ * cosλ; // X:
Toward prime meridian
- dstPts[dstOff+1] = rcosφ * sinλ; // Y:
Toward 90° east
- dstPts[dstOff+2] = (νℯ+h) * sinφ; // Z:
Toward north pole
+ dstPts[dstOff ] = λ; // Longitude
+ dstPts[dstOff+1] = atan(Z / rcosφ); // Latitude =
atan((1-ℯ²)⋅tan(φ)) when h = 0.
+ dstPts[dstOff+2] = R; // Radius
}
if (!derivate) {
return null;
}
- final double sdφ = νℯ * ν2 + h;
- final double dX_dh = cosφ * cosλ;
- final double dY_dh = cosφ * sinλ;
- final double dX_dλ = -r * dY_dh;
- final double dY_dλ = r * dX_dh;
- final double dX_dφ = -sdφ * (sinφ * cosλ);
- final double dY_dφ = -sdφ * (sinφ * sinλ);
- final double dZ_dφ = sdφ * cosφ;
- if (wh) {
- return new Matrix3(dX_dλ, dX_dφ, dX_dh,
- dY_dλ, dY_dφ, dY_dh,
- 0, dZ_dφ, sinφ);
- } else {
- return Matrices.create(3, 2, new double[] {
- dX_dλ, dX_dφ,
- dY_dλ, dY_dφ,
- 0, dZ_dφ});
+ final MatrixSIS derivative = Matrices.createDiagonal(3, wh ? 3 : 2);
+ final double R2 = R * R; // May underflow.
+ if (R2 != 0) {
+ final double ℯ2ν2 = eccentricitySquared*ν2;
+ final double rsinφ = r * sinφ;
+ final double Zsinφ = Z * sinφ;
+ final double rcos2φ = rcosφ * cosφ;
+ derivative.setElement(1, 1, ((ℯ2ν2*sinφ*(νℯ*rsinφ - ν*Z) +
(νℯ+h)*r)*(cosφ*cosφ) + Z*rsinφ) / R2); // ∂Ω/∂φ
+ derivative.setElement(2, 1, ((ℯ2ν2*(νℯ*Zsinφ + ν*rcos2φ) -
r*r)*sinφ + Z*(νℯ+h))*cosφ / R); // ∂R/∂φ
+ if (wh) {
+ derivative.setElement(1, 2, cosφ*(rsinφ - Z) / R2); //
∂Ω/∂h
+ derivative.setElement(2, 2, (Zsinφ + rcos2φ) / R); //
∂R/∂h
+ }
}
+ return derivative;
}
/**
@@ -619,16 +720,23 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
final double sinφ = sin(φ);
final double ν = 1/sqrt(1 - eccentricitySquared *
(sinφ*sinφ)); // Prime vertical radius of curvature at latitude φ
final double rcosφ = (ν + h) * cos(φ);
- dstPts[dstOff++] = rcosφ * cos(λ);
// X: Toward prime meridian
- dstPts[dstOff++] = rcosφ * sin(λ);
// Y: Toward 90° east
- dstPts[dstOff++] = (ν * (1 - eccentricitySquared) + h) * sinφ;
// Z: Toward north pole
+ final double Z = (h + ν * (1 - eccentricitySquared)) * sinφ;
+ if (toSphericalCS) {
+ dstPts[dstOff++] = λ; // Longitude
+ dstPts[dstOff++] = atan(Z / rcosφ); // Latitude =
atan((1-ℯ²)⋅tan(φ)) when h = 0.
+ dstPts[dstOff++] = hypot(Z, rcosφ); // Radius
+ } else {
+ dstPts[dstOff++] = rcosφ * cos(λ); // X: Toward
prime meridian
+ dstPts[dstOff++] = rcosφ * sin(λ); // Y: Toward
90° east
+ dstPts[dstOff++] = Z; // Z: Toward
north pole
+ }
srcOff += srcInc;
dstOff += dstInc;
}
}
/*
- * NOTE: we do not bother to override the methods expecting a 'float'
array because those methods should
+ * NOTE: we do not bother to override the methods expecting a `float`
array because those methods should
* be rarely invoked. Since there is usually LinearTransforms before
and after this transform, the
* conversion between float and double will be handled by those
LinearTransforms. If nevertheless
* this EllipsoidToCentricTransform is at the beginning or the end
of a transformation chain,
@@ -672,11 +780,23 @@ public class EllipsoidToCentricTransform extends
AbstractMathTransform implement
}
}
}
-next: while (--numPts >= 0) {
- final double X = srcPts[srcOff++];
- final double Y = srcPts[srcOff++];
- final double Z = srcPts[srcOff++];
- final double p = hypot(X, Y);
+ while (--numPts >= 0) {
+ final double p, λ, Z;
+ if (toSphericalCS) {
+ final double Ω, r;
+ λ = srcPts[srcOff++]; // Spherical longitude
+ Ω = srcPts[srcOff++]; // Spherical latitude
+ r = srcPts[srcOff++]; // Spherical radius
+ p = r * cos(Ω);
+ Z = r * sin(Ω);
+ } else {
+ final double X, Y;
+ X = srcPts[srcOff++]; // Toward prime meridian
+ Y = srcPts[srcOff++]; // Toward 90° east
+ Z = srcPts[srcOff++]; // Toward north pole
+ p = hypot(X, Y);
+ λ = atan2(Y, X);
+ }
/*
* EPSG guide gives q = atan((Z⋅a) / (p⋅b))
* where in this class a = 1 because of the normalization matrix.
@@ -688,45 +808,37 @@ next: while (--numPts >= 0) {
final double tanq = Z / (p*axisRatio);
final double cos2q = 1/(1 + tanq*tanq);
final double sin2q = 1 - cos2q;
+ double ν = Double.NaN;
double φ = atan((Z + copySign(eccentricitySquared *
sin2q*sqrt(sin2q), tanq) / axisRatio) /
(p - eccentricitySquared *
cos2q*sqrt(cos2q)));
/*
- * The above is an approximation of φ. Usually we are done with a
good approximation for
- * a planet of the eccentricity of Earth. Code below is the one
that will be executed in
- * the vast majority of cases.
+ * The above is an approximation of φ. Usually, we are done with a
good approximation for a planet
+ * of the same as eccentricity of Earth. Code below will be
executed only in a minority of cases
+ * where the value of φ needs to be improved.
*/
- if (!useIterations) {
- dstPts[dstOff++] = atan2(Y, X);
- dstPts[dstOff++] = φ;
- if (withHeight) {
+ if (useIterations) {
+ int it = Formulas.MAXIMUM_ITERATIONS;
+ double Δφ;
+ do {
+ if (--it < 0) {
+ throw new
TransformException(Resources.format(Resources.Keys.NoConvergence));
+ }
final double sinφ = sin(φ);
- final double ν = 1/sqrt(1 - eccentricitySquared *
(sinφ*sinφ));
- dstPts[dstOff++] = p/cos(φ) - ν;
- }
- srcOff += srcInc;
- dstOff += dstInc;
- } else {
- /*
- * If this code is used on a planet with high eccentricity,
- * the φ value may need to be improved by an iterative method.
- */
- for (int it = Formulas.MAXIMUM_ITERATIONS; it >= 0; it--) {
+ ν = 1/sqrt(1 - eccentricitySquared * (sinφ*sinφ));
+ Δφ = φ - (φ = atan((Z + eccentricitySquared * ν * sinφ) /
p));
+ } while (abs(Δφ) >= Formulas.ANGULAR_TOLERANCE * (PI/180) *
0.25);
+ }
+ dstPts[dstOff++] = λ;
+ dstPts[dstOff++] = φ;
+ if (withHeight) {
+ if (!useIterations) {
final double sinφ = sin(φ);
- final double ν = 1/sqrt(1 - eccentricitySquared *
(sinφ*sinφ));
- final double Δφ = φ - (φ = atan((Z + eccentricitySquared *
ν * sinφ) / p));
- if (!(abs(Δφ) >= Formulas.ANGULAR_TOLERANCE * (PI/180) *
0.25)) { // Use ! for accepting NaN.
- dstPts[dstOff++] = atan2(Y, X);
- dstPts[dstOff++] = φ;
- if (withHeight) {
- dstPts[dstOff++] = p/cos(φ) - ν;
- }
- srcOff += srcInc;
- dstOff += dstInc;
- continue next;
- }
+ ν = 1/sqrt(1 - eccentricitySquared * (sinφ*sinφ));
}
- throw new
TransformException(Resources.format(Resources.Keys.NoConvergence));
+ dstPts[dstOff++] = p/cos(φ) - ν;
}
+ srcOff += srcInc;
+ dstOff += dstInc;
}
}
@@ -747,7 +859,8 @@ next: while (--numPts >= 0) {
@Override
protected int computeHashCode() {
int code = super.computeHashCode() + Double.hashCode(axisRatio);
- if (withHeight) code += 37;
+ if (toSphericalCS) code += 71;
+ if (withHeight) code += 37;
return code;
}
@@ -763,7 +876,9 @@ next: while (--numPts >= 0) {
}
if (super.equals(object, mode)) {
final EllipsoidToCentricTransform that =
(EllipsoidToCentricTransform) object;
- return (withHeight == that.withHeight) &&
Numerics.equals(axisRatio, that.axisRatio);
+ return (withHeight == that.withHeight) &&
+ (toSphericalCS == that.toSphericalCS) &&
+ Numerics.equals(axisRatio, that.axisRatio);
// No need to compare the contextual parameters since this is done
by super-class.
}
return false;
@@ -773,8 +888,7 @@ next: while (--numPts >= 0) {
/**
- * Converts Cartesian coordinates (<var>X</var>,<var>Y</var>,<var>Z</var>)
- * to ellipsoidal coordinates (λ,φ) or (λ,φ,<var>h</var>).
+ * Converts geocentric coordinates (Cartesian or spherical) to ellipsoidal
coordinates (λ,φ) or (λ,φ,<var>h</var>).
*
* @author Martin Desruisseaux (IRD, Geomatys)
*/
@@ -848,9 +962,9 @@ next: while (--numPts >= 0) {
*/
@Override
public Matrix derivative(final DirectPosition point) throws
TransformException {
- final double[] coordinate = point.getCoordinates();
- ArgumentChecks.ensureDimensionMatches("point", 3, coordinate);
- return this.transform(coordinate, 0, coordinate, 0, true);
+ final double[] coordinates = point.getCoordinates();
+ ArgumentChecks.ensureDimensionMatches("point", 3, coordinates);
+ return this.transform(coordinates, 0, coordinates, 0, true);
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PolarToCartesian.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PolarToCartesian.java
index 20eb5786a1..2c9a8bab45 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PolarToCartesian.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PolarToCartesian.java
@@ -67,10 +67,10 @@ final class PolarToCartesian extends
CoordinateSystemTransform implements Serial
static final PolarToCartesian INSTANCE = new PolarToCartesian();
/**
- * Returns the singleton instance on deserialization.
+ * Returns the proxy to serialize instead of this class.
*/
- private Object readResolve() throws ObjectStreamException {
- return INSTANCE;
+ private Object writeReplace() throws ObjectStreamException {
+ return new Proxy(PolarToCartesian.class);
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/SphericalToCartesian.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/SphericalToCartesian.java
index d492785e83..9f9315dbc7 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/SphericalToCartesian.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/SphericalToCartesian.java
@@ -65,10 +65,10 @@ final class SphericalToCartesian extends
CoordinateSystemTransform implements Se
static final SphericalToCartesian INSTANCE = new SphericalToCartesian();
/**
- * Returns the singleton instance on deserialization.
+ * Returns the proxy to serialize instead of this class.
*/
- private Object readResolve() throws ObjectStreamException {
- return INSTANCE;
+ private Object writeReplace() throws ObjectStreamException {
+ return new Proxy(SphericalToCartesian.class);
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
index 04596f4417..9d710a5993 100644
---
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
+++
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToCentricTransformTest.java
@@ -44,44 +44,84 @@ import org.opengis.test.ToleranceModifier;
/**
- * Tests {@link EllipsoidToCentricTransform}.
+ * Tests {@link EllipsoidToCentricTransform} from geographic to geocentric
coordinates.
+ * When a test provides hard-coded expected results, those results are in
Cartesian coordinates.
+ * See {@link #targetType} for more information.
*
* @author Martin Desruisseaux (IRD, Geomatys)
*/
-public final class EllipsoidToCentricTransformTest extends
MathTransformTestCase {
+public class EllipsoidToCentricTransformTest extends MathTransformTestCase {
/**
* A JUnit extension for listening to log events.
*/
@RegisterExtension
public final LoggingWatcher loggings;
+ /**
+ * Whether the {@code EllipsoidToCentricTransform} should target Cartesian
or spherical coordinates.
+ * The default value is {@code CARTESIAN}. Note that even if this field is
set to {@cide SPHERICAL},
+ * the {@link #transform} target may still be Cartesian with a calculation
done in two steps:
+ * geographic to spherical, then {@link SphericalToCartesian}.
+ */
+ protected EllipsoidToCentricTransform.TargetType targetType;
+
+ /**
+ * Whether to add a spherical to Cartesian conversion after the
{@linkplain #transform transform} to test.
+ * It should be true only if {@link #targetType} is {@code SPHERICAL}.
+ */
+ protected boolean addSphericalToCartesian;
+
/**
* Creates a new test case.
*/
public EllipsoidToCentricTransformTest() {
loggings = new LoggingWatcher(Loggers.CRS_FACTORY);
+ targetType = EllipsoidToCentricTransform.TargetType.CARTESIAN;
}
/**
* Convenience method for creating an instance from an ellipsoid.
+ * The target coordinate system is usually Cartesian. If {@link
#targetType} is {@code SPHERICAL},
+ * a {@link SphericalToCartesian} step is added as an opaque {@code
MathTransform} for preventing
+ * Apache <abbr>SIS</abbr> to optimize the concatenation result.
+ *
+ * @param ellipsoid the semi-major and semi-minor axis lengths with
their unit of measurement.
+ * @param withHeight whether source geographic coordinates include an
ellipsoidal height.
+ * @throws FactoryException if an error occurred while creating a
transform.
*/
- private void createGeodeticConversion(final Ellipsoid ellipsoid, boolean
is3D) throws FactoryException {
+ protected void createGeodeticConversion(final Ellipsoid ellipsoid, final
boolean withHeight) throws FactoryException {
final MathTransformFactory factory =
DefaultMathTransformFactory.provider();
- transform =
EllipsoidToCentricTransform.createGeodeticConversion(factory, ellipsoid, is3D);
+ transform = EllipsoidToCentricTransform.createGeodeticConversion(
+ factory,
+ ellipsoid.getSemiMajorAxis(),
+ ellipsoid.getSemiMinorAxis(),
+ ellipsoid.getAxisUnit(),
+ withHeight,
+ targetType);
/*
- * If the ellipsoid is a sphere, then
EllipsoidToCentricTransform.createGeodeticConversion(…) created a
- * SphericalToCartesian instance instead of an
EllipsoidToCentricTransform instance. Create manually
- * the EllipsoidToCentricTransform here and wrap the two transform in
a comparator for making sure that
+ * If the ellipsoid is a sphere, then
`EllipsoidToCentricTransform.createGeodeticConversion(…)` created a
+ * `SphericalToCartesian` instance instead of an
`EllipsoidToCentricTransform` instance. Create manually
+ * the `EllipsoidToCentricTransform` here and wrap the two transforms
in a comparator for making sure that
* the two implementations are consistent.
*/
- if (ellipsoid.isSphere()) {
- EllipsoidToCentricTransform tr = new EllipsoidToCentricTransform(
+ if (ellipsoid.isSphere() && targetType ==
EllipsoidToCentricTransform.TargetType.CARTESIAN) {
+ var tr = new EllipsoidToCentricTransform(
ellipsoid.getSemiMajorAxis(),
ellipsoid.getSemiMinorAxis(),
- ellipsoid.getAxisUnit(), is3D,
- EllipsoidToCentricTransform.TargetType.CARTESIAN);
+ ellipsoid.getAxisUnit(),
+ withHeight,
+ targetType);
transform = new TransformResultComparator(transform,
tr.context.completeTransform(factory, tr), 1E-2);
}
+ /*
+ * If the transform is from geographic to spherical coordinates, add a
spherical to Cartesian step.
+ * Note that each step works in degrees, not in radians. The use of
`MathTransformWrapper` prevent
+ * the "radians to degrees to radians" conversions to be optimized,
which is intentional for test.
+ */
+ if (addSphericalToCartesian) {
+ var tr = new
MathTransformWrapper(SphericalToCartesian.INSTANCE.completeTransform(factory));
+ transform = ConcatenatedTransform.create(transform, tr, factory);
+ }
}
/**
@@ -156,8 +196,9 @@ public final class EllipsoidToCentricTransformTest extends
MathTransformTestCase
*/
@Test
public void testHighEccentricity() throws FactoryException,
TransformException, FactoryException {
- transform =
EllipsoidToCentricTransform.createGeodeticConversion(DefaultMathTransformFactory.provider(),
- 6000000, 4000000, Units.METRE, true,
EllipsoidToCentricTransform.TargetType.CARTESIAN);
+ transform = EllipsoidToCentricTransform.createGeodeticConversion(
+ DefaultMathTransformFactory.provider(),
+ 6000000, 4000000, Units.METRE, true, targetType);
final double delta = toRadians(100.0 / 60) / 1852;
derivativeDeltas = new double[] {delta, delta, 100};
@@ -170,20 +211,23 @@ public final class EllipsoidToCentricTransformTest
extends MathTransformTestCase
/**
* Executes the derivative test using the given ellipsoid.
*
- * @param ellipsoid the ellipsoid to use for the test.
- * @param hasHeight {@code true} if geographic coordinates include an
ellipsoidal height (i.e. are 3-D),
- * or {@code false} if they are only 2-D.
+ * @param ellipsoid the ellipsoid to use for the test.
+ * @param withHeight whether geographic coordinates include an
ellipsoidal height (i.e. are 3-D).
* @throws FactoryException if an error occurred while creating a
transform.
* @throws TransformException should never happen.
*/
- private void testDerivative(final Ellipsoid ellipsoid, final boolean
hasHeight) throws FactoryException, TransformException {
- createGeodeticConversion(ellipsoid, hasHeight);
- DirectPosition point = hasHeight ? new GeneralDirectPosition(-10, 40,
200) : new DirectPosition2D(-10, 40);
+ private void testDerivative(final Ellipsoid ellipsoid, final boolean
withHeight) throws FactoryException, TransformException {
+ createGeodeticConversion(ellipsoid, withHeight);
+ DirectPosition point = withHeight ? new GeneralDirectPosition(-10, 40,
200) : new DirectPosition2D(-10, 40);
/*
* Derivative of the direct transform.
*/
tolerance = 1E-2;
- derivativeDeltas = new double[] {toRadians(1.0 / 60) / 1852};
// Approximately one metre.
+ derivativeDeltas = new double[] {
+ toRadians(1.0 / 60) / 1852, // Approximately one metre.
+ toRadians(1.0 / 60) / 1852,
+ 1
+ };
verifyDerivative(point.getCoordinates());
/*
* Derivative of the inverse transform.
@@ -191,7 +235,7 @@ public final class EllipsoidToCentricTransformTest extends
MathTransformTestCase
point = transform.transform(point, null);
transform = transform.inverse();
tolerance = 1E-8;
- derivativeDeltas = new double[] {1};
// Approximately one metre.
+ derivativeDeltas = new double[] {1,1,1}; // Approximately one metre.
verifyDerivative(point.getCoordinates());
loggings.assertNoUnexpectedLog();
}
diff --git
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToSphericalTransformTest.java
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToSphericalTransformTest.java
new file mode 100644
index 0000000000..8c7a71f120
--- /dev/null
+++
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/EllipsoidToSphericalTransformTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.operation.transform;
+
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.referencing.CommonCRS;
+import org.junit.jupiter.api.Test;
+
+
+/**
+ * Tests {@link EllipsoidToCentricTransform} targeting a spherical coordinate
system.
+ * The expected results of all tests are still in Cartesian geocentric
coordinates.
+ * See {@link #targetType} for more information.
+ *
+ * @author Martin Desruisseaux (IRD, Geomatys)
+ */
+public final class EllipsoidToSphericalTransformTest extends
EllipsoidToCentricTransformTest {
+ /**
+ * Creates a new test case.
+ */
+ public EllipsoidToSphericalTransformTest() {
+ targetType = EllipsoidToCentricTransform.TargetType.SPHERICAL;
+ addSphericalToCartesian = true;
+ }
+
+ /**
+ * Tests derivative on spherical coordinates. The result should be
identity matrices.
+ */
+ @Test
+ @Override
+ public void testDerivativeOnSphere() throws FactoryException,
TransformException {
+ addSphericalToCartesian = false;
+ super.testDerivativeOnSphere();
+ }
+
+ /**
+ * Tests derivative.
+ */
+ @Test
+ @Override
+ public void testDerivative() throws FactoryException, TransformException {
+ addSphericalToCartesian = false;
+ super.testDerivative();
+ }
+
+ /**
+ * Tests the standard Well Known Text (version 1) formatting for
three-dimensional transforms.
+ * The tests in this class haves an additional "Cartesian to spherical"
step compared to the parent class.
+ */
+ @Test
+ @Override
+ public void testWKT3D() throws FactoryException, TransformException {
+ createGeodeticConversion(CommonCRS.WGS84.ellipsoid(), true);
+ assertWktEquals("CONCAT_MT[\n" +
+ " PARAM_MT[“Ellipsoid_To_Geocentric”,\n" +
+ " PARAMETER[“semi_major”, 6378137.0],\n" +
+ " PARAMETER[“semi_minor”, 6356752.314245179]],\n" +
+ " PARAM_MT[“Spherical to Cartesian”]]");
+
+ transform = transform.inverse();
+ assertWktEquals("CONCAT_MT[\n" +
+ " PARAM_MT[“Cartesian to spherical”],\n" +
+ " PARAM_MT[“Geocentric_To_Ellipsoid”,\n" +
+ " PARAMETER[“semi_major”, 6378137.0],\n" +
+ " PARAMETER[“semi_minor”, 6356752.314245179]]]");
+
+ loggings.assertNoUnexpectedLog();
+ }
+
+ /**
+ * Tests the standard Well Known Text (version 1) formatting for
two-dimensional transforms.
+ * The tests in this class haves an additional "Cartesian to spherical"
step compared to the parent class.
+ */
+ @Test
+ @Override
+ public void testWKT2D() throws FactoryException, TransformException {
+ createGeodeticConversion(CommonCRS.WGS84.ellipsoid(), false);
+ assertWktEquals("CONCAT_MT[\n" +
+ " INVERSE_MT[PARAM_MT[“Geographic3D to 2D
conversion”]],\n" +
+ " PARAM_MT[“Ellipsoid_To_Geocentric”,\n" +
+ " PARAMETER[“semi_major”, 6378137.0],\n" +
+ " PARAMETER[“semi_minor”, 6356752.314245179]],\n" +
+ " PARAM_MT[“Spherical to Cartesian”]]");
+
+ transform = transform.inverse();
+ assertWktEquals("CONCAT_MT[\n" +
+ " PARAM_MT[“Cartesian to spherical”],\n" +
+ " PARAM_MT[“Geocentric_To_Ellipsoid”,\n" +
+ " PARAMETER[“semi_major”, 6378137.0],\n" +
+ " PARAMETER[“semi_minor”, 6356752.314245179]],\n" +
+ " PARAM_MT[“Geographic3D to 2D conversion”]]");
+
+ loggings.assertNoUnexpectedLog();
+ }
+
+ /**
+ * Tests the internal Well Known Text formatting.
+ * The tests in this class haves an additional "Cartesian to spherical"
step compared to the parent class.
+ * Furthermore, it has no conversion of degrees to radians for the
longitude values.
+ */
+ @Test
+ @Override
+ public void testInternalWKT() throws FactoryException, TransformException {
+ createGeodeticConversion(CommonCRS.WGS84.ellipsoid(), true);
+ assertInternalWktEquals(
+ "Concat_MT[\n" +
+ " Param_MT[“Affine”,\n" +
+ " Parameter[“num_row”, 4],\n" +
+ " Parameter[“num_col”, 4],\n" +
+ " Parameter[“elt_1_1”, 0.017453292519943295],\n" +
+ " Parameter[“elt_2_2”, 1.567855942887398E-7]],\n" +
+ " Param_MT[“Ellipsoid (radians domain) to centric”,\n" +
+ " Parameter[“eccentricity”, 0.08181919084262157],\n" +
+ " Parameter[“target”, “SPHERICAL”],\n" +
+ " Parameter[“dim”, 3]],\n" +
+ " Param_MT[“Affine”,\n" +
+ " Parameter[“num_row”, 4],\n" +
+ " Parameter[“num_col”, 4],\n" +
+ " Parameter[“elt_1_1”, 57.29577951308232],\n" +
+ " Parameter[“elt_2_2”, 6378137.0]],\n" +
+ " Concat_MT[\n" +
+ " Param_MT[“Affine”,\n" +
+ " Parameter[“num_row”, 4],\n" +
+ " Parameter[“num_col”, 4],\n" +
+ " Parameter[“elt_0_0”, 0.017453292519943295],\n" +
+ " Parameter[“elt_1_1”, 0.017453292519943295]],\n" +
+ " Param_MT[“Spherical to Cartesian”]]]");
+
+ loggings.assertNoUnexpectedLog();
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
index c4d73a07b1..c25f1a7325 100644
---
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
+++
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
@@ -152,8 +152,8 @@ public abstract class MathTransformTestCase extends
TransformTestCase {
}
/**
- * Returns the value to use from the {@link #λDimension} or {@link
zDimension} for the
- * given comparison mode, or -1 if none.
+ * Returns the value to use from the {@link #λDimension} or {@link
#zDimension}
+ * for the given comparison mode, or -1 if none.
*/
@SuppressWarnings("fallthrough")
private static int forComparison(final int[] config, final CalculationType
mode) {
diff --git
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java
index ad2f8ec144..47ed3485b4 100644
---
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java
+++
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformWrapper.java
@@ -37,10 +37,10 @@ import org.opengis.coordinate.MismatchedDimensionException;
* that a given transform implements the {@link MathTransform2D} or {@link
LinearTransform} interface,
* in order to disable optimization paths in some tests.
*
- * <strong>Do not implement {@link MathTransform2D} in this base
class</strong>.
+ * <p><strong>Do not implement {@link MathTransform2D} in this base
class</strong>.
* This wrapper is sometimes used for hiding the fact that a transform
implements
* the {@code MathTransform2D} interface, typically for testing a different
code
- * path in a JUnit test.
+ * path in a JUnit test.</p>
*
* @author Martin Desruisseaux (Geomatys)
*/