Author: desruisseaux
Date: Fri Mar 25 18:23:14 2016
New Revision: 1736638
URL: http://svn.apache.org/viewvc?rev=1736638&view=rev
Log:
Replaced CoordinateOperationInference.createOperationStep(GeocentricCRS,
GeocentricCRS) by a version working on GeodeticCRS, thus including
GeographicCRS.
Previously (in Geotk) we had two separated methods for the Geocentric and
Geographic cases. But that separation does not exist in ISO 19111:2007 standard,
and indeed merging those two methods in a single one gives something both
simpler and more powerful (better handling of change of coordinate system type).
This work required changes in GeocentricAffine and other internal classes for
creating more appropriate operation depending on the coordinate system type.
Added:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
(with props)
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationInference.java
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
[UTF-8] Fri Mar 25 18:23:14 2016
@@ -24,6 +24,10 @@ import org.opengis.util.FactoryException
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.cs.CartesianCS;
+import org.opengis.referencing.cs.EllipsoidalCS;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.referencing.operation.Transformation;
@@ -67,6 +71,14 @@ public abstract class GeocentricAffine e
private static final long serialVersionUID = 8291967302538661639L;
/**
+ * The tolerance factor for comparing the {@link BursaWolfParameters}
values.
+ * We use a tolerance of 1E-6 ({@value Formulas#LINEAR_TOLERANCE} / 10000)
based on the knowledge
+ * that the translation terms are in metres and the rotation terms have
the some order of magnitude.
+ * Actually we could use a value of zero, but we add a small tolerance for
rounding errors.
+ */
+ private static final double BURSAWOLF_TOLERANCE =
Formulas.LINEAR_TOLERANCE / 10000;
+
+ /**
* The operation parameter descriptor for the <cite>X-axis
translation</cite>
* ({@linkplain BursaWolfParameters#tX tX}) parameter value. Valid values
range
* from negative to positive infinity. Units are {@linkplain SI#METRE
metres}.
@@ -200,6 +212,103 @@ public abstract class GeocentricAffine e
}
/**
+ * Creates parameter values for a Molodensky, Geocentric Translation or
Position Vector transformation.
+ *
+ * @param descriptor The {@code PARAMETERS} constant of the subclass
describing the operation to create.
+ * @param parameters Bursa-Wolf parameters from which to get the
values.
+ * @param isTranslation {@code true} if the operation contains only
translation terms.
+ * @return The operation parameters with their values initialized.
+ */
+ private static Parameters createParameters(final ParameterDescriptorGroup
descriptor,
+ final BursaWolfParameters parameters, final boolean isTranslation)
+ {
+ final Parameters values =
Parameters.castOrWrap(descriptor.createValue());
+ values.getOrCreate(TX).setValue(parameters.tX);
+ values.getOrCreate(TY).setValue(parameters.tY);
+ values.getOrCreate(TZ).setValue(parameters.tZ);
+ if (!isTranslation) {
+ values.getOrCreate(RX).setValue(parameters.rX);
+ values.getOrCreate(RY).setValue(parameters.rY);
+ values.getOrCreate(RZ).setValue(parameters.rZ);
+ values.getOrCreate(DS).setValue(parameters.dS);
+ }
+ return values;
+ }
+
+ /**
+ * Returns the parameters for creating a datum shift operation.
+ * The operation method will be one of the {@code GeocentricAffine}
subclasses.
+ * If no single operation method can be used, then this method returns
{@code null}.
+ *
+ * <p>This method does <strong>not</strong> change the coordinate system
type.
+ * The source and target coordinate systems can be both {@code
EllipsoidalCS} or both {@code CartesianCS}.
+ * Any other type or mix of types (e.g. a {@code EllipsoidalCS} source and
{@code CartesianCS} target)
+ * will cause this method to return {@code null}. In such case, it is
caller's responsibility to apply
+ * the datum shift itself in Cartesian geocentric coordinates.</p>
+ *
+ * @param sourceCS The source coordinate system. Only the type and
number of dimensions is checked.
+ * @param targetCS The target coordinate system. Only the type and
number of dimensions is checked.
+ * @param datumShift The datum shift as a matrix.
+ * @param useMolodensky {@code true} for allowing the use of Molodensky
approximation, or {@code false}
+ * for using the transformation in geocentric space
(which should be more accurate).
+ * @return The parameter values, or {@code null} if no single operation
method can be found.
+ */
+ public static ParameterValueGroup createParameters(final CoordinateSystem
sourceCS,
+ final CoordinateSystem targetCS, final Matrix datumShift, boolean
useMolodensky)
+ {
+ final boolean isEllipsoidal = (sourceCS instanceof EllipsoidalCS);
+ if (!(isEllipsoidal ? targetCS instanceof EllipsoidalCS
+ : targetCS instanceof CartesianCS && sourceCS
instanceof CartesianCS))
+ {
+ return null; // Coordinate systems are not two
EllipsoidalCS or two CartesianCS.
+ }
+ @SuppressWarnings("null")
+ int dimension = sourceCS.getDimension();
+ if (dimension != targetCS.getDimension()) {
+ dimension = 0; // Sentinal value for
mismatched dimensions.
+ }
+ /*
+ * Try to convert the matrix into (tX, tY, tZ, rX, rY, rZ, dS)
parameters.
+ * The matrix may not be convertible, in which case we will let the
callers
+ * uses the matrix directly in Cartesian geocentric coordinates.
+ */
+ final BursaWolfParameters parameters = new BursaWolfParameters(null,
null);
+ try {
+ parameters.setPositionVectorTransformation(datumShift,
BURSAWOLF_TOLERANCE);
+ } catch (IllegalArgumentException e) {
+ log(Loggers.COORDINATE_OPERATION, "createParameters", e);
+ return null;
+ }
+ final boolean isTranslation = parameters.isTranslation();
+ final ParameterDescriptorGroup descriptor;
+ /*
+ * Following "if" blocks are ordered from more accurate to less
accurate datum shift method
+ * supported by GeocentricAffine subclasses.
+ */
+ if (!isEllipsoidal) {
+ useMolodensky = false;
+ descriptor = isTranslation ? GeocentricTranslation.PARAMETERS
+ : PositionVector7Param .PARAMETERS;
+ } else {
+ if (!isTranslation) {
+ useMolodensky = false;
+ descriptor = (dimension >= 3) ?
PositionVector7Param3D.PARAMETERS
+ :
PositionVector7Param2D.PARAMETERS;
+ } else if (!useMolodensky) {
+ descriptor = (dimension >= 3) ?
GeocentricTranslation3D.PARAMETERS
+ :
GeocentricTranslation2D.PARAMETERS;
+ } else {
+ descriptor = Molodensky.PARAMETERS;
+ }
+ }
+ final Parameters values = createParameters(descriptor, parameters,
isTranslation);
+ if (useMolodensky && dimension != 0) {
+ values.getOrCreate(Molodensky.DIMENSION).setValue(dimension);
+ }
+ return values;
+ }
+
+ /**
* Given a transformation chain, conditionally replaces the affine
transform elements by an alternative object
* showing the Bursa-Wolf parameters. The replacement is applied if and
only if the affine transform is a scale,
* translation or rotation in the geocentric domain.
@@ -211,7 +320,7 @@ public abstract class GeocentricAffine e
*
* @param transforms The full chain of concatenated transforms.
*/
- public static void asDatumShift(final List<Object> transforms) throws
IllegalArgumentException {
+ public static void asDatumShift(final List<Object> transforms) {
for (int i=transforms.size() - 2; --i >= 0;) {
if (isOperation(GeographicToGeocentric.NAME, transforms.get(i)) &&
isOperation(GeocentricToGeographic.NAME, transforms.get(i+2)))
@@ -220,31 +329,18 @@ public abstract class GeocentricAffine e
if (step instanceof LinearTransform) {
final BursaWolfParameters parameters = new
BursaWolfParameters(null, null);
try {
- /*
- * We use a 0.01 metre tolerance
(Formulas.LINEAR_TOLERANCE) based on the knowledge that the
- * translation terms are in metres and the rotation
terms have the some order of magnitude.
- */
-
parameters.setPositionVectorTransformation(((LinearTransform)
step).getMatrix(), Formulas.LINEAR_TOLERANCE);
+
parameters.setPositionVectorTransformation(((LinearTransform)
step).getMatrix(), BURSAWOLF_TOLERANCE);
} catch (IllegalArgumentException e) {
/*
* Should not occur, except sometime on inverse
transform of relatively complex datum shifts
* (more than just translation terms). We can fallback
on formatting the full matrix.
*/
-
Logging.recoverableException(Logging.getLogger(Loggers.WKT),
GeocentricAffine.class, "asDatumShift", e);
+ log(Loggers.WKT, "asDatumShift", e);
continue;
}
final boolean isTranslation = parameters.isTranslation();
- final Parameters values = Parameters.castOrWrap(
- (isTranslation ? GeocentricTranslation.PARAMETERS
: PositionVector7Param.PARAMETERS).createValue());
- values.getOrCreate(TX).setValue(parameters.tX);
- values.getOrCreate(TY).setValue(parameters.tY);
- values.getOrCreate(TZ).setValue(parameters.tZ);
- if (!isTranslation) {
- values.getOrCreate(RX).setValue(parameters.rX);
- values.getOrCreate(RY).setValue(parameters.rY);
- values.getOrCreate(RZ).setValue(parameters.rZ);
- values.getOrCreate(DS).setValue(parameters.dS);
- }
+ final Parameters values = createParameters(isTranslation ?
GeocentricTranslation.PARAMETERS
+ : PositionVector7Param.PARAMETERS,
parameters, isTranslation);
transforms.set(i+1, new FormattableObject() {
@Override protected String formatTo(final Formatter
formatter) {
WKTUtilities.appendParamMT(values, formatter);
@@ -263,4 +359,11 @@ public abstract class GeocentricAffine e
return (actual instanceof Parameterized) &&
IdentifiedObjects.isHeuristicMatchForName(((Parameterized)
actual).getParameterDescriptors(), expected);
}
+
+ /**
+ * Logs a warning about a failure to compute the Bursa-Wolf parameters.
+ */
+ private static void log(final String logger, final String method, final
Exception e) {
+ Logging.recoverableException(Logging.getLogger(logger),
GeocentricAffine.class, method, e);
+ }
}
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricTranslation3D.java
[UTF-8] Fri Mar 25 18:23:14 2016
@@ -40,7 +40,7 @@ public final class GeocentricTranslation
/**
* The group of all parameters expected by this coordinate operation.
*/
- private static final ParameterDescriptorGroup PARAMETERS;
+ static final ParameterDescriptorGroup PARAMETERS;
static {
PARAMETERS = builder()
.addIdentifier("1035")
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
[UTF-8] Fri Mar 25 18:23:14 2016
@@ -679,7 +679,7 @@ public class TensorParameters<E> impleme
if (++indices[j] < actualSize[j]) {
break;
}
- indices[j] = 0; // We have done a full turn at that dimension.
Will increment next dimension.
+ indices[j] = 0; // We have done a full turn at that
dimension. Will increment next dimension.
}
}
return parameters;
@@ -737,6 +737,8 @@ public class TensorParameters<E> impleme
* @param properties The properties to be given to the identified object.
* @param matrix The matrix to copy in the new parameter group.
* @return A new parameter group initialized to the given matrix.
+ *
+ * @see #toMatrix(ParameterValueGroup)
*/
public ParameterValueGroup createValueGroup(final Map<String,?>
properties, final Matrix matrix) {
if (rank() != 2) {
@@ -755,6 +757,8 @@ public class TensorParameters<E> impleme
* @param parameters The group of parameters.
* @return A matrix constructed from the specified group of parameters.
* @throws InvalidParameterNameException if a parameter name was not
recognized.
+ *
+ * @see #createValueGroup(Map, Matrix)
*/
public Matrix toMatrix(final ParameterValueGroup parameters) throws
InvalidParameterNameException {
if (rank() != 2) {
@@ -762,7 +766,7 @@ public class TensorParameters<E> impleme
}
ArgumentChecks.ensureNonNull("parameters", parameters);
if (parameters instanceof TensorValues) {
- return ((TensorValues) parameters).toMatrix(); // More efficient
implementation
+ return ((TensorValues) parameters).toMatrix(); //
More efficient implementation
}
// Fallback on the general case (others implementations)
final ParameterValue<?> numRow =
parameters.parameter(dimensions[0].getName().getCode());
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
[UTF-8] Fri Mar 25 18:23:14 2016
@@ -534,10 +534,14 @@ public class BursaWolfParameters extends
}
/*
* Translation terms, taken "as-is".
+ * If the matrix contains only translation terms (which is often the
case), we are done.
*/
tX = matrix.getElement(0,3);
tY = matrix.getElement(1,3);
tZ = matrix.getElement(2,3);
+ if (Matrices.isTranslation(matrix)) { // Optimization for a common
case.
+ return;
+ }
/*
* Scale factor: take the average of elements on the diagonal. All
those
* elements should have the same value, but we tolerate slight
deviation
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
[UTF-8] Fri Mar 25 18:23:14 2016
@@ -225,14 +225,14 @@ class AbstractSingleOperation extends Ab
* ignoring null java.lang.Integer instances. We do not specify
whether the method
* dimensions should include the interpolation dimensions or not, so
we accept both.
*/
- int isTarget = 0; // 0 == false: the wrong dimension is the source
one.
+ int isTarget = 0; // 0 == false: the wrong dimension is
the source one.
if (expected == null || (actual == expected) || (actual == expected +
interpDim)) {
actual = transform.getTargetDimensions();
expected = method.getTargetDimensions();
if (expected == null || (actual == expected) || (actual ==
expected + interpDim)) {
return;
}
- isTarget = 1; // 1 == true: the wrong dimension is the target
one.
+ isTarget = 1; // 1 == true: the wrong dimension is
the target one.
}
/*
* At least one dimension does not match. In principle this is an
error, but we make an exception for the
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationInference.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationInference.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationInference.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationInference.java
[UTF-8] Fri Mar 25 18:23:14 2016
@@ -20,7 +20,6 @@ import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
import javax.measure.unit.Unit;
-import javax.measure.unit.NonSI;
import javax.measure.quantity.Duration;
import javax.measure.converter.ConversionException;
import org.opengis.util.FactoryException;
@@ -33,24 +32,27 @@ import org.opengis.metadata.Identifier;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.metadata.quality.PositionalAccuracy;
+import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.apache.sis.internal.metadata.ReferencingServices;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
import org.apache.sis.internal.referencing.PositionalAccuracyConstant;
+import org.apache.sis.internal.referencing.provider.GeocentricAffine;
import org.apache.sis.internal.referencing.provider.Affine;
import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.internal.util.Constants;
import org.apache.sis.measure.Units;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.metadata.iso.extent.Extents;
import org.apache.sis.parameter.Parameterized;
+import org.apache.sis.parameter.TensorParameters;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.NamedIdentifier;
-import org.apache.sis.referencing.cs.AxesConvention;
import org.apache.sis.referencing.cs.CoordinateSystems;
import org.apache.sis.referencing.datum.BursaWolfParameters;
import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
-import org.apache.sis.referencing.operation.matrix.Matrix4;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
@@ -61,7 +63,6 @@ import org.apache.sis.util.resources.Err
import org.apache.sis.util.resources.Vocabulary;
import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
-import static
org.apache.sis.internal.referencing.ReferencingUtilities.getGreenwichLongitude;
// Branch-dependent imports
import java.util.Objects;
@@ -89,6 +90,17 @@ import java.util.Objects;
*/
public class CoordinateOperationInference {
/**
+ * The accuracy threshold (in metres) for allowing the use of Molodensky
approximation instead than the
+ * Geocentric Translation method. The accuracy of datum shifts with
Molodensky approximation is about 5
+ * or 10 metres. However for this constant, we are not interested in
absolute accuracy but rather in the
+ * difference between Molodensky and Geocentric Translation methods, which
is much lower. We nevertheless
+ * use a relatively high threshold as a conservative approach.
+ *
+ * @see #desiredAccuracy
+ */
+ private static final double MOLODENSKY_ACCURACY = 5;
+
+ /**
* The identifier for an identity operation.
*/
private static final Identifier IDENTITY =
createIdentifier(Vocabulary.Keys.Identity);
@@ -153,6 +165,8 @@ public class CoordinateOperationInferenc
/**
* The desired accuracy in metres, or 0 for the best accuracy available.
+ *
+ * @see #MOLODENSKY_ACCURACY
*/
private double desiredAccuracy;
@@ -177,23 +191,6 @@ public class CoordinateOperationInferenc
}
/**
- * The operation to use by {@link
#createTransformationStep(GeographicCRS,GeographicCRS)}
- * for datum shift. This string can have one of the following values, from
most accurate
- * to most approximative operations:
- *
- * <ul>
- * <li>{@code null} for performing datum shifts in geocentric
coordinates.</li>
- * <li>{@code "Molodensky"} for the Molodensky transformation.</li>
- * <li>{@code "Abridged Molodensky"} for the abridged Molodensky
transformation.</li>
- * </ul>
- *
- * @todo Value will need to be determined according the desired accuracy.
- */
- private String getMolodenskyMethod() {
- return "Molodensky";
- }
-
- /**
* If the domain of interest was not set, defines it to the domain of
validity of the given CRS.
*/
private void updateDomainOfInterest(final CoordinateReferenceSystem
sourceCRS,
@@ -265,40 +262,23 @@ public class CoordinateOperationInferenc
}
////////////////////////////////////////////////////////////////////////////////
////
////
- //// Geographic → Geographic, Projected or Geocentric
////
+ //// Geodetic → Geocetric, Geographic or Projected
////
////
////
////////////////////////////////////////////////////////////////////////////////
- if (sourceCRS instanceof GeographicCRS) {
- final GeographicCRS source = (GeographicCRS) sourceCRS;
- if (targetCRS instanceof GeographicCRS) {
-// return createOperationStep(source, (GeographicCRS) targetCRS);
+ if (sourceCRS instanceof GeodeticCRS) {
+ final GeodeticCRS source = (GeodeticCRS) sourceCRS;
+ if (targetCRS instanceof GeodeticCRS) {
+ return createOperationStep(source, (GeodeticCRS) targetCRS);
}
if (targetCRS instanceof ProjectedCRS) {
// return createOperationStep(source, (ProjectedCRS) targetCRS);
}
- if (targetCRS instanceof GeocentricCRS) {
-// return createOperationStep(source, (GeocentricCRS) targetCRS);
- }
if (targetCRS instanceof VerticalCRS) {
// return createOperationStep(source, (VerticalCRS) targetCRS);
}
}
////////////////////////////////////////////////////////////////////////////////
////
////
- //// Geocentric → Geocentric or Geographic
////
- ////
////
-
////////////////////////////////////////////////////////////////////////////////
- if (sourceCRS instanceof GeocentricCRS) {
- final GeocentricCRS source = (GeocentricCRS) sourceCRS;
- if (targetCRS instanceof GeocentricCRS) {
- return createOperationStep(source, (GeocentricCRS) targetCRS);
- }
- if (targetCRS instanceof GeographicCRS) {
-// return createOperationStep(source, (GeographicCRS) targetCRS);
- }
- }
-
////////////////////////////////////////////////////////////////////////////////
- ////
////
//// Vertical → Vertical
////
////
////
////////////////////////////////////////////////////////////////////////////////
@@ -323,111 +303,128 @@ public class CoordinateOperationInferenc
}
/**
- * Creates an operation between two geocentric coordinate reference
systems.
- * The default implementation can adjust for axis order, orientation and
units of measurement.
- * If the datums are not the equal but {@linkplain
DefaultGeodeticDatum#getBursaWolfParameters()
- * Bursa-Wolf parameters exists} between the two datum in the area of
interest, then this method
- * will also perform a datum shift.
+ * Creates an operation between two geodetic (geographic or geocentric)
coordinate reference systems.
+ * The default implementation can:
+ *
+ * <ul>
+ * <li>adjust axis order and orientation, for example converting from
(<cite>North</cite>, <cite>West</cite>)
+ * axes to (<cite>East</cite>, <cite>North</cite>) axes,</li>
+ * <li>apply units conversion if needed,</li>
+ * <li>perform longitude rotation if needed,</li>
+ * <li>perform datum shift if {@linkplain BursaWolfParameters Bursa-Wolf
parameters} are available
+ * for the area of interest.</li>
+ * </ul>
*
* @param sourceCRS input coordinate reference system.
* @param targetCRS output coordinate reference system.
* @return a coordinate operation from {@code sourceCRS} to {@code
targetCRS}.
* @throws FactoryException if the operation can not be constructed.
*/
- protected CoordinateOperation createOperationStep(final GeocentricCRS
sourceCRS,
- final GeocentricCRS
targetCRS)
+ protected CoordinateOperation createOperationStep(final GeodeticCRS
sourceCRS,
+ final GeodeticCRS
targetCRS)
throws FactoryException
{
updateDomainOfInterest(sourceCRS, targetCRS);
final GeodeticDatum sourceDatum = sourceCRS.getDatum();
final GeodeticDatum targetDatum = targetCRS.getDatum();
- final CoordinateSystem sourceCS = sourceCRS.getCoordinateSystem();
- final CoordinateSystem targetCS = targetCRS.getCoordinateSystem();
- CoordinateSystem sourceNormalized = null;
- CoordinateSystem targetNormalized = null;
- Matrix datumShift = null; // Those 3 variables
will be null or non-null together.
+ Matrix datumShift = null;
/*
- * If both CRS use the same datum and the same prime meridian, then
the coordinate operation is just
- * an axis swapping, unit conversion or change between spherical and
Cartesian coordinate system type.
+ * If both CRS use the same datum and the same prime meridian, then
the coordinate operation is only axis
+ * swapping, unit conversion or change of coordinate system type
(Ellipsoidal ↔ Cartesian ↔ Spherical).
* Otherwise (if the datum are not the same), we need to perform a
scale, translation and rotation in
- * Cartesian space using the Bursa-Wolf parameters.
+ * Cartesian space using the Bursa-Wolf parameters. If the user does
not require the best accuracy,
+ * then the Molodensky approximation may be used for avoiding the
conversion step to geocentric CRS.
*/
Identifier identifier;
+ final DefaultMathTransformFactory mtFactory =
factorySIS.getDefaultMathTransformFactory();
if (equalsIgnoreMetadata(sourceDatum, targetDatum)) {
- identifier = equalsIgnoreMetadata(sourceCS, targetCS) ? IDENTITY :
AXIS_CHANGES;
+ identifier = AXIS_CHANGES;
} else {
identifier = ELLIPSOID_CHANGE;
if (sourceDatum instanceof DefaultGeodeticDatum) {
datumShift = ((DefaultGeodeticDatum)
sourceDatum).getPositionVectorTransformation(targetDatum, areaOfInterest);
if (datumShift != null) {
identifier = DATUM_SHIFT;
- sourceNormalized =
CommonCRS.WGS84.geocentric().getCoordinateSystem();
- targetNormalized = sourceNormalized;
}
}
+ }
+ /*
+ * If there is a change of prime meridian, concatenate that change
before or after the datum shift.
+ * Actually we do not know if we should concatenate longitude rotation
before or after datum shift.
+ * But this ambiguity does not apply to EPSG dataset 8.9 because
source and target prime meridians
+ * are always Greenwich. For reducing ambiguity in other cases, the
SIS DefaultGeodeticDatum class
+ * ensures that if the prime meridian are not the same, then the
target meridian must be Greenwich.
+ */
+ final DefaultMathTransformFactory.Context context =
ReferencingUtilities.createTransformContext(
+ sourceCRS, targetCRS, new MathTransformContext(sourceDatum,
targetDatum));
+ /*
+ * Conceptually, all transformations below could done by first
converting from the source coordinate
+ * system to geocentric Cartesian coordinates (X,Y,Z), apply an affine
transform represented by the
+ * datum shift matrix, then convert from the (X′,Y′,Z′) coordinates to
the target coordinate system.
+ * However there is two exceptions to this path:
+ *
+ * 1) In the particular where both the source and target CS are
ellipsoidal, we may use the
+ * Molodensky approximation as a shortcut (if the desired
accuracy allows).
+ * 2) Even if we really go through the XYZ coordinates without
Molodensky approximation, there is
+ * at least 9 different ways to name this operation depending on
whether the source and target
+ * CRS are geocentric or geographic, 2- or 3-dimensional, whether
there is a translation or not,
+ * the rotation sign, etc. We try to use the most specific name
if we can find one, and fallback
+ * on an arbitrary name only in last resort.
+ */
+ final CoordinateSystem sourceCS = context.getSourceCS();
+ final CoordinateSystem targetCS = context.getTargetCS();
+ final Map<String,?> properties = properties(identifier);
+ MathTransform before = null, after = null;
+ ParameterValueGroup parameters;
+ if (datumShift != null) {
/*
- * If there is a change of prime meridian, concatenate that change
before or after the datum shift.
- * Actually we do not know if we should concatenate longitude
rotation before or after datum shift.
- * But this ambiguity does not apply to EPSG dataset 8.9 because
source and target prime meridians
- * are always Greenwich. For reducing ambiguity in other cases,
the SIS DefaultGeodeticDatum class
- * ensures that if the prime meridian are not the same, then the
target meridian must be Greenwich.
+ * If the transform can be represented by a single coordinate
operation, returns that operation.
+ * Possible operations are:
+ *
+ * - Geocentric translation (in geocentric,
geographic-2D or geographic-3D domains)
+ * - Position Vector transformation (in geocentric,
geographic-2D or geographic-3D domains)
+ *
+ * Otherwise, maybe we failed to create the operation because the
coordinate system type were not the same.
+ * Convert unconditionally to XYZ geocentric coordinates and apply
the datum shift in that coordinate space.
*/
- final double sourceMeridian =
getGreenwichLongitude(sourceDatum.getPrimeMeridian(), NonSI.DEGREE_ANGLE);
- final double targetMeridian =
getGreenwichLongitude(targetDatum.getPrimeMeridian(), NonSI.DEGREE_ANGLE);
- if (sourceMeridian != targetMeridian) {
- if (sourceNormalized == null) {
- sourceNormalized = CoordinateSystems.replaceAxes(sourceCS,
AxesConvention.NORMALIZED);
- targetNormalized = CoordinateSystems.replaceAxes(targetCS,
AxesConvention.NORMALIZED);
- }
- final boolean isTargetCartesian = (targetNormalized instanceof
CartesianCS);
- if (!(isTargetCartesian || targetNormalized instanceof
SphericalCS)) {
- throw new
FactoryException(Errors.format(Errors.Keys.IllegalCoordinateSystem_1,
targetCS.getClass()));
- }
- final Matrix4 rot = new Matrix4();
- boolean isSource = true;
- do { // Executed exactly twice:
once for source, then once for target.
- double θ = isSource ? sourceMeridian : -targetMeridian;
- if (θ != 0) {
- if (isTargetCartesian) {
- θ = Math.toRadians(θ);
- rot.m00 = rot.m11 = Math.cos(θ);
- rot.m01 = -(rot.m10 = Math.sin(θ));
- } else {
- rot.m02 = θ;
- }
- if (datumShift == null) {
- datumShift = rot;
- } else if (isSource) {
- datumShift = Matrices.multiply(datumShift, rot);
// Apply rotation before datum shift.
- } else {
- datumShift = Matrices.multiply(rot, datumShift);
// Apply rotation after datum shift.
- }
- }
- } while ((isSource = !isSource) == false);
+ parameters = GeocentricAffine.createParameters(sourceCS, targetCS,
datumShift, desiredAccuracy >= MOLODENSKY_ACCURACY);
+ if (parameters == null) {
+ parameters =
TensorParameters.WKT1.createValueGroup(properties, datumShift);
+ final CoordinateSystem normalized =
CommonCRS.WGS84.geocentric().getCoordinateSystem();
+ before = mtFactory.createCoordinateSystemChange(sourceCS,
normalized);
+ after = mtFactory.createCoordinateSystemChange(normalized,
targetCS);
+ context.setSource(normalized);
+ context.setTarget(normalized);
}
+ } else {
+ parameters = TensorParameters.WKT1.createValueGroup(properties);
// Initialized to identity.
+
parameters.parameter(Constants.NUM_COL).setValue(sourceCS.getDimension() + 1);
+
parameters.parameter(Constants.NUM_ROW).setValue(targetCS.getDimension() + 1);
+ before = mtFactory.createCoordinateSystemChange(sourceCS,
targetCS);
+ context.setSource(targetCS);
}
/*
* Transform between differents datums using Bursa Wolf parameters.
The Bursa Wolf parameters are used
* with "standard" geocentric CS, i.e. with X axis towards the prime
meridian, Y axis towards East and
- * Z axis toward North. The following steps are applied:
+ * Z axis toward North, unless the Molodensky approximation is used.
The following steps are applied:
*
* source CRS →
* normalized CRS with source datum →
* normalized CRS with target datum →
* target CRS
+ *
+ * Those steps may be either explicit with the 'before' and 'after'
transform, or implicit with the
+ * Context parameter.
*/
- MathTransform tr;
- final DefaultMathTransformFactory mtFactory =
factorySIS.getDefaultMathTransformFactory();
- if (datumShift != null) {
- final MathTransform normalize =
mtFactory.createCoordinateSystemChange(sourceCS, sourceNormalized);
- final MathTransform denormalize =
mtFactory.createCoordinateSystemChange(targetNormalized, targetCS);
- tr = mtFactory.createAffineTransform(datumShift);
- tr = mtFactory.createConcatenatedTransform(normalize,
- mtFactory.createConcatenatedTransform(tr, denormalize));
- } else {
- tr = mtFactory.createCoordinateSystemChange(sourceCS, targetCS);
+ MathTransform transform =
mtFactory.createParameterizedTransform(parameters, context);
+ final OperationMethod method = mtFactory.getLastMethodUsed();
+ if (before != null) {
+ transform = mtFactory.createConcatenatedTransform(before,
transform);
+ if (after != null) {
+ transform = mtFactory.createConcatenatedTransform(transform,
after);
+ }
}
- return createFromMathTransform(properties(identifier), sourceCRS,
targetCRS, tr, null, null);
+ return createFromMathTransform(properties, sourceCRS, targetCRS,
transform, method, null);
}
/**
Added:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java?rev=1736638&view=auto
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
(added)
+++
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
[UTF-8] Fri Mar 25 18:23:14 2016
@@ -0,0 +1,117 @@
+/*
+ * 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;
+
+import javax.measure.unit.NonSI;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.cs.CartesianCS;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.datum.GeodeticDatum;
+import org.opengis.referencing.operation.Matrix;
+import org.apache.sis.internal.referencing.ReferencingUtilities;
+import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.matrix.Matrix4;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import
org.apache.sis.referencing.operation.transform.ContextualParameters.MatrixRole;
+import
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory.Context;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * Information about the context in which a {@code MathTransform} is created.
+ * This class performs the same normalization than the super-class (namely
axis swapping and unit conversions),
+ * with the addition of longitude rotation for supporting change of prime
meridian. This later change is not
+ * applied by the super-class because prime meridian is part of geodetic
datum, and the public math transform
+ * factory know nothing about datum (on design, for separation of concerns).
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.7
+ * @version 0.7
+ * @module
+ */
+final class MathTransformContext extends Context {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 8765209303733056283L;
+
+ /**
+ * The longitude of the source and target prime meridian, in number of
degrees East of Greenwich.
+ */
+ private double sourceMeridian, targetMeridian;
+
+ /**
+ * Creates a new context which add some datum-related information in
addition
+ * to the information provided by the super-class.
+ */
+ MathTransformContext(final GeodeticDatum source, final GeodeticDatum
target) {
+ final double rs =
ReferencingUtilities.getGreenwichLongitude(source.getPrimeMeridian(),
NonSI.DEGREE_ANGLE);
+ final double rt =
ReferencingUtilities.getGreenwichLongitude(target.getPrimeMeridian(),
NonSI.DEGREE_ANGLE);
+ if (rs != rt) {
+ sourceMeridian = rs;
+ targetMeridian = rt;
+ }
+ }
+
+ /**
+ * Returns the normalization or denormalization matrix.
+ */
+ @Override
+ @SuppressWarnings("fallthrough")
+ public Matrix getMatrix(final MatrixRole role) throws FactoryException {
+ final CoordinateSystem cs;
+ boolean inverse = false;
+ double rotation;
+ switch (role) {
+ default: throw new
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"role", role));
+ case INVERSE_NORMALIZATION: inverse = true; //
Fall through
+ case NORMALIZATION: rotation = sourceMeridian;
+ cs = getSourceCS();
+ break;
+ case INVERSE_DENORMALIZATION: inverse = true; //
Fall through
+ case DENORMALIZATION: inverse = !inverse;
+ rotation = targetMeridian;
+ cs = getTargetCS();
+ break;
+ }
+ Matrix matrix = super.getMatrix(role);
+ if (rotation != 0) {
+ if (inverse) rotation = -rotation;
+ MatrixSIS cm = MatrixSIS.castOrCopy(matrix);
+ if (cs instanceof CartesianCS) {
+ rotation = Math.toRadians(rotation);
+ final Matrix4 rot = new Matrix4();
+ rot.m00 = rot.m11 = Math.cos(rotation);
+ rot.m01 = -(rot.m10 = Math.sin(rotation));
+ if (inverse) {
+ matrix = Matrices.multiply(rot, cm); // Apply the
rotation after denormalization.
+ } else {
+ matrix = cm.multiply(rot); // Apply the
rotation before normalization.
+ }
+ } else {
+ final Double value = rotation;
+ if (inverse) {
+ cm.convertAfter(0, null, value); // Longitude
is the first axis in normalized CS.
+ } else {
+ cm.convertBefore(0, null, value);
+ }
+ matrix = cm;
+ }
+ }
+ return matrix;
+ }
+}
Propchange:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
------------------------------------------------------------------------------
svn:mime-type = text/plain;charset=UTF-8
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java?rev=1736638&r1=1736637&r2=1736638&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
[UTF-8] Fri Mar 25 18:23:14 2016
@@ -71,7 +71,6 @@ import org.apache.sis.referencing.cs.Coo
import org.apache.sis.referencing.factory.InvalidGeodeticParameterException;
import org.apache.sis.referencing.operation.DefaultOperationMethod;
import org.apache.sis.referencing.operation.matrix.Matrices;
-import
org.apache.sis.referencing.operation.matrix.NoninvertibleMatrixException;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.Classes;
@@ -636,33 +635,29 @@ public class DefaultMathTransformFactory
*/
@SuppressWarnings("fallthrough")
public Matrix getMatrix(final ContextualParameters.MatrixRole role)
throws FactoryException {
- final CoordinateSystem source, target;
+ final CoordinateSystem specified;
boolean inverse = false;
switch (role) {
- case INVERSE_NORMALIZATION: inverse = true; // Fall
through
- case NORMALIZATION: {
- source = getSourceCS(); if (source == null) return null;
- target = CoordinateSystems.replaceAxes(source,
AxesConvention.NORMALIZED);
- break;
- }
- case INVERSE_DENORMALIZATION: inverse = true; // Fall
through
- case DENORMALIZATION: {
- target = getTargetCS(); if (target == null) return null;
- source = CoordinateSystems.replaceAxes(target,
AxesConvention.NORMALIZED);
- break;
- }
default: throw new
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"role", role));
+ case INVERSE_NORMALIZATION: inverse = true; //
Fall through
+ case NORMALIZATION: specified = getSourceCS(); break;
+ case INVERSE_DENORMALIZATION: inverse = true; //
Fall through
+ case DENORMALIZATION: inverse = !inverse;
+ specified = getTargetCS(); break;
+ }
+ if (specified == null) {
+ return null;
}
- Matrix matrix;
+ final CoordinateSystem normalized =
CoordinateSystems.replaceAxes(specified, AxesConvention.NORMALIZED);
try {
- matrix = CoordinateSystems.swapAndScaleAxes(source, target);
if (inverse) {
- matrix = Matrices.inverse(matrix);
+ return CoordinateSystems.swapAndScaleAxes(normalized,
specified);
+ } else {
+ return CoordinateSystems.swapAndScaleAxes(specified,
normalized);
}
- } catch (IllegalArgumentException | ConversionException |
NoninvertibleMatrixException cause) {
+ } catch (IllegalArgumentException | ConversionException cause) {
throw new
InvalidGeodeticParameterException(cause.getLocalizedMessage(), cause);
}
- return matrix;
}
/**
@@ -1212,7 +1207,11 @@ public class DefaultMathTransformFactory
*/
@Override
public MathTransform createAffineTransform(final Matrix matrix) throws
FactoryException {
- lastMethod.remove(); // To be strict, we should set the
ProjectiveTransform provider.
+ /*
+ * Performance note: we could set lastMethod to the "Affine" operation
method provider, but we do not
+ * because setting this value is not free (e.g. it depends on matrix
size) and it is rarely needed.
+ */
+ lastMethod.remove();
return unique(MathTransforms.linear(matrix));
}