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 3a6d0acf80 Deprecate `DefaultMathTransformFactory.createParameterizedTransform(ParameterValueGroup, Context)`. This is replaced by a builder. 3a6d0acf80 is described below commit 3a6d0acf805e39a21eab1070eed03edf5cd14944 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sun Jun 30 05:00:29 2024 +0200 Deprecate `DefaultMathTransformFactory.createParameterizedTransform(ParameterValueGroup, Context)`. This is replaced by a builder. --- .../referencing/factory/sql/EPSGDataAccess.java | 3 +- .../operation/AbstractSingleOperation.java | 4 +- .../operation/CoordinateOperationFinder.java | 16 +- .../operation/CoordinateOperationRegistry.java | 41 +- .../referencing/operation/DefaultConversion.java | 42 +- .../DefaultCoordinateOperationFactory.java | 3 +- .../operation/MathTransformContext.java | 5 - .../operation/projection/package-info.java | 5 +- .../transform/DefaultMathTransformFactory.java | 512 ++++++++++++++------- .../referencing/privy/ReferencingUtilities.java | 68 +-- .../operation/provider/GeographicOffsetsTest.java | 13 +- .../transform/DefaultMathTransformFactoryTest.java | 18 +- .../transform/MathTransformFactoryBase.java | 8 + .../transform/MathTransformFactoryMock.java | 13 + geoapi/snapshot | 2 +- 15 files changed, 451 insertions(+), 302 deletions(-) diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java index 497646caba..f4694584cd 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java @@ -2950,7 +2950,8 @@ next: while (r.next()) { */ opProperties = new HashMap<>(opProperties); // Because this class uses a shared map. final MathTransformFactory mtFactory = owner.mtFactory; - final MathTransform mt = ReferencingUtilities.createBaseToDerived(mtFactory, sourceCRS, parameters, targetCRS); + final MathTransform mt = ReferencingUtilities.builder( + mtFactory, parameters, sourceCRS, targetCRS).create(); /* * Give a hint to the factory about the type of the coordinate operation. ISO 19111 defines * Conversion and Transformation, but SIS also have more specific sub-types. We begin with diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractSingleOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractSingleOperation.java index a721f0d225..de3287b2e1 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractSingleOperation.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractSingleOperation.java @@ -468,8 +468,8 @@ class AbstractSingleOperation extends AbstractCoordinateOperation implements Sin final CoordinateReferenceSystem sourceCRS = super.getSourceCRS(); final CoordinateReferenceSystem targetCRS = super.getTargetCRS(); if (transform == null && sourceCRS != null && targetCRS != null && parameters != null) try { - transform = ReferencingUtilities.createBaseToDerived(DefaultMathTransformFactory.provider(), - sourceCRS, parameters, targetCRS); + transform = ReferencingUtilities.builder(DefaultMathTransformFactory.provider(), + parameters, sourceCRS, targetCRS).create(); } catch (FactoryException e) { Context.warningOccured(Context.current(), AbstractSingleOperation.class, "afterUnmarshal", e, true); } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java index 39a7d78e28..1c2313349b 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationFinder.java @@ -513,6 +513,8 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { final GeodeticCRS targetCRS) throws FactoryException { + final CoordinateSystem sourceCS = sourceCRS.getCoordinateSystem(); + final CoordinateSystem targetCS = targetCRS.getCoordinateSystem(); final GeodeticDatum sourceDatum = sourceCRS.getDatum(); final GeodeticDatum targetDatum = targetCRS.getDatum(); Matrix datumShift = null; @@ -525,8 +527,8 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { * prime meridians are not the same, then the target meridian must be Greenwich. */ final DefaultMathTransformFactory.Context context = new MathTransformContext(sourceDatum, targetDatum); - context.setSource(sourceCRS); - context.setTarget(targetCRS); + context.setSourceAxes(sourceCS, sourceDatum.getEllipsoid()); + context.setTargetAxes(targetCS, targetDatum.getEllipsoid()); /* * 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). @@ -536,8 +538,6 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { */ Identifier identifier; boolean isGeographicToGeocentric = false; - final CoordinateSystem sourceCS = context.getSourceCS(); - final CoordinateSystem targetCS = context.getTargetCS(); if (equalsIgnoreMetadata(sourceDatum, targetDatum)) { final boolean isGeocentricToGeographic; isGeographicToGeocentric = (sourceCS instanceof EllipsoidalCS && targetCS instanceof CartesianCS); @@ -612,8 +612,8 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { final CoordinateSystem normalized = CommonCRS.WGS84.geocentric().getCoordinateSystem(); before = mtFactory.createCoordinateSystemChange(sourceCS, normalized, sourceDatum.getEllipsoid()); after = mtFactory.createCoordinateSystemChange(normalized, targetCS, targetDatum.getEllipsoid()); - context.setSource(normalized); - context.setTarget(normalized); + context.setSourceAxes(normalized, null); + context.setTargetAxes(normalized, null); /* * The name of the `parameters` group determines the `OperationMethod` later in this method. * We cannot leave that name to "Affine" if `before` or `after` transforms are not identity. @@ -655,7 +655,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { */ before = mtFactory.createCoordinateSystemChange(sourceCS, targetCS, (sourceCS instanceof EllipsoidalCS ? sourceDatum : targetDatum).getEllipsoid()); - context.setSource(targetCS); + context.setSourceAxes(targetCS, null); method = mtFactory.getLastMethodUsed(); } } @@ -675,7 +675,7 @@ public class CoordinateOperationFinder extends CoordinateOperationRegistry { */ MathTransform transform = mtFactory.createParameterizedTransform(parameters, context); if (method == null) { - method = context.getMethodUsed(); + method = context.getMethod().orElse(null); } if (before != null) { parameters = null; // Providing parameters would be misleading because they apply to only a step of the operation. diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java index 6bbb7f6814..c4aa7c3866 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java @@ -53,7 +53,6 @@ import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.referencing.cs.CoordinateSystems; import org.apache.sis.referencing.operation.matrix.Matrices; import org.apache.sis.referencing.operation.transform.MathTransforms; -import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory; import org.apache.sis.referencing.factory.IdentifiedObjectFinder; import org.apache.sis.referencing.factory.GeodeticAuthorityFactory; import org.apache.sis.referencing.factory.UnavailableFactoryException; @@ -1023,13 +1022,11 @@ class CoordinateOperationRegistry { CoordinateReferenceSystem crs; if (Utilities.equalsApproximately(sourceCRS, crs = operation.getSourceCRS())) sourceCRS = crs; if (Utilities.equalsApproximately(targetCRS, crs = operation.getTargetCRS())) targetCRS = crs; - final MathTransformFactory mtFactory = factorySIS.getMathTransformFactory(); - if (mtFactory instanceof DefaultMathTransformFactory) { - MathTransform mt = ((DefaultMathTransformFactory) mtFactory).createParameterizedTransform( - parameters, ReferencingUtilities.createTransformContext(sourceCRS, targetCRS)); - return factorySIS.createSingleOperation(IdentifiedObjects.getProperties(operation), - sourceCRS, targetCRS, null, operation.getMethod(), mt); - } + var builder = ReferencingUtilities.builder( + factorySIS.getMathTransformFactory(), parameters, sourceCRS, targetCRS); + final MathTransform mt = builder.create(); // Must be before `operation.getMethod()`. + return factorySIS.createSingleOperation(IdentifiedObjects.getProperties(operation), + sourceCRS, targetCRS, null, operation.getMethod(), mt); } else { // Should never happen because parameters are mandatory, but let be safe. log(resources().getLogRecord(Level.WARNING, Resources.Keys.MissingParameterValues_1, @@ -1141,22 +1138,20 @@ class CoordinateOperationRegistry { if (matrix == null) { if (op instanceof SingleOperation) { final MathTransformFactory mtFactory = factorySIS.getMathTransformFactory(); - if (mtFactory instanceof DefaultMathTransformFactory) { - if (forward) sourceCRS = toGeodetic3D(sourceCRS, source3D); - else targetCRS = toGeodetic3D(targetCRS, target3D); - final DefaultMathTransformFactory.Context context; - final MathTransform mt; - try { - context = ReferencingUtilities.createTransformContext(sourceCRS, targetCRS); - mt = ((DefaultMathTransformFactory) mtFactory).createParameterizedTransform( - ((SingleOperation) op).getParameterValues(), context); - } catch (InvalidGeodeticParameterException e) { - log(null, e); - break; - } - operations.set(recreate(op, sourceCRS, targetCRS, mt, context.getMethodUsed())); - return true; + if (forward) sourceCRS = toGeodetic3D(sourceCRS, source3D); + else targetCRS = toGeodetic3D(targetCRS, target3D); + final MathTransform.Builder builder; + final MathTransform mt; + try { + final var parameters = ((SingleOperation) op).getParameterValues(); + builder = ReferencingUtilities.builder(mtFactory, parameters, sourceCRS, targetCRS); + mt = builder.create(); + } catch (InvalidGeodeticParameterException e) { + log(null, e); + break; } + operations.set(recreate(op, sourceCRS, targetCRS, mt, builder.getMethod().orElse(null))); + return true; } break; } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java index b3d96d3e2a..f7358bb8d5 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/DefaultConversion.java @@ -237,41 +237,31 @@ public class DefaultConversion extends AbstractSingleOperation implements Conver if (parameters == null) { throw new IllegalArgumentException(Resources.format(Resources.Keys.UnspecifiedParameterValues)); } - if (factory instanceof DefaultMathTransformFactory) { - /* - * Apache SIS specific API (not yet defined in GeoAPI, but could be proposed). - * Note that setTarget(…) intentionally uses only the CoordinateSystem instead of the full - * CoordinateReferenceSystem because the targetCRS is typically under construction when this - * method in invoked, and attempts to use it can cause NullPointerException. - */ - final DefaultMathTransformFactory.Context context; - if (target instanceof DerivedCRS) { - context = ReferencingUtilities.createTransformContext(source, null); - context.setTarget(target.getCoordinateSystem()); // Using `target` would be unsafe here. - } else { - context = ReferencingUtilities.createTransformContext(source, target); - } - transform = ((DefaultMathTransformFactory) factory).createParameterizedTransform(parameters, context); + /* + * Note that setTargetAxes(…) intentionally uses only the CoordinateSystem instead of the full + * CoordinateReferenceSystem because the targetCRS is typically under construction when this + * method in invoked, and attempts to use it can cause NullPointerException. + */ + final boolean isDerived = (target instanceof DerivedCRS); + var builder = ReferencingUtilities.builder(factory, parameters, source, isDerived ? null : target); + if (isDerived) { + builder.setTargetAxes(target.getCoordinateSystem(), null); + } + transform = builder.create(); + if (builder instanceof DefaultMathTransformFactory.Context) { + final var context = (DefaultMathTransformFactory.Context) builder; setParameterValues(context.getCompletedParameters(), context.getContextualParameters()); - } else { - /* - * Fallback for non-SIS implementation. Equivalent to the above code, except that we can - * not get the parameters completed with semi-major and semi-minor axis lengths. Most of - * the code should work anyway. - */ - transform = factory.createBaseToDerived(source, parameters, target.getCoordinateSystem()); } } else { /* * If the user specified explicitly a MathTransform, we may still need to swap or scale axes. * If this conversion is a defining conversion (which is usually the case when creating a new - * ProjectedCRS), then DefaultMathTransformFactory has a specialized createBaseToDerived(…) - * method for this job. + * ProjectedCRS), then MathTransformFactory has a specialized builder(…) method for this job. */ if (sourceCRS == null && targetCRS == null && factory instanceof DefaultMathTransformFactory) { final var context = new DefaultMathTransformFactory.Context(); - context.setSource(source.getCoordinateSystem()); - context.setTarget(target.getCoordinateSystem()); // See comment on the other setTarget(…) call. + context.setSourceAxes(source.getCoordinateSystem(), null); + context.setTargetAxes(target.getCoordinateSystem(), null); // See comment on the other setTargetAxes(…) call. transform = ((DefaultMathTransformFactory) factory).swapAndScaleAxes(transform, context); } else { /* 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 80e25bccd7..52c794c1cc 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 @@ -506,7 +506,8 @@ next: for (int i=components.size(); --i >= 0;) { if (parameters == null) { throw new NullPointerException(Errors.format(Errors.Keys.NullArgument_1, "transform")); } - transform = ReferencingUtilities.createBaseToDerived(getMathTransformFactory(), sourceCRS, parameters, targetCRS); + transform = ReferencingUtilities.builder( + getMathTransformFactory(), parameters, sourceCRS, targetCRS).create(); } /* * The "operationType" property is currently undocumented. The intent is to help this factory method in diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java index 90b94e6f6d..350c25ed91 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/MathTransformContext.java @@ -43,11 +43,6 @@ import org.apache.sis.measure.Units; * @author Martin Desruisseaux (Geomatys) */ 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. */ diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/package-info.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/package-info.java index 69564d8421..7b28b53b7b 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/package-info.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/package-info.java @@ -99,9 +99,8 @@ * The real axis flip is performed outside this projection package, upon * {@linkplain org.opengis.referencing.cs.CoordinateSystemAxis coordinate system axis} inspection, * as a concatenation of the North oriented cartographic projection with an affine transform. - * Such axis analysis and transforms concatenation can be performed automatically by the - * {@link org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory#createBaseToDerived - * createBaseToDerived(…)} method defined in the {@code MathTransformFactory} interface. + * Such axis analysis and transforms concatenation can be performed automatically by + * {@link org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory.Context}. * The same rule applies to the <cite>Krovak</cite> projection as well (at the opposite of what ESRI does). * * <p>In order to reduce the risk of confusion, this package never defines south oriented map projection. diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java index 84e54a6232..d11a29a3e5 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.IdentityHashMap; import java.util.ServiceLoader; import java.util.Objects; +import java.util.Optional; import java.util.OptionalInt; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -29,7 +30,6 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.LogRecord; import java.lang.reflect.Constructor; -import java.io.Serializable; import javax.measure.Unit; import javax.measure.IncommensurableException; import javax.measure.quantity.Length; @@ -45,6 +45,7 @@ import org.opengis.referencing.crs.GeodeticCRS; import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.cs.EllipsoidalCS; import org.opengis.referencing.cs.CartesianCS; +import org.opengis.referencing.cs.SphericalCS; import org.opengis.referencing.datum.Ellipsoid; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.MathTransform; @@ -85,6 +86,9 @@ import org.apache.sis.util.iso.AbstractFactory; import org.apache.sis.util.logging.Logging; import org.apache.sis.util.resources.Errors; +// Specific to the geoapi-3.1 and geoapi-4.0 branches: +import org.opengis.util.UnimplementedServiceException; + /** * Low level factory for creating {@linkplain AbstractMathTransform math transforms}. @@ -476,6 +480,39 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math return method; } + /** + * Returns a builder for a parameterized math transform using the specified operation method. + * The {@code method} argument should be the name or identifier of an {@link OperationMethod} + * instance returned by <code>{@link #getAvailableMethods(Class) getAvailableMethods}(null)</code>. + * + * <p>The returned builder allows to specify not only the operation parameter values, + * but also some contextual information such as the source and target axes. + * The builder uses these information for:</p> + * + * <ol> + * <li>Inferring the {@code "semi_major"}, {@code "semi_minor"}, {@code "src_semi_major"}, + * {@code "src_semi_minor"}, {@code "tgt_semi_major"} or {@code "tgt_semi_minor"} parameter values + * from the {@link Ellipsoid} associated to the source or target CRS, if these parameters are + * not explicitly given and if they are relevant for the coordinate operation method.</li> + * <li>{@linkplain #createConcatenatedTransform Concatenating} the parameterized transform + * with any other transforms required for performing units changes and coordinates swapping.</li> + * </ol> + * + * The complete group of parameters, including {@code "semi_major"}, {@code "semi_minor"} or other + * calculated values, can be obtained by a call to {@link Context#getCompletedParameters()}. + * + * @param method the case insensitive name or identifier of the desired coordinate operation method. + * @return a builder for a meth transform implementing the formulas identified by the given method. + * @throws NoSuchIdentifierException if there is no supported method for the given name or identifier. + * + * @see #getAvailableMethods(Class) + * @since 1.5 + */ + @Override + public MathTransform.Builder builder(final String method) throws NoSuchIdentifierException { + return new Context(this, getOperationMethod(method)); + } + /** * Returns the default parameter values for a math transform using the given operation method. * The {@code method} argument is the name of any {@code OperationMethod} instance returned by @@ -500,36 +537,16 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math } /** - * Creates a transform from a group of parameters. - * The set of expected parameters varies for each operation. - * - * @param parameters the parameter values. The {@linkplain ParameterDescriptorGroup#getName() parameter group name} - * shall be the name of the desired {@linkplain DefaultOperationMethod operation method}. - * @return the transform created from the given parameters. - * @throws NoSuchIdentifierException if there is no method for the given parameter group name. - * @throws FactoryException if the object creation failed. This exception is thrown - * if some required parameter has not been supplied, or has illegal value. - * - * @deprecated Replaced by {@link #createParameterizedTransform(ParameterValueGroup, Context)} - * where the {@code Context} argument can be null. - */ - @Override - @Deprecated(since="0.7") - public MathTransform createParameterizedTransform(final ParameterValueGroup parameters) - throws NoSuchIdentifierException, FactoryException - { - return createParameterizedTransform(parameters, null); - } - - /** - * Source and target coordinate systems for which a new parameterized transform is going to be used. + * Builder of a parameterized math transform identified by a name or code. + * A builder can optionally contain the source and target coordinate systems + * for which a new parameterized transform is going to be used. * {@link DefaultMathTransformFactory} uses this information for: * * <ul> * <li>Completing some parameters if they were not provided. In particular, the {@linkplain #getSourceEllipsoid() * source ellipsoid} can be used for providing values for the {@code "semi_major"} and {@code "semi_minor"} * parameters in map projections.</li> - * <li>{@linkplain CoordinateSystems#swapAndScaleAxes Swapping and scaling axes} if the source or the target + * <li>{@linkplain #swapAndScaleAxes Swapping and scaling axes} if the source or the target * coordinate systems are not {@linkplain AxesConvention#NORMALIZED normalized}.</li> * </ul> * @@ -538,25 +555,19 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math * or anything else related to datum. Datum changes have dedicated {@link OperationMethod}, * for example <q>Longitude rotation</q> (EPSG:9601) for changing the prime meridian. * - * <h2>Scope</h2> - * Instances of this class should be short-lived - * (they exist only the time needed for creating a {@link MathTransform}) - * and should not be shared (because they provide no immutability guarantees). - * This class is not thread-safe. + * <p>Each instance is created by {@link #builder(String)} and should be used only once. + * This class is <em>not</em> thread-safe.</p> * * @author Martin Desruisseaux (Geomatys) * @version 1.5 * @since 0.7 + * + * @see #builder(String) */ - @SuppressWarnings("serial") // All field values are usually serializable instances. - public static class Context implements MathTransformProvider.Context, Serializable { - /** - * For cross-version compatibility. - */ - private static final long serialVersionUID = -239563539875674709L; - + public static class Context implements MathTransform.Builder, MathTransformProvider.Context { /** * The factory to use if the provider needs to create other math transforms as operation steps. + * @todo Replace by non-static inner class after deprecated methods have been removed. * * @see #getFactory() */ @@ -569,21 +580,23 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math /** * The ellipsoid of the source or target ellipsoidal coordinate system, or {@code null} if it does not apply. - * Valid only if {@link #sourceCS} or {@link #targetCS} is an instance of {@link EllipsoidalCS}. */ private Ellipsoid sourceEllipsoid, targetEllipsoid; /** * The provider that created the parameterized {@link MathTransform} instance, or {@code null} - * if this information does not apply. This field is used for transferring information between - * {@code createParameterizedTransform(…)} and {@code swapAndScaleAxes(…)}. + * if this information does not apply. This is initially set to the operation method specified + * in the call to {@link #builder(String)}, but may be modified by {@link #create()}. + * + * @see #getMethod() */ private OperationMethod provider; /** * The parameters actually used. + * @todo Make final after the deprecated methods have been removed. * - * @see #getCompletedParameters() + * @see #parameters() */ private ParameterValueGroup parameters; @@ -594,17 +607,47 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math */ private final Map<String,Boolean> contextualParameters; + /** + * Whether the user-specified parameters have been completed with the contextual parameters. + * This set to {@code true} the first time that {@link #getCompletedParameters()} is invoked. + * After this flag became {@code true}, this {@code Context} should not be modified anymore. + * + * @see #completeParameters() + */ + private boolean completedParameters; + + /** + * The warning that occurred during parameters completion, or {@code null} if none. + * This warning is not necessarily fatal, but will be appended to the suppressed exceptions + * of {@link FactoryException} if the {@link MathTransform} creation nevertheless fail. + */ + private RuntimeException warning; + /** * Creates a new context with all properties initialized to {@code null}. + * + * @deprecated Use {@link #builder(String)} instead. */ + @Deprecated(since="1.5", forRemoval=true) public Context() { contextualParameters = new HashMap<>(); } + /** + * Creates a new context with all properties initialized to {@code null}. + * + * @param method a method known to the enclosing factory. + */ + Context(final DefaultMathTransformFactory factory, final OperationMethod method) { + this.factory = factory; + provider = method; + parameters = method.getParameters().createValue(); + contextualParameters = new HashMap<>(); + } + /** * Returns the factory to use if the provider needs to create other math transforms as operation steps. - * This is the factory on which {@link #createParameterizedTransform(ParameterValueGroup, Context)} is - * invoked, or the {@linkplain #provider() default factory} otherwise. + * This is the factory on which {@link #builder(String)} has been invoked. * * @since 1.5 */ @@ -613,15 +656,39 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math return (factory != null) ? factory : provider(); } + /** + * Gives hints about axis lengths and their orientations in input coordinates. + * The {@code ellipsoid} argument is often provided together with an {@link EllipsoidalCS}, but not only. + * For example, a two-dimensional {@link SphericalCS} may also require information about the ellipsoid. + * + * <p>Each call to this method replaces the values of the previous call. + * However, this method cannot be invoked anymore after {@link #getCompletedParameters()} has been invoked.</p> + * + * @param cs the coordinate system defining source axis order and units, or {@code null} if none. + * @param ellipsoid the ellipsoid providing source semi-axis lengths, or {@code null} if none. + * @throws IllegalStateException if {@link #getCompletedParameters()} has already been invoked. + * + * @since 1.5 + */ + @Override + public void setSourceAxes(CoordinateSystem cs, Ellipsoid ellipsoid) { + if (completedParameters) { + throw new IllegalStateException(Errors.format(Errors.Keys.AlreadyInitialized_1, "completedParameters")); + } + sourceCS = cs; + sourceEllipsoid = ellipsoid; + } + /** * Sets the source coordinate system to the given value. * The source ellipsoid is unconditionally set to {@code null}. * * @param cs the coordinate system to set as the source (can be {@code null}). + * @deprecated Replaced by {@link #setSourceAxes(CoordinateSystem, Ellipsoid)} for simplifying the API. */ + @Deprecated(since="1.5", forRemoval=true) public void setSource(final CoordinateSystem cs) { - sourceCS = cs; - sourceEllipsoid = null; + setSourceAxes(cs, null); } /** @@ -636,15 +703,38 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math * @param crs the coordinate system and ellipsoid to set as the source, or {@code null}. * * @since 1.3 + * @deprecated Replaced by {@link #setSourceAxes(CoordinateSystem, Ellipsoid)} for simplifying the API. */ + @Deprecated(since="1.5", forRemoval=true) public void setSource(final GeodeticCRS crs) { if (crs != null) { - sourceCS = crs.getCoordinateSystem(); - sourceEllipsoid = crs.getDatum().getEllipsoid(); + setSourceAxes(crs.getCoordinateSystem(), crs.getDatum().getEllipsoid()); } else { - sourceCS = null; - sourceEllipsoid = null; + setSourceAxes(null, null); + } + } + + /** + * Gives hints about axis lengths and their orientations in output coordinates. + * The {@code ellipsoid} argument is often provided together with an {@link EllipsoidalCS}, but not only. + * For example, a two-dimensional {@link SphericalCS} may also require information about the ellipsoid. + * + * <p>Each call to this method replaces the values of the previous call. + * However, this method cannot be invoked anymore after {@link #getCompletedParameters()} has been invoked.</p> + * + * @param cs the coordinate system defining target axis order and units, or {@code null} if none. + * @param ellipsoid the ellipsoid providing target semi-axis lengths, or {@code null} if none. + * @throws IllegalStateException if {@link #getCompletedParameters()} has already been invoked. + * + * @since 1.5 + */ + @Override + public void setTargetAxes(CoordinateSystem cs, Ellipsoid ellipsoid) { + if (completedParameters) { + throw new IllegalStateException(Errors.format(Errors.Keys.AlreadyInitialized_1, "completedParameters")); } + targetCS = cs; + targetEllipsoid = ellipsoid; } /** @@ -652,10 +742,11 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math * The target ellipsoid is unconditionally set to {@code null}. * * @param cs the coordinate system to set as the target (can be {@code null}). + * @deprecated Replaced by {@link #setTargetAxes(CoordinateSystem, Ellipsoid)} for simplifying the API. */ + @Deprecated(since="1.5", forRemoval=true) public void setTarget(final CoordinateSystem cs) { - targetCS = cs; - targetEllipsoid = null; + setTargetAxes(cs, null); } /** @@ -670,14 +761,14 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math * @param crs the coordinate system and ellipsoid to set as the target, or {@code null}. * * @since 1.3 + * @deprecated Replaced by {@link #setTargetAxes(CoordinateSystem, Ellipsoid)} for simplifying the API. */ + @Deprecated(since="1.5", forRemoval=true) public void setTarget(final GeodeticCRS crs) { if (crs != null) { - targetCS = crs.getCoordinateSystem(); - targetEllipsoid = crs.getDatum().getEllipsoid(); + setTargetAxes(crs.getCoordinateSystem(), crs.getDatum().getEllipsoid()); } else { - targetCS = null; - targetEllipsoid = null; + setTargetAxes(null, null); } } @@ -691,10 +782,9 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math } /** - * Returns the ellipsoid of the source ellipsoidal coordinate system, or {@code null} if it does not apply. - * This information is valid only if {@link #getSourceCS()} returns an instance of {@link EllipsoidalCS}. + * Returns the ellipsoid used together with the source coordinate system, or {@code null} if none. * - * @return the ellipsoid of the source ellipsoidal coordinate system, or {@code null} if it does not apply. + * @return the ellipsoid used together with the source coordinate system, or {@code null} if none. */ public Ellipsoid getSourceEllipsoid() { return sourceEllipsoid; @@ -721,10 +811,9 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math } /** - * Returns the ellipsoid of the target ellipsoidal coordinate system, or {@code null} if it does not apply. - * This information is valid only if {@link #getTargetCS()} returns an instance of {@link EllipsoidalCS}. + * Returns the ellipsoid used together with the target coordinate system, or {@code null} if none. * - * @return the ellipsoid of the target ellipsoidal coordinate system, or {@code null} if it does not apply. + * @return the ellipsoid used together with the target coordinate system, or {@code null} if none. */ public Ellipsoid getTargetEllipsoid() { return targetEllipsoid; @@ -801,6 +890,21 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math } } + /** + * Returns the operation method used for creating the math transform from the parameter values. + * This is initially the operation method specified in the call to {@link #builder(String)}, + * but may change after the call to {@link #create()} if the method has been adjusted because + * of the parameter values. + * + * @return the operation method used for creating the math transform from the parameter values. + * + * @since 1.5 + */ + @Override + public Optional<OperationMethod> getMethod() { + return Optional.ofNullable(provider); + } + /** * Returns the operation method used for the math transform creation. * This is the same information as {@link #getLastMethodUsed()} but more stable @@ -810,10 +914,11 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math * @throws IllegalStateException if {@link #createParameterizedTransform(ParameterValueGroup, Context)} * has not yet been invoked. * - * @see #getLastMethodUsed() + * @deprecated Replaced by {@link #getMethod()}. * * @since 1.3 */ + @Deprecated(since="1.5", forRemoval=true) public OperationMethod getMethodUsed() { if (provider != null) { return provider; @@ -821,6 +926,24 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math throw new IllegalStateException(Resources.format(Resources.Keys.UnspecifiedParameterValues)); } + /** + * Returns the parameter values to modify for defining the transform to create. + * Those parameters are initialized to default values, which are {@linkplain #getMethod() method} depend. + * User-supplied values should be set directly in the returned instance with codes like + * <code>parameter(</code><var>name</var><code>).setValue(</code><var>value</var><code>)</code>. + * + * @return the parameter values to modify for defining the transform to create. + * + * @since 1.5 + */ + @Override + public ParameterValueGroup parameters() { + if (parameters == null) { + parameters = provider.getParameters().createValue(); + } + return parameters; + } + /** * Returns the names of parameters that have been inferred from the context. * The set of keys can contain any of {@code "dim"}, @@ -831,15 +954,15 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math * The parameters named in that set are included in the parameters * returned by {@link #getCompletedParameters()}. * - * <h4>Associated boolean values</h4> - * The associated boolean in the map tells whether the named parameter value is really contextual. - * The boolean is {@code FALSE} if the user explicitly specified a value in the parameters given to + * <h4>Associated Boolean values</h4> + * The associated Boolean in the map tells whether the named parameter value is really contextual. + * The Boolean is {@code FALSE} if the user explicitly specified a value in the parameters given to * the {@link #createParameterizedTransform(ParameterValueGroup, Context)} method, * and that value is different than the value inferred from the context. * Such inconsistencies are also logged at {@link Level#WARNING}. * In all other cases * (no value specified by the user, or a value was specified but is consistent with the context), - * the associated boolean in the map is {@code TRUE}. + * the associated Boolean in the map is {@code TRUE}. * * <h4>Mutability</h4> * The returned map is modifiable for making easier for callers to amend the contextual information. @@ -858,15 +981,26 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math /** * Returns the parameter values used for the math transform creation, * including the parameters completed by the factory. - * The parameters inferred from the context are listed by {@link #getContextualParameters()}. + * This is the union of {@link #parameters()} with {@link #getContextualParameters()}. + * The completed parameters may only have additional parameters compared to the user-supplied parameters. + * {@linkplain #parameters() Parameter} values that were explicitly set by the user are not overwritten. + * + * <p>After this method has been invoked, the {@link #setSourceAxes setSourceAxes(…)} + * and {@link #setTargetAxes setTargetAxes(…)} methods can no longer be invoked.</p> * * @return the parameter values used by the factory. - * @throws IllegalStateException if {@link #createParameterizedTransform(ParameterValueGroup, Context)} - * has not yet been invoked. */ @Override public ParameterValueGroup getCompletedParameters() { if (parameters != null) { + /* + * If the user's parameters do not contain semi-major and semi-minor axis lengths, infer + * them from the ellipsoid. We have to do that because those parameters are often omitted, + * since the standard place where to provide this information is in the ellipsoid object. + */ + if (!completedParameters) { + warning = completeParameters(); + } return parameters; } throw new IllegalStateException(Resources.format(Resources.Keys.UnspecifiedParameterValues)); @@ -885,6 +1019,8 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math * @param writable {@code true} if this method should also check that the parameters group is editable. * @throws IllegalArgumentException if the copy cannot be performed because a parameter has * a unrecognized name or an illegal value. + * + * @todo We should be able to remove this method after we removed the other deprecated methods. */ private void ensureCompatibleParameters(final boolean writable) throws IllegalArgumentException { final ParameterDescriptorGroup expected = provider.getParameters(); @@ -1053,41 +1189,16 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math /** * Completes the parameter group with information about source and target ellipsoid axis lengths, * if available. This method writes semi-major and semi-minor parameter values only if they do not - * already exists in the given parameters. + * already exists in the current parameters. * - * <p>The given method and parameters are stored in the {@link #provider} and {@link #parameters} - * fields respectively. The actual stored values may differ from the values given to this method. - * The {@link #factory} field must be set before this method is invoked.</p> - * - * @param method description of the transform to be created, or {@code null} if unknown. * @return the exception if the operation failed, or {@code null} if none. This exception is not thrown now * because the caller may succeed in creating the transform anyway, or otherwise may produce a more * informative exception. - * @throws IllegalArgumentException if the operation fails because a parameter has a unrecognized name or an - * illegal value. * * @see #getCompletedParameters() */ - final RuntimeException completeParameters(OperationMethod method, final ParameterValueGroup userParams) - throws FactoryException, IllegalArgumentException - { - /* - * The "Geographic/geocentric conversions" conversion (EPSG:9602) can be either: - * - * - "Ellipsoid_To_Geocentric" - * - "Geocentric_To_Ellipsoid" - * - * EPSG defines both by a single operation, but Apache SIS needs to distinguish them. - */ - if (method instanceof AbstractProvider) { - final String alt = ((AbstractProvider) method).resolveAmbiguity(this); - if (alt != null) { - method = factory.getOperationMethod(alt); - } - } - provider = method; - parameters = userParams; - ensureCompatibleParameters(false); // Invoke only after we set `provider` to its final instance. + private RuntimeException completeParameters() throws IllegalArgumentException { + completedParameters = true; // Need to be first. /* * Get a mask telling us if we need to set parameters for the source and/or target ellipsoid. * This information should preferably be given by the provider. But if the given provider is @@ -1128,10 +1239,65 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math return failure; } + /** + * Creates the parameterized transform. The operation method is given by {@link #getMethod()} + * and the parameter values should have been set on the group returned by {@link #parameters()} + * before to invoke this constructor. + * + * @return the parameterized transform. + * @throws FactoryException if the transform creation failed. + * This exception is thrown if some required parameters have not been supplied, or have illegal values. + * + * @since 1.5 + */ + @Override + public MathTransform create() throws FactoryException { + try { + if (provider instanceof AbstractProvider) { + /* + * The "Geographic/geocentric conversions" conversion (EPSG:9602) can be either: + * + * - "Ellipsoid_To_Geocentric" + * - "Geocentric_To_Ellipsoid" + * + * EPSG defines both by a single operation, but Apache SIS needs to distinguish them. + */ + final String method = ((AbstractProvider) provider).resolveAmbiguity(this); + if (method != null) { + provider = factory.getOperationMethod(method); + } + } + final MathTransform transform; + if (provider instanceof MathTransformProvider) try { + transform = ((MathTransformProvider) provider).createMathTransform(this); + } catch (IllegalArgumentException | IllegalStateException exception) { + throw new InvalidGeodeticParameterException(exception.getLocalizedMessage(), exception); + } else { + throw new UnimplementedServiceException(Errors.format( + Errors.Keys.UnsupportedImplementation_1, Classes.getClass(provider))); + } + /* + * Make sure that the number of dimensions is compatible with the OperationMethod instance. + * Then make final adjustment for axis directions and units of measurement. + */ + if (provider instanceof AbstractProvider) { + provider = ((AbstractProvider) provider).variantFor(transform); + } + return factory.unique(factory.swapAndScaleAxes(transform, this)); + } catch (FactoryException exception) { + if (warning != null) { + exception.addSuppressed(warning); + } + throw exception; + } finally { + factory.lastMethod.set(provider); + } + } + /** * Returns a string representation of this context for debugging purposes. - * Current implementation write the name of source/target coordinate systems and ellipsoids. - * If {@linkplain #getContextualParameters() contextual parameters} have already been inferred, + * The current implementation writes the name of source/target coordinate systems and ellipsoids. + * If the {@linkplain #getContextualParameters() contextual parameters} have already been inferred, * then their names are appended with inconsistent parameters (if any) written on a separated line. * * @return a string representation of this context. @@ -1186,32 +1352,11 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math * } * * Sometimes the {@code "semi_major"} and {@code "semi_minor"} parameter values are not explicitly provided, - * but rather inferred from the {@linkplain org.apache.sis.referencing.datum.DefaultGeodeticDatum geodetic - * datum} of the source Coordinate Reference System. If the given {@code context} argument is non-null, - * then this method will use those contextual information for: - * - * <ol> - * <li>Inferring the {@code "semi_major"}, {@code "semi_minor"}, {@code "src_semi_major"}, - * {@code "src_semi_minor"}, {@code "tgt_semi_major"} or {@code "tgt_semi_minor"} parameter values - * from the {@linkplain org.apache.sis.referencing.datum.DefaultEllipsoid ellipsoids} associated to - * the source or target CRS, if those parameters are not explicitly given and if they are relevant - * for the coordinate operation method.</li> - * <li>{@linkplain #createConcatenatedTransform Concatenating} the parameterized transform - * with any other transforms required for performing units changes and coordinates swapping.</li> - * </ol> - * - * The complete group of parameters, including {@code "semi_major"}, {@code "semi_minor"} or other calculated values, - * can be obtained by a call to {@link Context#getCompletedParameters()} after {@code createParameterizedTransform(…)} - * returned. Note that the completed parameters may only have additional parameters compared to the given parameter - * group; existing parameter values should not be modified. - * - * <p>The {@code OperationMethod} instance used by this constructor can be obtained by a call to - * {@link #getLastMethodUsed()}.</p> + * but rather inferred from the {@linkplain org.opengis.referencing.datum.GeodeticDatum geodetic + * reference frame} of the source Coordinate Reference System. * * @param parameters the parameter values. The {@linkplain ParameterDescriptorGroup#getName() parameter group name} - * shall be the name of the desired {@linkplain DefaultOperationMethod operation method}. - * @param context information about the context (for example source and target coordinate systems) - * in which the new transform is going to be used, or {@code null} if none. + * shall be the name of the desired {@linkplain DefaultOperationMethod operation method}. * @return the transform created from the given parameters. * @throws NoSuchIdentifierException if there is no method for the given parameter group name. * @throws FactoryException if the object creation failed. This exception is thrown @@ -1222,8 +1367,9 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math * @see #getLastMethodUsed() * @see org.apache.sis.parameter.ParameterBuilder#createGroupForMapProjection(ParameterDescriptor...) */ - public MathTransform createParameterizedTransform(final ParameterValueGroup parameters, - final Context context) throws NoSuchIdentifierException, FactoryException + @Override + public MathTransform createParameterizedTransform(final ParameterValueGroup parameters) + throws NoSuchIdentifierException, FactoryException { OperationMethod method = null; RuntimeException failure = null; @@ -1247,50 +1393,30 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math } catch (NoSuchIdentifierException exception) { if (methodIdentifier.equals(methodName)) { throw exception; + } else try { + method = getOperationMethod(methodName); + } catch (NoSuchIdentifierException e) { + e.addSuppressed(exception); + throw e; } - method = getOperationMethod(methodName); Logging.recoverableException(AbstractMathTransform.LOGGER, DefaultMathTransformFactory.class, "createParameterizedTransform", exception); } - if (!(method instanceof MathTransformProvider)) { - throw new NoSuchIdentifierException(Errors.format( // For now, handle like an unknown operation. - Errors.Keys.UnsupportedImplementation_1, Classes.getClass(method)), methodName); - } /* * Will catch only exceptions that may be the result of improper parameter usage (e.g. a value out * of range). Do not catch exceptions caused by programming errors (e.g. null pointer exception). */ - try { - /* - * If the user's parameters do not contain semi-major and semi-minor axis lengths, infer - * them from the ellipsoid. We have to do that because those parameters are often omitted, - * since the standard place where to provide this information is in the ellipsoid object. - */ - if (context != null) { - context.factory = this; - failure = context.completeParameters(method, parameters); - method = context.provider; - transform = ((MathTransformProvider) method).createMathTransform(context); - } else { - transform = ((MathTransformProvider) method).createMathTransform(this, parameters); - } + if (method instanceof MathTransformProvider) try { + transform = ((MathTransformProvider) method).createMathTransform(this, parameters); } catch (IllegalArgumentException | IllegalStateException exception) { throw new InvalidGeodeticParameterException(exception.getLocalizedMessage(), exception); + } else { + throw new UnimplementedServiceException(Errors.format( + Errors.Keys.UnsupportedImplementation_1, Classes.getClass(method))); } - /* - * Cache the transform that we just created and make sure that the number of dimensions - * is compatible with the OperationMethod instance. Then make final adjustment for axis - * directions and units of measurement. - */ transform = unique(transform); if (method instanceof AbstractProvider) { method = ((AbstractProvider) method).variantFor(transform); - if (context != null) { - context.provider = method; - } - } - if (context != null) { - transform = swapAndScaleAxes(transform, context); } } catch (FactoryException e) { if (failure != null) { @@ -1303,6 +1429,52 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math return transform; } + /** + * Creates a transform from a group of parameters and a context. + * + * @param parameters the parameter values. + * @param context information about the context, or {@code null} if none. + * @return the transform created from the given parameters. + * @throws NoSuchIdentifierException if there is no method for the given parameter group name. + * @throws FactoryException if the object creation failed. + * @deprecated Replaced by a builder pattern with {@link #builder(String)}. + */ + @Deprecated(since="1.5", forRemoval=true) + public MathTransform createParameterizedTransform(final ParameterValueGroup parameters, + final Context context) throws NoSuchIdentifierException, FactoryException + { + // TODO: After the deprecated methods have been removed + // - Make the `Context.parameters` field final (initialized in constructor). + // - Delete `Context.ensureCompatibleParameters(boolean)`. + // - Remove the null check on `Context.getCompletedParameters()` method body. + if (context == null) { + return createParameterizedTransform(parameters); + } + final ParameterDescriptorGroup descriptor = parameters.getDescriptor(); + final String methodName = descriptor.getName().getCode(); + String methodIdentifier = IdentifiedObjects.toString(IdentifiedObjects.getIdentifier(descriptor, Citations.EPSG)); + if (methodIdentifier == null) { + methodIdentifier = methodName; + } + OperationMethod method; + try { + method = getOperationMethod(methodIdentifier); + } catch (NoSuchIdentifierException exception) { + if (methodIdentifier.equals(methodName)) { + throw exception; + } + method = getOperationMethod(methodName); + Logging.recoverableException(AbstractMathTransform.LOGGER, + DefaultMathTransformFactory.class, "createParameterizedTransform", exception); + } + context.factory = this; + context.provider = method; + context.parameters = parameters; + context.completedParameters = false; + context.ensureCompatibleParameters(false); + return context.create(); + } + /** * Given a transform between normalized spaces, * creates a transform taking in account axis directions, units of measurement and longitude rotation. @@ -1509,9 +1681,9 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math ArgumentChecks.ensureNonNull("baseCRS", baseCRS); ArgumentChecks.ensureNonNull("parameters", parameters); ArgumentChecks.ensureNonNull("derivedCS", derivedCS); - final Context context = ReferencingUtilities.createTransformContext(baseCRS, null); - context.setTarget(derivedCS); - return createParameterizedTransform(parameters, context); + var builder = ReferencingUtilities.builder(this, parameters, baseCRS, null); + builder.setTargetAxes(derivedCS, null); + return builder.create(); } /** @@ -1551,22 +1723,17 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math * EllipsoidalCS and SphericalCS or other coordinate systems. */ if ((isEllipsoidalSource ? target : source) instanceof CartesianCS) { - final Context context = new Context(); - final EllipsoidalCS cs; - final String operation; + final var context = new Context(this, getOperationMethod( + isEllipsoidalSource ? GeographicToGeocentric.NAME + : GeocentricToGeographic.NAME)); if (isEllipsoidalSource) { - operation = GeographicToGeocentric.NAME; - context.setSource(cs = (EllipsoidalCS) source); - context.setTarget(target); - context.sourceEllipsoid = ellipsoid; + context.setSourceAxes(source, ellipsoid); + context.setTargetAxes(target, null); } else { - operation = GeocentricToGeographic.NAME; - context.setSource(source); - context.setTarget(cs = (EllipsoidalCS) target); - context.targetEllipsoid = ellipsoid; + context.setSourceAxes(source, null); + context.setTargetAxes(target, ellipsoid); } - final ParameterValueGroup pg = getDefaultParameters(operation); - return createParameterizedTransform(pg, context); + return context.create(); } } } @@ -1764,8 +1931,11 @@ public class DefaultMathTransformFactory extends AbstractFactory implements Math * * @see #createParameterizedTransform(ParameterValueGroup, Context) * @see Context#getMethodUsed() + * + * @deprecated Replaced by {@link MathTransform.Builder#getMethod()}. */ @Override + @Deprecated(since = "1.5") public OperationMethod getLastMethodUsed() { return lastMethod.get(); } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java index 3179cdfb67..d7dc4b2150 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/ReferencingUtilities.java @@ -54,7 +54,8 @@ import org.apache.sis.referencing.cs.AxesConvention; import org.apache.sis.referencing.cs.DefaultEllipsoidalCS; import org.apache.sis.referencing.internal.VerticalDatumTypes; import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory; -import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory.Context; +import org.apache.sis.referencing.factory.InvalidGeodeticParameterException; +import org.apache.sis.parameter.Parameters; /** @@ -511,58 +512,35 @@ public final class ReferencingUtilities extends Static { } /** - * Creates a context with source and target ellipsoids and coordinate systems inferred from the given CRS. + * Creates a builder with source and target ellipsoids and coordinate systems inferred from the given CRSs. * The ellipsoids will be non-null only if the given CRS is geodetic (geographic or geocentric). * - * @param sourceCRS the CRS from which to get the source coordinate system and ellipsoid, or {@code null}. - * @param targetCRS the CRS from which to get the target coordinate system and ellipsoid, or {@code null}. + * @param factory the factory on which to create the builder. + * @param parameters the operation parameter value group. + * @param sourceCRS the CRS from which to get the source coordinate system and ellipsoid, or {@code null}. + * @param targetCRS the CRS from which to get the target coordinate system and ellipsoid, or {@code null}. * @return the context to provides to math transform factory. + * @throws FactoryException if the builder cannot be created. */ - public static Context createTransformContext(final CoordinateReferenceSystem sourceCRS, - final CoordinateReferenceSystem targetCRS) + public static MathTransform.Builder builder( + final MathTransformFactory factory, + final ParameterValueGroup parameters, + final CoordinateReferenceSystem sourceCRS, + final CoordinateReferenceSystem targetCRS) throws FactoryException { - final Context context = new Context(); - if (sourceCRS instanceof GeodeticCRS) { - context.setSource((GeodeticCRS) sourceCRS); - } else if (sourceCRS != null) { - context.setSource(sourceCRS.getCoordinateSystem()); + final var builder = factory.builder(parameters.getDescriptor().getName().getCode()); + try { + Parameters.copy(parameters, builder.parameters()); + } catch (IllegalArgumentException e) { + throw new InvalidGeodeticParameterException(e.getMessage(), e); } - if (targetCRS instanceof GeodeticCRS) { - context.setTarget((GeodeticCRS) targetCRS); - } else if (targetCRS != null) { - context.setTarget(targetCRS.getCoordinateSystem()); + if (sourceCRS != null) { + builder.setSourceAxes(sourceCRS.getCoordinateSystem(), getEllipsoid(sourceCRS)); } - return context; - } - - /** - * Substitute for the deprecated {@link MathTransformFactory#createBaseToDerived createBaseToDerived(…)} method. - * This substitute uses the full {@code targetCRS} instead of only the coordinate system of the target. - * This is needed for setting the {@code "tgt_semi_minor"} and {@code "tgt_semi_major"} parameters of - * Molodensky transformation for example. - * - * @param factory the factory to use for creating the transform. - * @param sourceCRS the source (base) coordinate reference system. - * @param parameters the parameter values for the transform. - * @param targetCRS the target (derived) coordinate system. - * @return the parameterized transform from {@code sourceCRS} to {@code targetCRS}, - * including unit conversions and axis swapping. - * @throws FactoryException if the object creation failed. This exception is thrown - * if some required parameter has not been supplied, or has illegal value. - * - * @see <a href="https://issues.apache.org/jira/browse/SIS-512">SIS-512 on issues tracker</a> - */ - public static MathTransform createBaseToDerived(final MathTransformFactory factory, - final CoordinateReferenceSystem sourceCRS, final ParameterValueGroup parameters, - final CoordinateReferenceSystem targetCRS) throws FactoryException - { - if (factory instanceof DefaultMathTransformFactory) { - return ((DefaultMathTransformFactory) factory).createParameterizedTransform( - parameters, createTransformContext(sourceCRS, targetCRS)); - } else { - // Fallback for non-SIS implementations. Work for map projections but not for Molodensky. - return factory.createBaseToDerived(sourceCRS, parameters, targetCRS.getCoordinateSystem()); + if (targetCRS != null) { + builder.setTargetAxes(targetCRS.getCoordinateSystem(), getEllipsoid(targetCRS)); } + return builder; } /** diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeographicOffsetsTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeographicOffsetsTest.java index 94b9949288..755c275c6f 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeographicOffsetsTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/GeographicOffsetsTest.java @@ -124,19 +124,18 @@ public final class GeographicOffsetsTest extends TransformTestCase { @Test public void testCreateWithContext() throws FactoryException, TransformException { final DefaultMathTransformFactory factory = DefaultMathTransformFactory.provider(); - final ParameterValueGroup pv = factory.getDefaultParameters("Vertical Offset"); - pv.parameter("Vertical Offset").setValue(15.55, Units.FOOT); /* * Now create the MathTransform. But at the difference of the above testVerticalOffset() method, * we supply information about axis directions. The operation parameter shall have the same sign - * than in the EPSG database (which is positive), and the source and target coordinates shall have + * as in the EPSG database (which is positive), and the source and target coordinates shall have * the same sign as in the EPSG example (positive too). However, we do not test unit conversion * in this method (EPSG sample point uses feet units), only axis direction. */ - final DefaultMathTransformFactory.Context context = new DefaultMathTransformFactory.Context(); - context.setSource(HardCodedCS.GRAVITY_RELATED_HEIGHT); // Direction up, in metres. - context.setTarget(HardCodedCS.DEPTH); // Direction down, in metres. - transform = factory.createParameterizedTransform(pv, context); + final var builder = factory.builder("Vertical Offset"); + builder.parameters().parameter("Vertical Offset").setValue(15.55, Units.FOOT); + builder.setSourceAxes(HardCodedCS.GRAVITY_RELATED_HEIGHT, null); // Direction up, in metres. + builder.setTargetAxes(HardCodedCS.DEPTH, null); // Direction down, in metres. + transform = builder.create(); tolerance = Formulas.LINEAR_TOLERANCE; final double[] source = new double[transform.getSourceDimensions()]; final double[] target = new double[transform.getTargetDimensions()]; diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java index 5a29446915..b75449db34 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactoryTest.java @@ -310,9 +310,9 @@ public final class DefaultMathTransformFactoryTest extends TestCase { @Test public void testSwapAndScaleAxes() throws FactoryException { final DefaultMathTransformFactory factory = factory(); - final DefaultMathTransformFactory.Context context = new DefaultMathTransformFactory.Context(); - context.setSource(HardCodedCS.GEODETIC_3D); - context.setTarget(HardCodedCS.CARTESIAN_3D); + final var context = new DefaultMathTransformFactory.Context(); + context.setSourceAxes(HardCodedCS.GEODETIC_3D, null); + context.setTargetAxes(HardCodedCS.CARTESIAN_3D, null); /* * Simulate a case where the parameterized transform is a two-dimensional map projection, * but the input and output CRS are three-dimensional geographic and projected CRS respectively. @@ -324,8 +324,8 @@ public final class DefaultMathTransformFactoryTest extends TestCase { /* * Transform from 3D to 2D. Height dimension is dropped. */ - context.setSource(HardCodedCS.GEODETIC_3D); - context.setTarget(HardCodedCS.GEODETIC_2D); + context.setSourceAxes(HardCodedCS.GEODETIC_3D, null); + context.setTargetAxes(HardCodedCS.GEODETIC_2D, null); mt = factory.swapAndScaleAxes(MathTransforms.identity(2), context); var expected = Matrices.create(3, 4, new double[] { 1, 0, 0, 0, @@ -337,8 +337,8 @@ public final class DefaultMathTransformFactoryTest extends TestCase { * Transform from 2D to 3D. Coordinate values in the height dimension are unknown (NaN). * This case happen when the third dimension is handled as a "pass through" dimension. */ - context.setSource(HardCodedCS.GEODETIC_2D); - context.setTarget(HardCodedCS.GEODETIC_3D); + context.setSourceAxes(HardCodedCS.GEODETIC_2D, null); + context.setTargetAxes(HardCodedCS.GEODETIC_3D, null); mt = factory.swapAndScaleAxes(MathTransforms.identity(2), context); expected = Matrices.create(4, 3, new double[] { 1, 0, 0, @@ -362,8 +362,8 @@ public final class DefaultMathTransformFactoryTest extends TestCase { /* * Test error message when adding a dimension that is not ellipsoidal height. */ - context.setSource(HardCodedCS.CARTESIAN_2D); - context.setTarget(HardCodedCS.CARTESIAN_3D); + context.setSourceAxes(HardCodedCS.CARTESIAN_2D, null); + context.setTargetAxes(HardCodedCS.CARTESIAN_3D, null); var e = assertThrows(InvalidGeodeticParameterException.class, () -> factory.swapAndScaleAxes(MathTransforms.identity(2), context), "Should not have accepted the given coordinate systems."); diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java index b8c252b7d0..84412badde 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryBase.java @@ -65,6 +65,7 @@ class MathTransformFactoryBase implements MathTransformFactory { /** Default implementation unconditionally returns {@code null}. */ @Override + @Deprecated public OperationMethod getLastMethodUsed() { return null; } @@ -77,10 +78,17 @@ class MathTransformFactoryBase implements MathTransformFactory { /** Default implementation throws an exception. */ @Override + @Deprecated public MathTransform createBaseToDerived(CoordinateReferenceSystem baseCRS, ParameterValueGroup parameters, CoordinateSystem derivedCS) throws FactoryException { throw new FactoryException(MESSAGE); } + /** Default implementation throws an exception. */ + @Override + public MathTransform.Builder builder(String code) throws NoSuchIdentifierException { + throw new NoSuchIdentifierException(MESSAGE, code); + } + /** Default implementation throws an exception. */ @Override public MathTransform createParameterizedTransform(ParameterValueGroup parameters) throws FactoryException { diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java index 762fce44e2..f368841e2d 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/MathTransformFactoryMock.java @@ -102,6 +102,7 @@ public final class MathTransformFactoryMock implements MathTransformFactory { * @return the method given at construction time. */ @Override + @Deprecated public OperationMethod getLastMethodUsed() { return method; } @@ -192,12 +193,24 @@ public final class MathTransformFactoryMock implements MathTransformFactory { * @return never returned. */ @Override + @Deprecated public MathTransform createBaseToDerived(CoordinateReferenceSystem baseCRS, ParameterValueGroup parameters, CoordinateSystem derivedCS) { throw new UnsupportedOperationException(); } + /** + * Unimplemented method. + * + * @param code ignored. + * @return never returned. + */ + @Override + public MathTransform.Builder builder(String code) { + throw new UnsupportedOperationException(); + } + /** * Unimplemented method. * diff --git a/geoapi/snapshot b/geoapi/snapshot index a0ae30dd67..e57e817bb1 160000 --- a/geoapi/snapshot +++ b/geoapi/snapshot @@ -1 +1 @@ -Subproject commit a0ae30dd67b67a718b74bd8a90140ea0a559e8b6 +Subproject commit e57e817bb1965d241d4f16f498499d6bb304fddd