Author: desruisseaux
Date: Sat Mar 26 17:38:57 2016
New Revision: 1736701
URL: http://svn.apache.org/viewvc?rev=1736701&view=rev
Log:
Search of coordinate operation path now take in account map projections.
Begin tests.
Added:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java
(with props)
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationInferenceTest.java
(with props)
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationInference.java
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
Added:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java?rev=1736701&view=auto
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java
(added)
+++
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java
[UTF-8] Sat Mar 26 17:38:57 2016
@@ -0,0 +1,108 @@
+/*
+ * 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 org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.IdentifiedObject;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.Classes;
+
+// Branch-dependent imports
+import java.util.Objects;
+
+
+/**
+ * A pair of source-destination {@link CoordinateReferenceSystem} objects.
+ * Used as key in hash map.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.7
+ * @version 0.7
+ * @module
+ */
+final class CRSPair {
+ /**
+ * The source and target CRS.
+ */
+ final CoordinateReferenceSystem sourceCRS, targetCRS;
+
+ /**
+ * Creates a {@code CRSPair} for the specified source and target CRS.
+ */
+ public CRSPair(final CoordinateReferenceSystem sourceCRS,
+ final CoordinateReferenceSystem targetCRS)
+ {
+ this.sourceCRS = sourceCRS;
+ this.targetCRS = targetCRS;
+ }
+
+ /**
+ * Returns the hash code value.
+ */
+ @Override
+ public int hashCode() {
+ return sourceCRS.hashCode() * 31 + targetCRS.hashCode();
+ }
+
+ /**
+ * Compares this pair to the specified object for equality.
+ *
+ * {@note We perform the CRS comparison using strict equality, not using
+ * <code>equalsIgnoreMetadata</code>, because metadata matter since
+ * they are attributes of the <code>CoordinateOperation</code>
+ * object to be created.}
+ */
+ @Override
+ public boolean equals(final Object object) {
+ if (object instanceof CRSPair) {
+ final CRSPair that = (CRSPair) object;
+ return Objects.equals(this.sourceCRS, that.sourceCRS) &&
+ Objects.equals(this.targetCRS, that.targetCRS);
+ }
+ return false;
+ }
+
+ /**
+ * Returns a name for the given object, truncating it if needed.
+ */
+ static String shortName(final IdentifiedObject object) {
+ String name = IdentifiedObjects.getName(object, null);
+ if (name == null) {
+ name = Classes.getShortClassName(object);
+ } else {
+ int i = 30; // Arbitrary length threshold.
+ if (name.length() >= i) {
+ while (i > 15) { // Arbitrary minimal length.
+ final int c = name.codePointBefore(i);
+ if (Character.isSpaceChar(c)) break;
+ i -= Character.charCount(c);
+ }
+ name = CharSequences.trimWhitespaces(name, 0, i).toString() +
'…';
+ }
+ }
+ return name;
+ }
+
+ /**
+ * Return a string representation of this key.
+ */
+ @Override
+ public String toString() {
+ return shortName(sourceCRS) + " → " + shortName(targetCRS);
+ }
+}
Propchange:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java
------------------------------------------------------------------------------
svn:mime-type = text/plain;charset=UTF-8
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=1736701&r1=1736700&r2=1736701&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] Sat Mar 26 17:38:57 2016
@@ -37,6 +37,8 @@ import org.opengis.parameter.ParameterDe
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.GeographicToGeocentric;
+import org.apache.sis.internal.referencing.provider.GeocentricToGeographic;
import org.apache.sis.internal.referencing.provider.GeocentricAffine;
import org.apache.sis.internal.referencing.provider.Affine;
import org.apache.sis.internal.system.DefaultFactories;
@@ -57,8 +59,6 @@ import org.apache.sis.referencing.operat
import org.apache.sis.referencing.operation.transform.MathTransforms;
import
org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory;
import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.Classes;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.resources.Vocabulary;
@@ -171,6 +171,12 @@ public class CoordinateOperationInferenc
private double desiredAccuracy;
/**
+ * The pair of source and target CRS for which we already searched a
coordinate operation.
+ * This is used as a safety against infinite recursivity.
+ */
+ private final Map<CRSPair,Boolean> previousSearches;
+
+ /**
* Creates a new instance for the given factory and context.
*
* @param factory The factory to use for creating coordinate operations.
@@ -188,29 +194,18 @@ public class CoordinateOperationInferenc
desiredAccuracy = context.getDesiredAccuracy();
bbox = context.getGeographicBoundingBox();
}
- }
-
- /**
- * 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,
- final CoordinateReferenceSystem
targetCRS)
- {
- if (bbox == null) {
- bbox =
Extents.intersection(CRS.getGeographicBoundingBox(sourceCRS),
-
CRS.getGeographicBoundingBox(targetCRS));
- areaOfInterest =
CoordinateOperationContext.setGeographicBoundingBox(areaOfInterest, bbox);
- }
+ previousSearches = new HashMap<>(4);
}
/**
* Infers an operation for conversion or transformation between two
coordinate reference systems.
* This method inspects the given CRS and delegates the work to one or
many {@code createOperationStep(…)} methods.
- * This method fails if no path between the CRS is found.
+ * Note that some {@code createOperationStep(…)} methods may callback this
{@code createOperation(…)} method with
+ * another source or target CRS.
*
- * @param sourceCRS Input coordinate reference system.
- * @param targetCRS Output coordinate reference system.
- * @return A coordinate operation from {@code sourceCRS} to {@code
targetCRS}.
+ * @param sourceCRS input coordinate reference system.
+ * @param targetCRS output coordinate reference system.
+ * @return a coordinate operation from {@code sourceCRS} to {@code
targetCRS}.
* @throws OperationNotFoundException if no operation path was found from
{@code sourceCRS} to {@code targetCRS}.
* @throws FactoryException if the operation creation failed for some
other reason.
*/
@@ -220,6 +215,18 @@ public class CoordinateOperationInferenc
{
ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS);
ArgumentChecks.ensureNonNull("targetCRS", targetCRS);
+ if (!previousSearches.isEmpty()) {
+ // TODO: verify here if the path is defined in EPSG database.
+ }
+ final CRSPair key = new CRSPair(sourceCRS, targetCRS);
+ if (previousSearches.put(key, Boolean.TRUE) != null) {
+ throw new
FactoryException(Errors.format(Errors.Keys.RecursiveCreateCallForCode_2,
CoordinateOperation.class, key));
+ }
+ if (bbox == null) {
+ bbox =
Extents.intersection(CRS.getGeographicBoundingBox(sourceCRS),
+
CRS.getGeographicBoundingBox(targetCRS));
+ areaOfInterest =
CoordinateOperationContext.setGeographicBoundingBox(areaOfInterest, bbox);
+ }
////////////////////////////////////////////////////////////////////////////////
////
////
//// Compound → various CRS
////
@@ -245,19 +252,27 @@ public class CoordinateOperationInferenc
}
////////////////////////////////////////////////////////////////////////////////
////
////
- //// Projected → Projected or Geographic
////
- ////
////
- //// This check needs to be done before the check for DerivedCRS
////
- //// because ProjectedCRS is a particular kind of derived CRS.
////
+ //// Derived → any Single CRS
////
////
////
////////////////////////////////////////////////////////////////////////////////
- if (sourceCRS instanceof ProjectedCRS) {
- final ProjectedCRS source = (ProjectedCRS) sourceCRS;
- if (targetCRS instanceof ProjectedCRS) {
-// return createOperationStep(source, (ProjectedCRS) targetCRS);
+ if (sourceCRS instanceof GeneralDerivedCRS) {
+ final GeneralDerivedCRS source = (GeneralDerivedCRS) sourceCRS;
+ if (targetCRS instanceof GeneralDerivedCRS) {
+ return createOperationStep(source, (GeneralDerivedCRS)
targetCRS);
}
- if (targetCRS instanceof GeographicCRS) {
-// return createOperationStep(source, (GeographicCRS) targetCRS);
+ if (targetCRS instanceof SingleCRS) {
+ return createOperationStep(source, (SingleCRS) targetCRS);
+ }
+ }
+
////////////////////////////////////////////////////////////////////////////////
+ ////
////
+ //// any Single CRS → Derived
////
+ ////
////
+
////////////////////////////////////////////////////////////////////////////////
+ if (targetCRS instanceof GeneralDerivedCRS) {
+ final GeneralDerivedCRS target = (GeneralDerivedCRS) targetCRS;
+ if (sourceCRS instanceof SingleCRS) {
+ return createOperationStep((SingleCRS) sourceCRS, target);
}
}
////////////////////////////////////////////////////////////////////////////////
@@ -270,9 +285,6 @@ public class CoordinateOperationInferenc
if (targetCRS instanceof GeodeticCRS) {
return createOperationStep(source, (GeodeticCRS) targetCRS);
}
- if (targetCRS instanceof ProjectedCRS) {
-// return createOperationStep(source, (ProjectedCRS) targetCRS);
- }
if (targetCRS instanceof VerticalCRS) {
// return createOperationStep(source, (VerticalCRS) targetCRS);
}
@@ -303,6 +315,89 @@ public class CoordinateOperationInferenc
}
/**
+ * Creates an operation from an arbitrary single CRS to a derived
coordinate reference system.
+ * Conversions from {@code GeographicCRS} to {@code ProjectedCRS} are also
handled by this method,
+ * since projected CRS are a special kind of {@code GeneralDerivedCRS}.
+ *
+ * <p>The default implementation constructs the following operation
chain:</p>
+ * <blockquote><code>sourceCRS → {@linkplain
GeneralDerivedCRS#getBaseCRS() baseCRS} → targetCRS</code></blockquote>
+ *
+ * where the conversion from {@code baseCRS} to {@code targetCRS} is
obtained from
+ * <code>targetCRS.{@linkplain GeneralDerivedCRS#getConversionFromBase()
getConversionFromBase()}</code>.
+ *
+ * @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 SingleCRS
sourceCRS,
+ final GeneralDerivedCRS
targetCRS)
+ throws FactoryException
+ {
+ CoordinateOperation step1 = createOperation(sourceCRS,
targetCRS.getBaseCRS());
+ return concatenate(step1, targetCRS.getConversionFromBase());
+ }
+
+ /**
+ * Creates an operation from a derived CRS to an arbitrary single
coordinate reference system.
+ * Conversions from {@code ProjectedCRS} to {@code GeographicCRS} are also
handled by this method,
+ * since projected CRS are a special kind of {@code GeneralDerivedCRS}.
+ *
+ * <p>The default implementation constructs the following operation
chain:</p>
+ * <blockquote><code>sourceCRS → {@linkplain
GeneralDerivedCRS#getBaseCRS() baseCRS} → targetCRS</code></blockquote>
+ *
+ * where the conversion from {@code sourceCRS} to {@code baseCRS} is
obtained from the inverse of
+ * <code>sourceCRS.{@linkplain GeneralDerivedCRS#getConversionFromBase()
getConversionFromBase()}</code>.
+ *
+ * @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 GeneralDerivedCRS
sourceCRS,
+ final SingleCRS
targetCRS)
+ throws FactoryException
+ {
+ final SingleCRS base = sourceCRS.getBaseCRS();
+ CoordinateOperation step2 = createOperation(base, targetCRS);
+ CoordinateOperation step1 = sourceCRS.getConversionFromBase();
+ MathTransform transform = step1.getMathTransform();
+ try {
+ transform = transform.inverse();
+ } catch (NoninvertibleTransformException exception) {
+ throw new OperationNotFoundException(notFoundMessage(sourceCRS,
base), exception);
+ }
+ step1 = createFromMathTransform(properties(INVERSE_OPERATION),
sourceCRS, base, transform, null, null);
+ return concatenate(step1, step2);
+ }
+
+ /**
+ * Creates an operation between two derived coordinate reference systems.
+ * The default implementation performs three steps:
+ *
+ * <ol>
+ * <li>Convert from {@code sourceCRS} to its base CRS.</li>
+ * <li>Convert the source base CRS to target base CRS.</li>
+ * <li>Convert from the target base CRS to the {@code targetCRS}.</li>
+ * </ol>
+ *
+ * @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 GeneralDerivedCRS
sourceCRS,
+ final GeneralDerivedCRS
targetCRS)
+ throws FactoryException
+ {
+ final SingleCRS sourceBase = sourceCRS.getBaseCRS();
+ final SingleCRS targetBase = targetCRS.getBaseCRS();
+ return concatenate(createOperation(sourceCRS, sourceBase),
+ createOperation(sourceBase, targetBase),
+ createOperation(targetBase, targetCRS));
+ }
+
+ /**
* Creates an operation between two geodetic (geographic or geocentric)
coordinate reference systems.
* The default implementation can:
*
@@ -324,21 +419,44 @@ public class CoordinateOperationInferenc
final GeodeticCRS
targetCRS)
throws FactoryException
{
- updateDomainOfInterest(sourceCRS, targetCRS);
final GeodeticDatum sourceDatum = sourceCRS.getDatum();
final GeodeticDatum targetDatum = targetCRS.getDatum();
Matrix datumShift = null;
/*
+ * If the prime meridian is not the same, we will concatenate a
longitude rotation before or after datum shift
+ * (that concatenation will be performed by the customized
DefaultMathTransformFactory.Context created below).
+ * Actually we do not know if the longitude rotation should be before
or after datum shift. But this ambiguity
+ * can usually be ignored because Bursa-Wolf parameters are always
used with source and target prime meridians
+ * set to Greenwich in EPSG dataset 8.9. For safety, the SIS's
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));
+ /*
* 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. If the user does
not require the best accuracy,
+ * Otherwise (if the datum are not the same), we will need to perform
a scale, translation and rotation
+ * in 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();
+ boolean isGeographicToGeocentric = false;
+ final CoordinateSystem sourceCS = context.getSourceCS();
+ final CoordinateSystem targetCS = context.getTargetCS();
if (equalsIgnoreMetadata(sourceDatum, targetDatum)) {
- identifier = AXIS_CHANGES;
+ final boolean isGeocentricToGeographic;
+ isGeographicToGeocentric = (sourceCS instanceof EllipsoidalCS &&
targetCS instanceof CartesianCS);
+ isGeocentricToGeographic = (sourceCS instanceof CartesianCS &&
targetCS instanceof EllipsoidalCS);
+ /*
+ * Above booleans should never be true in same time. If it
nevertheless happen (we are paranoiac;
+ * maybe a lazy user implemented all interfaces in a single
class), do not apply any geographic ↔
+ * geocentric conversion. Instead do as if the coordinate system
types were the same.
+ */
+ if (isGeocentricToGeographic ^ isGeographicToGeocentric) {
+ identifier = GEOCENTRIC_CONVERSION;
+ } else {
+ identifier = AXIS_CHANGES;
+ }
} else {
identifier = ELLIPSOID_CHANGE;
if (sourceDatum instanceof DefaultGeodeticDatum) {
@@ -349,15 +467,6 @@ public class CoordinateOperationInferenc
}
}
/*
- * 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.
@@ -365,15 +474,14 @@ public class CoordinateOperationInferenc
*
* 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);
+ final DefaultMathTransformFactory mtFactory =
factorySIS.getDefaultMathTransformFactory();
MathTransform before = null, after = null;
ParameterValueGroup parameters;
if (datumShift != null) {
@@ -389,15 +497,18 @@ public class CoordinateOperationInferenc
*/
parameters = GeocentricAffine.createParameters(sourceCS, targetCS,
datumShift, desiredAccuracy >= MOLODENSKY_ACCURACY);
if (parameters == null) {
- parameters =
TensorParameters.WKT1.createValueGroup(properties, datumShift);
+ parameters =
TensorParameters.WKT1.createValueGroup(properties(Constants.AFFINE),
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 if (identifier == GEOCENTRIC_CONVERSION) {
+ parameters = (isGeographicToGeocentric ?
GeographicToGeocentric.PARAMETERS
+ :
GeocentricToGeographic.PARAMETERS).createValue();
} else {
- parameters = TensorParameters.WKT1.createValueGroup(properties);
// Initialized to identity.
+ parameters =
TensorParameters.WKT1.createValueGroup(properties(Constants.AFFINE)); //
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);
@@ -424,7 +535,7 @@ public class CoordinateOperationInferenc
transform = mtFactory.createConcatenatedTransform(transform,
after);
}
}
- return createFromMathTransform(properties, sourceCRS, targetCRS,
transform, method, null);
+ return createFromMathTransform(properties(identifier), sourceCRS,
targetCRS, transform, method, null);
}
/**
@@ -443,7 +554,6 @@ public class CoordinateOperationInferenc
final VerticalCRS
targetCRS)
throws FactoryException
{
- updateDomainOfInterest(sourceCRS, targetCRS);
final VerticalDatum sourceDatum = sourceCRS.getDatum();
final VerticalDatum targetDatum = targetCRS.getDatum();
if (!equalsIgnoreMetadata(sourceDatum, targetDatum)) {
@@ -474,7 +584,6 @@ public class CoordinateOperationInferenc
final TemporalCRS
targetCRS)
throws FactoryException
{
- updateDomainOfInterest(sourceCRS, targetCRS);
final TemporalDatum sourceDatum = sourceCRS.getDatum();
final TemporalDatum targetDatum = targetCRS.getDatum();
final TimeCS sourceCS = sourceCRS.getCoordinateSystem();
@@ -645,8 +754,6 @@ public class CoordinateOperationInferenc
final CoordinateOperation step2)
throws FactoryException
{
- if (step1 == null) return step2;
- if (step2 == null) return step1;
if (isIdentity(step1)) return step2;
if (isIdentity(step2)) return step1;
final MathTransform mt1 = step1.getMathTransform();
@@ -671,13 +778,38 @@ public class CoordinateOperationInferenc
}
/**
+ * Concatenates three transformation steps. If the first and/or the last
operation is an {@link #AXIS_CHANGES},
+ * then it will be included as part of the second operation instead of
creating a {@link ConcatenatedOperation}.
+ * If a concatenated operation is created, it will get an automatically
generated name.
+ *
+ * @param step1 The first step, or {@code null} for the identity
operation.
+ * @param step2 The second step, or {@code null} for the identity
operation.
+ * @param step3 The third step, or {@code null} for the identity
operation.
+ * @return A concatenated operation, or {@code null} if all arguments were
null.
+ * @throws FactoryException if the operation can not be constructed.
+ */
+ protected CoordinateOperation concatenate(final CoordinateOperation step1,
+ final CoordinateOperation step2,
+ final CoordinateOperation step3)
+ throws FactoryException
+ {
+ if (isIdentity(step1)) return concatenate(step2, step3);
+ if (isIdentity(step2)) return concatenate(step1, step3);
+ if (isIdentity(step3)) return concatenate(step1, step2);
+ if (step1.getName() == AXIS_CHANGES) return
concatenate(concatenate(step1, step2), step3);
+ if (step3.getName() == AXIS_CHANGES) return concatenate(step1,
concatenate(step2, step3));
+ final Map<String,?> properties = defaultName(step1.getSourceCRS(),
step3.getTargetCRS());
+ return factory.createConcatenatedOperation(properties, step1, step2,
step3);
+ }
+
+ /**
* Returns {@code true} if the specified operation is an identity
conversion.
* This method always returns {@code false} for transformations even if
their
* associated math transform is an identity one, because such
transformations
* are usually datum shift and must be visible.
*/
private static boolean isIdentity(final CoordinateOperation operation) {
- return (operation instanceof Conversion) &&
operation.getMathTransform().isIdentity();
+ return (operation == null) || ((operation instanceof Conversion) &&
operation.getMathTransform().isIdentity());
}
/**
@@ -706,32 +838,17 @@ public class CoordinateOperationInferenc
}
/**
- * Returns a name for the given object, truncating it if needed.
+ * Returns the given name in a singleton map.
*/
- private static String shortName(final IdentifiedObject object) {
- String name = IdentifiedObjects.getName(object, null);
- if (name == null) {
- name = Classes.getShortClassName(object);
- } else {
- int i = 30; // Arbitrary length threshold.
- if (name.length() >= i) {
- while (i > 15) { // Arbitrary minimal length.
- final int c = name.codePointBefore(i);
- if (Character.isSpaceChar(c)) break;
- i -= Character.charCount(c);
- }
- name = CharSequences.trimWhitespaces(name, 0, i).toString() +
'…';
- }
- }
- return name;
+ private static Map<String,?> properties(final String name) {
+ return Collections.singletonMap(IdentifiedObject.NAME_KEY, name);
}
/**
- * Returns a temporary name for a transformation between two CRS.
+ * Returns a name for a transformation between two CRS.
*/
private static Map<String,?> defaultName(CoordinateReferenceSystem source,
CoordinateReferenceSystem target) {
- final String name = shortName(source) + " → " + shortName(target);
- return Collections.singletonMap(IdentifiedObject.NAME_KEY, name);
+ return properties(CRSPair.shortName(source) + " → " +
CRSPair.shortName(target));
}
/**
@@ -743,6 +860,6 @@ public class CoordinateOperationInferenc
* @return A default error message.
*/
private static String notFoundMessage(final IdentifiedObject source, final
IdentifiedObject target) {
- return Errors.format(Errors.Keys.CoordinateOperationNotFound_2,
shortName(source), shortName(target));
+ return Errors.format(Errors.Keys.CoordinateOperationNotFound_2,
CRSPair.shortName(source), CRSPair.shortName(target));
}
}
Added:
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationInferenceTest.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationInferenceTest.java?rev=1736701&view=auto
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationInferenceTest.java
(added)
+++
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationInferenceTest.java
[UTF-8] Sat Mar 26 17:38:57 2016
@@ -0,0 +1,85 @@
+/*
+ * 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 org.opengis.util.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.apache.sis.referencing.CommonCRS;
+
+// Test dependencies
+import org.apache.sis.referencing.operation.transform.MathTransformTestCase;
+import org.apache.sis.test.DependsOn;
+import org.junit.BeforeClass;
+import org.junit.AfterClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link CoordinateOperationInference}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.7
+ * @version 0.7
+ * @module
+ */
+@DependsOn({
+ DefaultConversionTest.class,
+ DefaultTransformationTest.class,
+ DefaultPassThroughOperationTest.class,
+ DefaultConcatenatedOperationTest.class
+})
+public final strictfp class CoordinateOperationInferenceTest extends
MathTransformTestCase {
+ /**
+ * The transformation factory to use for testing.
+ */
+ private static DefaultCoordinateOperationFactory factory;
+
+ /**
+ * Creates a new {@link DefaultCoordinateOperationFactory} to use for
testing purpose.
+ * The same factory will be used for all tests in this class.
+ */
+ @BeforeClass
+ public static void createFactory() {
+ factory = new DefaultCoordinateOperationFactory();
+ }
+
+ /**
+ * Disposes the factory created by {@link #createFactory()} after all
tests have been executed.
+ */
+ @AfterClass
+ public static void disposeFactory() {
+ factory = null;
+ }
+
+ /**
+ * Makes sure that {@code createOperation(sourceCRS, targetCRS)} returns
an identity transform
+ * when {@code sourceCRS} and {@code targetCRS} are identical.
+ *
+ * @throws FactoryException if the operation can not be created.
+ */
+ @Test
+ public void testIdentityTransform() throws FactoryException {
+ CoordinateReferenceSystem crs = CommonCRS.WGS84.normalizedGeographic();
+ CoordinateOperation operation = factory.createOperation(crs, crs);
+ assertSame("sourceCRS", crs, operation.getSourceCRS());
+ assertSame("targetCRS", crs, operation.getTargetCRS());
+ assertTrue("isIdentity", operation.getMathTransform().isIdentity());
+ }
+}
Propchange:
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationInferenceTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationInferenceTest.java
------------------------------------------------------------------------------
svn:mime-type = text/plain;charset=UTF-8
Modified:
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java?rev=1736701&r1=1736700&r2=1736701&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
[UTF-8] Sat Mar 26 17:38:57 2016
@@ -170,6 +170,7 @@ import org.junit.BeforeClass;
org.apache.sis.referencing.operation.SingleOperationMarshallingTest.class,
org.apache.sis.referencing.operation.DefaultPassThroughOperationTest.class,
org.apache.sis.referencing.operation.DefaultConcatenatedOperationTest.class,
+
org.apache.sis.referencing.operation.CoordinateOperationInferenceTest.class,
org.apache.sis.referencing.crs.DefaultProjectedCRSTest.class,
org.apache.sis.referencing.crs.DefaultDerivedCRSTest.class,
org.apache.sis.referencing.crs.SubTypesTest.class,