| /* |
| * 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 java.util.Map; |
| import java.util.Set; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Collection; |
| import java.util.logging.Logger; |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import jakarta.xml.bind.Unmarshaller; |
| import jakarta.xml.bind.annotation.XmlType; |
| import jakarta.xml.bind.annotation.XmlSeeAlso; |
| import jakarta.xml.bind.annotation.XmlElement; |
| import jakarta.xml.bind.annotation.XmlRootElement; |
| import javax.measure.IncommensurableException; |
| import org.opengis.util.InternationalString; |
| import org.opengis.metadata.Identifier; |
| import org.opengis.metadata.quality.PositionalAccuracy; |
| import org.opengis.referencing.IdentifiedObject; |
| import org.opengis.referencing.crs.GeographicCRS; |
| import org.opengis.referencing.crs.CoordinateReferenceSystem; |
| import org.opengis.referencing.operation.ConcatenatedOperation; |
| import org.opengis.referencing.operation.CoordinateOperation; |
| import org.opengis.referencing.operation.OperationMethod; |
| import org.opengis.referencing.operation.MathTransform; |
| import org.opengis.referencing.operation.PassThroughOperation; |
| import org.opengis.referencing.operation.TransformException; |
| import org.opengis.parameter.GeneralParameterValue; |
| import org.opengis.parameter.ParameterDescriptorGroup; |
| import org.opengis.parameter.ParameterValueGroup; |
| import org.apache.sis.io.wkt.Formatter; |
| import org.apache.sis.io.wkt.FormattableObject; |
| import org.apache.sis.io.wkt.Convention; |
| import org.apache.sis.util.Classes; |
| import org.apache.sis.util.ComparisonMode; |
| import org.apache.sis.util.UnsupportedImplementationException; |
| import org.apache.sis.util.collection.Containers; |
| import org.apache.sis.util.logging.Logging; |
| import org.apache.sis.util.resources.Errors; |
| import org.apache.sis.parameter.Parameterized; |
| import org.apache.sis.metadata.iso.citation.Citations; |
| import org.apache.sis.referencing.AbstractIdentifiedObject; |
| import org.apache.sis.referencing.cs.CoordinateSystems; |
| import org.apache.sis.referencing.operation.transform.MathTransforms; |
| import org.apache.sis.referencing.operation.transform.PassThroughTransform; |
| import org.apache.sis.referencing.privy.PositionalAccuracyConstant; |
| import org.apache.sis.referencing.privy.CoordinateOperations; |
| import org.apache.sis.referencing.privy.ReferencingUtilities; |
| import org.apache.sis.referencing.privy.WKTUtilities; |
| import org.apache.sis.referencing.privy.WKTKeywords; |
| import org.apache.sis.referencing.internal.Resources; |
| import org.apache.sis.metadata.privy.ImplementationHelper; |
| import org.apache.sis.util.privy.Constants; |
| import org.apache.sis.util.privy.CollectionsExt; |
| import org.apache.sis.util.privy.UnmodifiableArrayList; |
| import org.apache.sis.system.Semaphores; |
| import org.apache.sis.system.Loggers; |
| import static org.apache.sis.util.Utilities.deepEquals; |
| |
| // Specific to the geoapi-3.1 and geoapi-4.0 branches: |
| import org.opengis.referencing.crs.DerivedCRS; |
| import org.opengis.coordinate.CoordinateSet; |
| |
| |
| /** |
| * Describes the operation for transforming coordinates in the source CRS to coordinates in the target CRS. |
| * Coordinate operations contain a {@linkplain org.apache.sis.referencing.operation.transform.AbstractMathTransform |
| * math transform}, which does the actual work of transforming coordinates, together with the following information: |
| * |
| * <ul> |
| * <li>The {@linkplain #getSourceCRS() source} and {@linkplain #getTargetCRS() target CRS}.</li> |
| * <li>The {@linkplain #getInterpolationCRS() interpolation CRS} if a CRS other than source and target is needed |
| * for interpolating.</li> |
| * <li>In {@linkplain DefaultConversion conversion} and {@linkplain DefaultTransformation transformation} subclasses, |
| * a description of the {@linkplain DefaultOperationMethod operation method} together with the parameter values.</li> |
| * <li>The {@linkplain org.apache.sis.referencing.DefaultObjectDomain#getDomainOfValidity() domain of validity}.</li> |
| * <li>An estimation of the {@linkplain #getCoordinateOperationAccuracy() operation accuracy}.</li> |
| * </ul> |
| * |
| * <h2>Instantiation</h2> |
| * This class is conceptually <i>abstract</i>, even if it is technically possible to instantiate it. |
| * Typical applications should create instances of the most specific subclass prefixed by {@code Default} instead. |
| * An exception to this rule may occur when it is not possible to identify the exact operation type. |
| * |
| * <h2>Immutability and thread safety</h2> |
| * This base class is immutable and thus thread-safe if the property <em>values</em> (not necessarily the map itself) |
| * given to the constructor are also immutable. Most SIS subclasses and related classes are immutable under similar |
| * conditions. This means that unless otherwise noted in the javadoc, {@code CoordinateOperation} instances created |
| * using only SIS factories and static constants can be shared by many objects and passed between threads without |
| * synchronization. |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| * @version 1.5 |
| * @since 0.6 |
| */ |
| @XmlType(name = "AbstractCoordinateOperationType", propOrder = { |
| "operationVersion", |
| "accuracy", |
| "source", |
| "target" |
| }) |
| @XmlRootElement(name = "AbstractCoordinateOperation") |
| @XmlSeeAlso({ |
| AbstractSingleOperation.class, |
| DefaultPassThroughOperation.class, |
| DefaultConcatenatedOperation.class |
| }) |
| public class AbstractCoordinateOperation extends AbstractIdentifiedObject implements CoordinateOperation { |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = -5441746770453401219L; |
| |
| /** |
| * The logger for coordinate operations. |
| */ |
| static final Logger LOGGER = Logger.getLogger(Loggers.COORDINATE_OPERATION); |
| |
| /** |
| * The source CRS, or {@code null} if not available. |
| * |
| * <p><b>Consider this field as final!</b> |
| * This field is non-final only for the convenience of constructors and for initialization |
| * at XML unmarshalling time by {@link #setSource(CoordinateReferenceSystem)}.</p> |
| * |
| * @see #getSourceCRS() |
| */ |
| @SuppressWarnings("serial") // Most SIS implementations are serializable. |
| CoordinateReferenceSystem sourceCRS; |
| |
| /** |
| * The target CRS, or {@code null} if not available. |
| * |
| * <p><b>Consider this field as final!</b> |
| * This field is non-final only for the convenience of constructors and for initialization |
| * at XML unmarshalling time by {@link #setTarget(CoordinateReferenceSystem)}.</p> |
| * |
| * @see #getTargetCRS() |
| */ |
| @SuppressWarnings("serial") // Most SIS implementations are serializable. |
| CoordinateReferenceSystem targetCRS; |
| |
| /** |
| * The CRS required for performing the operation. |
| * It may differ from the source and target CRS. |
| * |
| * <p><b>Consider this field as final!</b> |
| * This field is non-final only for the convenience of constructors.</p> |
| * |
| * @see #getInterpolationCRS() |
| */ |
| @SuppressWarnings("serial") // Most SIS implementations are serializable. |
| private CoordinateReferenceSystem interpolationCRS; |
| |
| /** |
| * Version of the coordinate transformation |
| * (i.e., instantiation due to the stochastic nature of the parameters). |
| * |
| * <p><b>Consider this field as final!</b> |
| * This field is modified only at unmarshalling time by {@link #setOperationVersion(String)}.</p> |
| * |
| * @see #getOperationVersion() |
| */ |
| @XmlElement(name = "operationVersion") |
| private String operationVersion; |
| |
| /** |
| * Estimate(s) of the impact of this operation on point accuracy, or {@code null} if none. |
| * |
| * <p><b>Consider this field as final!</b> |
| * This field is non-final only for the convenience of constructors and for initialization |
| * at XML unmarshalling time by {@link #setAccuracy(PositionalAccuracy[])}.</p> |
| * |
| * @see #getCoordinateOperationAccuracy() |
| */ |
| @SuppressWarnings("serial") // Most SIS implementations are serializable. |
| Collection<PositionalAccuracy> coordinateOperationAccuracy; |
| |
| /** |
| * Transform from positions in the {@linkplain #getSourceCRS source coordinate reference system} |
| * to positions in the {@linkplain #getTargetCRS target coordinate reference system}. |
| * |
| * <p><b>Consider this field as final!</b> |
| * This field is non-final only for the convenience of constructors and for initialization |
| * at XML unmarshalling time by {@link AbstractSingleOperation#afterUnmarshal(Unmarshaller, Object)}.</p> |
| */ |
| @SuppressWarnings("serial") // Most SIS implementations are serializable. |
| MathTransform transform; |
| |
| /** |
| * Indices of target dimensions where "wrap around" may happen as a result of this coordinate operation. |
| * This is usually the longitude axis when the source CRS uses the [-180 … +180]° range and the target |
| * CRS uses the [0 … 360]° range, or the converse. If there is no change, then this is an empty set. |
| * |
| * @see #getWrapAroundChanges() |
| * @see #computeTransientFields() |
| */ |
| private transient Set<Integer> wrapAroundChanges; |
| |
| /** |
| * The inverse of this coordinate operation, computed when first needed. This is stored for making |
| * possible to find the original operation when the inverse of an inverse operation is requested. |
| * Serialized for avoiding information lost if the inverse is requested after deserialization. |
| * |
| * <p>This field is not formally part of coordinate operation definition, |
| * so it is not compared by {@link #equals(Object, ComparisonMode)}.</p> |
| * |
| * @see #getCachedInverse(CoordinateOperation) |
| */ |
| @SuppressWarnings("serial") // Most SIS implementations are serializable. |
| private volatile CoordinateOperation inverse; |
| |
| /** |
| * Creates a new coordinate operation initialized from the given properties. |
| * It is caller's responsibility to: |
| * |
| * <ul> |
| * <li>Set the following fields:<ul> |
| * <li>{@link #sourceCRS}</li> |
| * <li>{@link #targetCRS}</li> |
| * <li>{@link #transform}</li> |
| * </ul></li> |
| * <li>Invoke {@link #checkDimensions(Map)} after the above-cited fields have been set.</li> |
| * </ul> |
| */ |
| AbstractCoordinateOperation(final Map<String,?> properties) { |
| super(properties); |
| this.operationVersion = Containers.property(properties, OPERATION_VERSION_KEY, String.class); |
| Object value = properties.get(COORDINATE_OPERATION_ACCURACY_KEY); |
| if (value != null) { |
| if (value instanceof PositionalAccuracy[]) { |
| coordinateOperationAccuracy = CollectionsExt.nonEmptySet((PositionalAccuracy[]) value); |
| } else { |
| coordinateOperationAccuracy = Set.of((PositionalAccuracy) value); |
| } |
| } |
| } |
| |
| /** |
| * Creates a coordinate operation from the given properties. |
| * The properties given in argument follow the same rules as for the |
| * {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}. |
| * Additionally, the following properties are understood by this constructor: |
| * |
| * <table class="sis"> |
| * <caption>Recognized properties (non exhaustive list)</caption> |
| * <tr> |
| * <th>Property name</th> |
| * <th>Value type</th> |
| * <th>Returned by</th> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.operation.CoordinateOperation#OPERATION_VERSION_KEY}</td> |
| * <td>{@link String}</td> |
| * <td>{@link #getOperationVersion()}</td> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.operation.CoordinateOperation#COORDINATE_OPERATION_ACCURACY_KEY}</td> |
| * <td>{@link PositionalAccuracy} (optionally as array)</td> |
| * <td>{@link #getCoordinateOperationAccuracy()}</td> |
| * </tr><tr> |
| * <th colspan="3" class="hsep">Defined in parent class (reminder)</th> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td> |
| * <td>{@link Identifier} or {@link String}</td> |
| * <td>{@link #getName()}</td> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td> |
| * <td>{@link org.opengis.util.GenericName} or {@link CharSequence} (optionally as array)</td> |
| * <td>{@link #getAlias()}</td> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td> |
| * <td>{@link Identifier} (optionally as array)</td> |
| * <td>{@link #getIdentifiers()}</td> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.IdentifiedObject#DOMAINS_KEY}</td> |
| * <td>{@link org.opengis.referencing.ObjectDomain} (optionally as array)</td> |
| * <td>{@link #getDomains()}</td> |
| * </tr><tr> |
| * <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td> |
| * <td>{@link InternationalString} or {@link String}</td> |
| * <td>{@link #getRemarks()}</td> |
| * </tr> |
| * </table> |
| * |
| * <h4>Constraints</h4> |
| * All arguments except {@code properties} can be {@code null}. |
| * If non-null, the dimension of CRS arguments shall be related to the {@code transform} argument as below: |
| * |
| * <ul> |
| * <li>Dimension of {@code sourceCRS} shall be equal to the transform |
| * {@linkplain org.apache.sis.referencing.operation.transform.AbstractMathTransform#getSourceDimensions() |
| * source dimension} minus the dimension of the {@code interpolationCRS} (if any).</li> |
| * <li>Dimension of {@code targetCRS} shall be equal to the transform |
| * {@linkplain org.apache.sis.referencing.operation.transform.AbstractMathTransform#getTargetDimensions() |
| * target dimension}, minus the dimension of the {@code interpolationCRS} (if any).</li> |
| * </ul> |
| * |
| * If the {@code interpolationCRS} is non-null, then the given {@code transform} shall expect input coordinates |
| * in the following order: |
| * |
| * <ol> |
| * <li>Coordinates of the interpolation CRS. Example: (<var>x</var>,<var>y</var>) in a vertical transform.</li> |
| * <li>Coordinates of the source CRS. Example: (<var>z</var>) in a vertical transform.</li> |
| * </ol> |
| * |
| * The math transform shall let the interpolation coordinates {@linkplain DefaultPassThroughOperation pass through |
| * the operation}. |
| * |
| * @param properties the properties to be given to the identified object. |
| * @param sourceCRS the source CRS, or {@code null} if unspecified. |
| * @param targetCRS the target CRS, or {@code null} if unspecified. |
| * @param interpolationCRS the CRS of additional coordinates needed for the operation, or {@code null} if none. |
| * @param transform transform from positions in the source CRS to positions in the target CRS, |
| * or {@code null} if unspecified. |
| */ |
| @SuppressWarnings("this-escape") // False positive. |
| public AbstractCoordinateOperation(final Map<String,?> properties, |
| final CoordinateReferenceSystem sourceCRS, |
| final CoordinateReferenceSystem targetCRS, |
| final CoordinateReferenceSystem interpolationCRS, |
| final MathTransform transform) |
| { |
| this(properties); |
| this.sourceCRS = sourceCRS; |
| this.targetCRS = targetCRS; |
| this.interpolationCRS = interpolationCRS; |
| this.transform = transform; |
| checkDimensions(properties); |
| } |
| |
| /** |
| * Ensures that {@link #sourceCRS}, {@link #targetCRS} and {@link #interpolationCRS} dimensions |
| * are consistent with {@link #transform} input and output dimensions. |
| */ |
| final void checkDimensions(final Map<String,?> properties) { |
| @SuppressWarnings("LocalVariableHidesMemberVariable") |
| final MathTransform transform = this.transform; // Protect from changes. |
| if (transform != null) { |
| final int interpDim = ReferencingUtilities.getDimension(interpolationCRS); |
| check: for (int isTarget=0; ; isTarget++) { // 0 == source check; 1 == target check. |
| final CoordinateReferenceSystem crs; // Will determine the expected dimensions. |
| int actual; // The MathTransform number of dimensions. |
| switch (isTarget) { |
| case 0: crs = sourceCRS; actual = transform.getSourceDimensions(); break; |
| case 1: crs = targetCRS; actual = transform.getTargetDimensions(); break; |
| default: break check; |
| } |
| int expected = ReferencingUtilities.getDimension(crs); |
| if (interpDim != 0) { |
| if (actual == expected || actual < interpDim) { |
| // This check is not strictly necessary as the next check below would catch the error, |
| // but we provide here a hopefully more helpful error message for a common mistake. |
| throw new IllegalArgumentException(Resources.forProperties(properties) |
| .getString(Resources.Keys.MissingInterpolationOrdinates)); |
| } |
| expected += interpDim; |
| } |
| if (crs != null && actual != expected) { |
| throw new IllegalArgumentException(Errors.forProperties(properties).getString( |
| Errors.Keys.MismatchedTransformDimension_4, super.getName().getCode(), |
| isTarget, expected, actual)); |
| } |
| } |
| } |
| computeTransientFields(); |
| } |
| |
| /** |
| * Computes the {@link #wrapAroundChanges} field after we verified that the coordinate operation is valid. |
| */ |
| final void computeTransientFields() { |
| if (sourceCRS != null && targetCRS != null) { |
| wrapAroundChanges = CoordinateOperations.wrapAroundChanges(sourceCRS, targetCRS.getCoordinateSystem()); |
| } else { |
| wrapAroundChanges = Set.of(); |
| } |
| } |
| |
| /** |
| * Computes transient fields after deserialization. |
| * |
| * @param in the input stream from which to deserialize a coordinate operation. |
| * @throws IOException if an I/O error occurred while reading or if the stream contains invalid data. |
| * @throws ClassNotFoundException if the class serialized on the stream is not on the module path. |
| */ |
| private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { |
| in.defaultReadObject(); |
| computeTransientFields(); |
| } |
| |
| /** |
| * Creates a new coordinate operation with the same values as the specified one. |
| * This copy constructor provides a way to convert an arbitrary implementation into a SIS one |
| * or a user-defined one (as a subclass), usually in order to leverage some implementation-specific API. |
| * |
| * <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p> |
| * |
| * @param operation the coordinate operation to copy. |
| * |
| * @see #castOrCopy(CoordinateOperation) |
| */ |
| protected AbstractCoordinateOperation(final CoordinateOperation operation) { |
| super(operation); |
| sourceCRS = operation.getSourceCRS(); |
| targetCRS = operation.getTargetCRS(); |
| interpolationCRS = operation.getInterpolationCRS().orElse(null); |
| operationVersion = operation.getOperationVersion().orElse(null); |
| coordinateOperationAccuracy = operation.getCoordinateOperationAccuracy(); |
| transform = operation.getMathTransform(); |
| if (operation instanceof AbstractCoordinateOperation) { |
| wrapAroundChanges = ((AbstractCoordinateOperation) operation).wrapAroundChanges; |
| } else { |
| computeTransientFields(); |
| } |
| } |
| |
| /** |
| * Returns a SIS coordinate operation implementation with the values of the given arbitrary implementation. |
| * This method performs the first applicable action in the following choices: |
| * |
| * <ul> |
| * <li>If the given object is {@code null}, then this method returns {@code null}.</li> |
| * <li>Otherwise if the given object is an instance of |
| * {@link org.opengis.referencing.operation.Conversion}, |
| * {@link org.opengis.referencing.operation.Transformation}, |
| * {@link org.opengis.referencing.operation.PointMotionOperation}, |
| * {@link org.opengis.referencing.operation.PassThroughOperation} or |
| * {@link org.opengis.referencing.operation.ConcatenatedOperation}, |
| * then this method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass. |
| * Note that if the given object implements more than one of the above-cited interfaces, |
| * then the {@code castOrCopy(…)} method to be used is unspecified.</li> |
| * <li>Otherwise if the given object is already an instance of |
| * {@code AbstractCoordinateOperation}, then it is returned unchanged.</li> |
| * <li>Otherwise a new {@code AbstractCoordinateOperation} instance is created using the |
| * {@linkplain #AbstractCoordinateOperation(CoordinateOperation) copy constructor} and returned. |
| * Note that this is a <em>shallow</em> copy operation, because the other |
| * properties contained in the given object are not recursively copied.</li> |
| * </ul> |
| * |
| * @param object the object to get as a SIS implementation, or {@code null} if none. |
| * @return a SIS implementation containing the values of the given object (may be the |
| * given object itself), or {@code null} if the argument was null. |
| */ |
| public static AbstractCoordinateOperation castOrCopy(final CoordinateOperation object) { |
| return SubTypes.castOrCopy(object); |
| } |
| |
| /** |
| * Returns the GeoAPI interface implemented by this class. |
| * The default implementation returns {@code CoordinateOperation.class}. |
| * Subclasses implementing a more specific GeoAPI interface shall override this method. |
| * |
| * @return the coordinate operation interface implemented by this class. |
| */ |
| @Override |
| public Class<? extends CoordinateOperation> getInterface() { |
| return CoordinateOperation.class; |
| } |
| |
| /** |
| * Returns {@code true} if this coordinate operation is for the definition of a |
| * {@linkplain org.apache.sis.referencing.crs.DefaultDerivedCRS derived} or |
| * {@linkplain org.apache.sis.referencing.crs.DefaultProjectedCRS projected CRS}. |
| * The standard (ISO 19111) approach constructs <i>defining conversion</i> |
| * as an operation of type {@link org.opengis.referencing.operation.Conversion} |
| * with null {@linkplain #getSourceCRS() source} and {@linkplain #getTargetCRS() target CRS}. |
| * But SIS supports also defining conversions with non-null CRS provided that: |
| * |
| * <ul> |
| * <li>{@link DerivedCRS#getBaseCRS()} is the {@linkplain #getSourceCRS() source CRS} of this operation, and</li> |
| * <li>{@link DerivedCRS#getConversionFromBase()} is this operation instance.</li> |
| * </ul> |
| * |
| * When this method returns {@code true}, the source and target CRS are not marshalled in XML documents. |
| * |
| * @return {@code true} if this coordinate operation is for the definition of a derived or projected CRS. |
| */ |
| public boolean isDefiningConversion() { |
| /* |
| * Trick: we do not need to verify if (this instanceof Conversion) because: |
| * - Only DefaultConversion constructor accepts null source and target CRS. |
| * - DerivedCRS.getConversionFromBase() return type is Conversion. |
| */ |
| return (sourceCRS == null && targetCRS == null) |
| || ((targetCRS instanceof DerivedCRS) |
| && ((DerivedCRS) targetCRS).getBaseCRS() == sourceCRS |
| && ((DerivedCRS) targetCRS).getConversionFromBase() == this); |
| } |
| |
| /** |
| * Returns the source CRS, or {@code null} if unspecified. |
| * The source CRS is mandatory for {@linkplain DefaultTransformation transformations} only. |
| * This information is optional for {@linkplain DefaultConversion conversions} according |
| * the ISO 19111 standard, but Apache SIS tries to provide that CRS in most cases anyway. |
| * |
| * @return the source CRS, or {@code null} if not available. |
| */ |
| @Override |
| public CoordinateReferenceSystem getSourceCRS() { |
| return sourceCRS; |
| } |
| |
| /** |
| * Returns the target CRS, or {@code null} if unspecified. |
| * The target CRS is mandatory for {@linkplain DefaultTransformation transformations} only. |
| * This information is optional for {@linkplain DefaultConversion conversions} according |
| * the ISO 19111 standard, but Apache SIS tries to provide that CRS in most cases anyway. |
| * |
| * @return the target CRS, or {@code null} if not available. |
| */ |
| @Override |
| public CoordinateReferenceSystem getTargetCRS() { |
| return targetCRS; |
| } |
| |
| /** |
| * Returns the <abbr>CRS</abbr> to be used for interpolations in a grid. |
| * Some single coordinate operations employ methods which include interpolation within a grid to derive |
| * the values of operation parameters. The <abbr>CRS</abbr> to be used for the interpolation |
| * may be different from either the source <abbr>CRS</abbr> or the target <abbr>CRS</abbr>. |
| * |
| * <h4>Example</h4> |
| * Some transformations of vertical coordinates (<var>h</var>) require the horizontal coordinates (φ,λ) |
| * in order to interpolate in a grid. This method returns the CRS of the grid where such interpolations |
| * are performed. |
| * |
| * @return the <abbr>CRS</abbr> required for interpolating the values. |
| */ |
| @Override |
| public Optional<CoordinateReferenceSystem> getInterpolationCRS() { |
| return Optional.ofNullable(interpolationCRS); |
| } |
| |
| /** |
| * Returns the version of the coordinate operation. Different versions of a coordinate |
| * {@linkplain DefaultTransformation transformation} may exist because of the stochastic |
| * nature of the parameters. In principle this property is irrelevant to coordinate |
| * {@linkplain DefaultConversion conversions}, but Apache SIS accepts it anyway. |
| * |
| * @return the coordinate operation version. |
| */ |
| @Override |
| public Optional<String> getOperationVersion() { |
| return Optional.ofNullable(operationVersion); |
| } |
| |
| /** |
| * Returns an estimation of the impact of this operation on point accuracy. |
| * The positional accuracy gives position error estimates for target coordinates |
| * of this coordinate operation, assuming no errors in source coordinates. |
| * |
| * @return the position error estimations, or an empty collection if not available. |
| * |
| * @see #getLinearAccuracy() |
| */ |
| @Override |
| public Collection<PositionalAccuracy> getCoordinateOperationAccuracy() { |
| return CollectionsExt.nonNull(coordinateOperationAccuracy); |
| } |
| |
| /** |
| * Returns an estimation of positional accuracy in metres, or {@code NaN} if unknown. |
| * The default implementation tries to infer a value from the metadata returned by |
| * {@link #getCoordinateOperationAccuracy()} using SIS-specific heuristics. |
| * |
| * <h4>Current implementation</h4> |
| * The current implementation uses the heuristic rules listed below. |
| * Note that those rules may change in any future SIS version. |
| * |
| * <ul> |
| * <li>If at least one {@linkplain org.apache.sis.metadata.iso.quality.DefaultQuantitativeResult quantitative |
| * result} is found with a linear unit, then returns the largest result value converted to metres.</li> |
| * |
| * <li>Otherwise if the operation is a {@linkplain DefaultConversion conversion}, |
| * then returns 0 since a conversion is by definition accurate up to rounding errors.</li> |
| * |
| * <li>Otherwise if the operation is a {@linkplain DefaultTransformation transformation}, |
| * then checks if the datum shift were applied with the help of Bursa-Wolf parameters. |
| * If a datum shift has been applied, returns 25 meters. |
| * If a datum shift should have been applied but has been omitted, returns 3000 meters. |
| * |
| * <div class="note"><b>Note:</b> |
| * the 3000 meters value is higher than the highest value (999 meters) found in the EPSG |
| * database version 6.7. The 25 meters value is the next highest value found in the EPSG |
| * database for a significant number of transformations.</div> |
| * |
| * <li>Otherwise if the operation is a {@linkplain DefaultConcatenatedOperation concatenated operation}, |
| * returns the sum of the accuracy of all components. |
| * This is a conservative scenario where we assume that errors cumulate linearly. |
| * |
| * <div class="note"><b>Note:</b> |
| * this is not necessarily the "worst case" scenario since the accuracy could be worst |
| * if the math transforms are highly non-linear.</div></li> |
| * </ul> |
| * |
| * @return the accuracy estimation (always in meters), or NaN if unknown. |
| * |
| * @see org.apache.sis.referencing.CRS#getLinearAccuracy(CoordinateOperation) |
| */ |
| public double getLinearAccuracy() { |
| return PositionalAccuracyConstant.getLinearAccuracy(this); |
| } |
| |
| /** |
| * Returns the object for transforming coordinates in the source CRS to coordinates in the target CRS. |
| * The transform may be {@code null} if this coordinate operation is a defining conversion. |
| * |
| * @return the transform from source to target CRS, or {@code null} if not applicable. |
| */ |
| @Override |
| public MathTransform getMathTransform() { |
| return transform; |
| } |
| |
| /** |
| * Changes coordinates from being referenced to the source <abbr>CRS</abbr> |
| * and/or epoch to being referenced to the target <abbr>CRS</abbr> and/or epoch. |
| * This method operates on coordinate tuples and does not deal with interpolation of geometry types. |
| * |
| * <h4>Implementation specific behavior</h4> |
| * The default implementation has the following characteristics. Those characteristics are not |
| * guaranteed to be met by all implementations of the {@link CoordinateOperation} interface: |
| * |
| * <ul> |
| * <li>If the <abbr>CRS</abbr> and/or epoch of the given data are not equivalent to the source <abbr>CRS</abbr> and/or |
| * epoch of this coordinate operation, this method will automatically prepend an additional operation step.</li> |
| * <li>The coordinate tuples are not transformed immediately, but instead will be computed on-the-fly |
| * in the stream returned by {@link CoordinateSet#stream()}.</li> |
| * <li>If a {@link TransformException} occurs during on-the-fly coordinate operation, it will be wrapped |
| * in an unchecked {@link org.apache.sis.util.collection.BackingStoreException}.</li> |
| * <li>The returned coordinate set is serializable if the given data and the math transform are also serializable.</li> |
| * </ul> |
| * |
| * @param data the coordinates to change. |
| * @return the result of changing coordinates. |
| * @throws TransformException if some coordinates cannot be changed. Note that an absence of exception during |
| * this method call is not a guarantee that the coordinate changes succeeded, because other errors can |
| * occur during the stream execution. |
| * |
| * @since 1.5 |
| */ |
| @Override |
| public CoordinateSet transform(final CoordinateSet data) throws TransformException { |
| return new TransformedCoordinateSet(this, data); |
| } |
| |
| /** |
| * Returns the operation method. This apply only to {@link AbstractSingleOperation} subclasses, |
| * which will make this method public. |
| * |
| * @return the operation method, or {@code null} if none. |
| */ |
| OperationMethod getMethod() { |
| return null; |
| } |
| |
| /** |
| * Returns the parameter descriptor. The default implementation infers the descriptor from the |
| * {@linkplain #transform}, if possible. If no descriptor can be inferred from the math transform, |
| * then this method fallback on the {@link OperationMethod} parameters. |
| */ |
| ParameterDescriptorGroup getParameterDescriptors() { |
| ParameterDescriptorGroup descriptor = getParameterDescriptors(transform); |
| if (descriptor == null) { |
| final OperationMethod method = getMethod(); |
| if (method != null) { |
| descriptor = method.getParameters(); |
| } |
| } |
| return descriptor; |
| } |
| |
| /** |
| * Returns the parameter descriptors for the given transform, or {@code null} if unknown. |
| */ |
| static ParameterDescriptorGroup getParameterDescriptors(MathTransform transform) { |
| while (transform != null) { |
| if (transform instanceof Parameterized) { |
| final ParameterDescriptorGroup param; |
| if (Semaphores.queryAndSet(Semaphores.ENCLOSED_IN_OPERATION)) { |
| throw new AssertionError(); // Should never happen. |
| } |
| try { |
| param = ((Parameterized) transform).getParameterDescriptors(); |
| } finally { |
| Semaphores.clear(Semaphores.ENCLOSED_IN_OPERATION); |
| } |
| if (param != null) { |
| return param; |
| } |
| } |
| if (transform instanceof PassThroughTransform) { |
| transform = ((PassThroughTransform) transform).getSubTransform(); |
| } else { |
| break; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the parameter values. The default implementation infers the |
| * parameter values from the {@linkplain #transform}, if possible. |
| * |
| * @return the parameter values (never {@code null}). |
| * @throws UnsupportedOperationException if the parameter values cannot |
| * be determined for the current math transform implementation. |
| */ |
| ParameterValueGroup getParameterValues() throws UnsupportedOperationException { |
| MathTransform mt = transform; |
| while (mt != null) { |
| if (mt instanceof Parameterized) { |
| final ParameterValueGroup param; |
| if (Semaphores.queryAndSet(Semaphores.ENCLOSED_IN_OPERATION)) { |
| throw new AssertionError(); // Should never happen. |
| } |
| try { |
| param = ((Parameterized) mt).getParameterValues(); |
| } finally { |
| Semaphores.clear(Semaphores.ENCLOSED_IN_OPERATION); |
| } |
| if (param != null) { |
| return param; |
| } |
| } |
| if (mt instanceof PassThroughTransform) { |
| mt = ((PassThroughTransform) mt).getSubTransform(); |
| } else { |
| break; |
| } |
| } |
| throw new UnsupportedImplementationException(Classes.getClass(mt)); |
| } |
| |
| /** |
| * Returns the indices of target dimensions where "wrap around" may happen as a result of this coordinate operation. |
| * If such change exists, then this is usually the longitude axis when the source CRS uses the [-180 … +180]° range |
| * and the target CRS uses the [0 … 360]° range, or the converse. If there is no change, then this is an empty set. |
| * |
| * <h4>Inverse relationship</h4> |
| * sometimes the target dimensions returned by this method can be mapped directly to wraparound axes in source CRS, |
| * but this is not always the case. For example, consider the following operation chain: |
| * |
| * <div style="text-align:center">source projected CRS ⟶ base CRS ⟶ target geographic CRS</div> |
| * |
| * In this example, a wraparound axis in the target CRS (the longitude) can be mapped to a wraparound axis in |
| * the {@linkplain org.apache.sis.referencing.crs.DefaultProjectedCRS#getBaseCRS() base CRS}. But there is no |
| * corresponding wraparound axis in the source CRS because the <em>easting</em> axis in projected CRS does not |
| * have a wraparound range meaning. We could argue that |
| * {@linkplain org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getDirection() axis directions} match, |
| * but such matching is not guaranteed to exist since {@code ProjectedCRS} is a special case of {@code DerivedCRS} |
| * and derived CRS can have rotations. |
| * |
| * <h4>Default implementation</h4> |
| * The default implementation infers this set by inspecting the source and target coordinate system axes. |
| * It returns the indices of all target axes having {@link org.opengis.referencing.cs.RangeMeaning#WRAPAROUND} |
| * and for which the following condition holds: a colinear source axis exists with compatible unit of measurement, |
| * and the range (taking unit conversions in account) or range meaning of those source and target axes are not |
| * the same. |
| * |
| * @return indices of target dimensions where "wrap around" may happen as a result of this coordinate operation. |
| * |
| * @since 0.8 |
| */ |
| @SuppressWarnings("ReturnOfCollectionOrArrayField") |
| public Set<Integer> getWrapAroundChanges() { |
| return wrapAroundChanges; |
| } |
| |
| /** |
| * Returns the inverse of the given coordinate operation, or {@code null} if unspecified. |
| * This method only checks the cached value and does not compute a new value if none is present. |
| * |
| * @param forward the operation for which to get the inverse. |
| * @return the cached inverse operation, or {@code null} if none. |
| */ |
| static CoordinateOperation getCachedInverse(final CoordinateOperation forward) { |
| if (forward instanceof AbstractCoordinateOperation) { |
| final CoordinateOperation inverse = ((AbstractCoordinateOperation) forward).inverse; |
| if (inverse != null) { |
| return inverse; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Caches the inverse of the given coordinate operation. |
| * |
| * @param forward the operation for which to cache the inverse. |
| * @param inverse the inverse operation to cache. |
| */ |
| static void setCachedInverse(final CoordinateOperation forward, final CoordinateOperation inverse) { |
| if (inverse instanceof AbstractCoordinateOperation) { |
| ((AbstractCoordinateOperation) inverse).inverse = forward; |
| } |
| if (forward instanceof AbstractCoordinateOperation) { |
| ((AbstractCoordinateOperation) forward).inverse = inverse; |
| } |
| } |
| |
| /** |
| * Compares this coordinate operation with the specified object for equality. If the {@code mode} argument |
| * is {@link ComparisonMode#STRICT} or {@link ComparisonMode#BY_CONTRACT BY_CONTRACT}, then all available |
| * properties are compared including the {@linkplain #getDomains() domains} and the accuracy. |
| * |
| * @param object the object to compare to {@code this}. |
| * @param mode {@link ComparisonMode#STRICT STRICT} for performing a strict comparison, or |
| * {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} for ignoring properties |
| * that do not make a difference in the numerical results of coordinate operations. |
| * @return {@code true} if both objects are equal for the given comparison mode. |
| */ |
| @Override |
| public boolean equals(final Object object, ComparisonMode mode) { |
| if (super.equals(object, mode)) { |
| if (mode == ComparisonMode.STRICT) { |
| final AbstractCoordinateOperation that = (AbstractCoordinateOperation) object; |
| if (Objects.equals(sourceCRS, that.sourceCRS) && |
| Objects.equals(interpolationCRS, that.interpolationCRS) && |
| Objects.equals(transform, that.transform) && |
| Objects.equals(coordinateOperationAccuracy, that.coordinateOperationAccuracy)) |
| { |
| // Check against never-ending recursivity with DerivedCRS. |
| if (Semaphores.queryAndSet(Semaphores.CONVERSION_AND_CRS)) { |
| return true; |
| } else try { |
| return Objects.equals(targetCRS, that.targetCRS); |
| } finally { |
| Semaphores.clear(Semaphores.CONVERSION_AND_CRS); |
| } |
| } |
| } else { |
| /* |
| * This block is for all ComparisonModes other than STRICT. At this point we know that the metadata |
| * properties (class, name, identifiers, etc.) match the criterion of the given comparison mode. |
| * Before to continue perform the following checks: |
| * |
| * - Scope, domain and accuracy properties only if NOT in "ignore metadata" mode. |
| * - Interpolation CRS in all cases (regardless if ignoring metadata or not). |
| */ |
| final CoordinateOperation that = (CoordinateOperation) object; |
| if ((mode.isIgnoringMetadata() || |
| (deepEquals(getCoordinateOperationAccuracy(), that.getCoordinateOperationAccuracy(), mode))) && |
| deepEquals(getInterpolationCRS(), that.getInterpolationCRS(), mode)) |
| { |
| /* |
| * At this point all metadata match or can be ignored. First, compare the targetCRS. |
| * We need to perform this comparison only if this 'equals(…)' method is not invoked |
| * from AbstractDerivedCRS, otherwise we would fall in an infinite recursive loop |
| * (because targetCRS is the DerivedCRS, which in turn wants to compare this operation). |
| * |
| * We also opportunistically use this "anti-recursivity" check for another purpose. |
| * The Semaphores.COMPARING flag should be set only when AbstractDerivedCRS is comparing |
| * its "from base" conversion. The flag should never be set in any other circumstance, |
| * since this is an internal Apache SIS mechanism. If we know that we are comparing the |
| * AbstractDerivedCRS.fromBase conversion, then (in the way Apache SIS is implemented) |
| * this.sourceCRS == AbstractDerivedCRS.baseCRS. Consequently, we can relax the check of |
| * sourceCRS axis order if the mode is ComparisonMode.IGNORE_METADATA. |
| */ |
| boolean debug = false; |
| if (Semaphores.queryAndSet(Semaphores.CONVERSION_AND_CRS)) { |
| if (mode.isIgnoringMetadata()) { |
| debug = (mode == ComparisonMode.DEBUG); |
| mode = ComparisonMode.ALLOW_VARIANT; |
| } |
| } else try { |
| if (!deepEquals(getTargetCRS(), that.getTargetCRS(), mode)) { |
| return false; |
| } |
| } finally { |
| Semaphores.clear(Semaphores.CONVERSION_AND_CRS); |
| } |
| /* |
| * Now compare the sourceCRS, potentially with a relaxed ComparisonMode (see above comment). |
| * If the comparison mode allows the two CRS to have different axis order and units, then we |
| * need to take in account those difference before to compare the MathTransform. We proceed |
| * by modifying 'tr2' as if it was a MathTransform with crs1 as the source instead of crs2. |
| */ |
| final CoordinateReferenceSystem crs1 = this.getSourceCRS(); |
| final CoordinateReferenceSystem crs2 = that.getSourceCRS(); |
| if (deepEquals(crs1, crs2, mode)) { |
| MathTransform tr1 = this.getMathTransform(); |
| MathTransform tr2 = that.getMathTransform(); |
| if (mode == ComparisonMode.ALLOW_VARIANT) try { |
| final MathTransform before = MathTransforms.linear( |
| CoordinateSystems.swapAndScaleAxes(crs1.getCoordinateSystem(), |
| crs2.getCoordinateSystem())); |
| final MathTransform after = MathTransforms.linear( |
| CoordinateSystems.swapAndScaleAxes(that.getTargetCRS().getCoordinateSystem(), |
| this.getTargetCRS().getCoordinateSystem())); |
| tr2 = MathTransforms.concatenate(before, tr2, after); |
| } catch (IncommensurableException | RuntimeException e) { |
| Logging.ignorableException(LOGGER, AbstractCoordinateOperation.class, "equals", e); |
| } |
| if (deepEquals(tr1, tr2, mode)) return true; |
| assert !debug || deepEquals(tr1, tr2, ComparisonMode.DEBUG); // For locating the mismatch. |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Invoked by {@code hashCode()} for computing the hash code when first needed. |
| * See {@link AbstractIdentifiedObject#computeHashCode()} for more information. |
| * |
| * @return the hash code value. This value may change in any future Apache SIS version. |
| */ |
| @Override |
| protected long computeHashCode() { |
| /* |
| * Do NOT take 'getMethod()' in account in hash code calculation. See the comment |
| * inside the above 'equals(Object, ComparisonMode)' method for more information. |
| * Note that we use the 'transform' hash code, which should be sufficient. |
| */ |
| return super.computeHashCode() + Objects.hash(sourceCRS, targetCRS, interpolationCRS, transform); |
| } |
| |
| /** |
| * Formats this coordinate operation in Well Known Text (WKT) version 2 format. |
| * |
| * <h4>ESRI extension</h4> |
| * Coordinate operations cannot be formatted in standard WKT 1 format, but an ESRI variant of WKT 1 |
| * allows a subset of coordinate operations with the ESRI-specific {@code GEOGTRAN} keyword. |
| * To enabled this variant, {@link org.apache.sis.io.wkt.WKTFormat} can be configured as below: |
| * |
| * {@snippet lang="java" : |
| * format = new WKTFormat(); |
| * format.setConvention(Convention.WKT1_IGNORE_AXES); |
| * format.setNameAuthority(Citations.ESRI); |
| * } |
| * |
| * @param formatter the formatter to use. |
| * @return {@code "CoordinateOperation"}. |
| * |
| * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#113">WKT 2 specification §17</a> |
| */ |
| @Override |
| protected String formatTo(final Formatter formatter) { |
| super.formatTo(formatter); |
| formatter.newLine(); |
| @SuppressWarnings("LocalVariableHidesMemberVariable") |
| final CoordinateReferenceSystem sourceCRS = getSourceCRS(), |
| targetCRS = getTargetCRS(); |
| final Convention convention = formatter.getConvention(); |
| final boolean isWKT1 = (convention.majorVersion() == 1); |
| /* |
| * If the WKT is a component of a ConcatenatedOperation, do not format the source CRS since it is identical |
| * to the target CRS of the previous step, or to the source CRS of the enclosing "ConcatenatedOperation" if |
| * this step is the first step. |
| * |
| * This decision is SIS-specific since the WKT 2 specification does not define concatenated operations. |
| * This choice may change in any future SIS version. |
| */ |
| final FormattableObject enclosing = formatter.getEnclosingElement(1); |
| final boolean isSubOperation = (enclosing instanceof PassThroughOperation); |
| final boolean isComponent = (enclosing instanceof ConcatenatedOperation); |
| boolean isGeogTran = false; |
| if (!isSubOperation && !isComponent) { |
| isGeogTran = isWKT1 && (sourceCRS instanceof GeographicCRS) && (targetCRS instanceof GeographicCRS); |
| if (isGeogTran) { |
| // ESRI-specific, similar to WKT 1. |
| formatter.append(WKTUtilities.toFormattable(sourceCRS)); formatter.newLine(); |
| formatter.append(WKTUtilities.toFormattable(targetCRS)); formatter.newLine(); |
| } else { |
| // WKT 2 (ISO 19162). |
| append(formatter, sourceCRS, WKTKeywords.SourceCRS); |
| append(formatter, targetCRS, WKTKeywords.TargetCRS); |
| } |
| } |
| final OperationMethod method = getMethod(); |
| if (method != null) { |
| formatter.append(DefaultOperationMethod.castOrCopy(method)); |
| ParameterValueGroup parameters; |
| try { |
| parameters = getParameterValues(); |
| } catch (UnsupportedOperationException e) { |
| final IdentifiedObject c = getParameterDescriptors(); |
| formatter.setInvalidWKT(c != null ? c : this, e); |
| parameters = null; |
| } |
| if (parameters != null) { |
| /* |
| * Format the parameter values. Apache SIS uses the EPSG geodetic dataset as the main source of |
| * parameter definitions. When a parameter is defined by both OGC and EPSG with different names, |
| * the Formatter class is responsible for choosing an appropriate name. But when the difference |
| * is more fundamental, we may have duplication. For example in the "Molodensky" operation, OGC |
| * uses source and target axis lengths while EPSG uses only difference between those lengths. |
| * In this case, OGC and EPSG parameters are defined separately and are redundant. To simplify |
| * the CoordinateOperation WKT, we omit non-EPSG parameters when we have determined that we are |
| * about to describe an EPSG operation. We could generalize this filtering to any authority, but |
| * we don't because few authorities are as complete as EPSG, so other authorities are more likely |
| * to mix EPSG or someone else components with their own. Note also that we don't apply filtering |
| * on MathTransform WKT neither for more reliable debugging. |
| */ |
| final boolean filter = isGeogTran || |
| (WKTUtilities.isEPSG(parameters.getDescriptor(), false) && // NOT method.getName() |
| Constants.EPSG.equalsIgnoreCase(Citations.toCodeSpace(formatter.getNameAuthority()))); |
| formatter.newLine(); |
| formatter.indent(+1); |
| for (final GeneralParameterValue param : parameters.values()) { |
| if (!filter || WKTUtilities.isEPSG(param.getDescriptor(), true)) { |
| WKTUtilities.append(param, formatter); |
| } |
| } |
| formatter.indent(-1); |
| } |
| } |
| /* |
| * Add interpolation CRS if we are formatting a top-level WKT 2 single operation. |
| */ |
| if (!isSubOperation && !isGeogTran && !(this instanceof ConcatenatedOperation)) { |
| append(formatter, getInterpolationCRS().orElse(null), WKTKeywords.InterpolationCRS); |
| final double accuracy = getLinearAccuracy(); |
| if (accuracy > 0) { |
| formatter.append(new FormattableObject() { |
| @Override protected String formatTo(final Formatter formatter) { |
| formatter.append(accuracy); |
| return WKTKeywords.OperationAccuracy; |
| } |
| }); |
| } |
| } |
| /* |
| * Verifies if what we wrote is allowed by the standard. |
| */ |
| if (isGeogTran) { |
| if (method == null || convention != Convention.WKT1_IGNORE_AXES) { |
| formatter.setInvalidWKT(this, null); |
| } |
| return WKTKeywords.GeogTran; |
| } |
| if (isWKT1) { |
| formatter.setInvalidWKT(this, null); |
| } |
| if (isComponent) { |
| formatter.setInvalidWKT(this, null); |
| return "CoordinateOperationStep"; |
| } |
| return WKTKeywords.CoordinateOperation; |
| } |
| |
| /** |
| * Appends the given CRS (if non-null) wrapped in an element of the given name. |
| * |
| * @param formatter the formatter where to append the object name. |
| * @param crs the object to append, or {@code null} if none. |
| * @param type the keyword to write before the object. |
| */ |
| private static void append(final Formatter formatter, final CoordinateReferenceSystem crs, final String type) { |
| if (crs != null) { |
| formatter.append(new FormattableObject() { |
| @Override protected String formatTo(final Formatter formatter) { |
| formatter.indent(-1); |
| formatter.append(WKTUtilities.toFormattable(crs)); |
| formatter.indent(+1); |
| return type; |
| } |
| }); |
| formatter.newLine(); |
| } |
| } |
| |
| |
| |
| |
| /* |
| ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ |
| ┃ ┃ |
| ┃ XML support with JAXB ┃ |
| ┃ ┃ |
| ┃ The following methods are invoked by JAXB using reflection (even if ┃ |
| ┃ they are private) or are helpers for other methods invoked by JAXB. ┃ |
| ┃ Those methods can be safely removed if Geographic Markup Language ┃ |
| ┃ (GML) support is not needed. ┃ |
| ┃ ┃ |
| ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ |
| */ |
| |
| /** |
| * Creates a new object in which every attributes are set to a null value. |
| * <strong>This is not a valid object.</strong> This constructor is strictly |
| * reserved to JAXB, which will assign values to the fields using reflection. |
| */ |
| AbstractCoordinateOperation() { |
| super(org.apache.sis.referencing.privy.NilReferencingObject.INSTANCE); |
| } |
| |
| /** |
| * Invoked by JAXB for getting the source CRS to marshal. |
| */ |
| @XmlElement(name = "sourceCRS") |
| private CoordinateReferenceSystem getSource() { |
| return isDefiningConversion() ? null : getSourceCRS(); |
| } |
| |
| /** |
| * Invoked by JAXB at marshalling time for setting the source CRS. |
| */ |
| private void setSource(final CoordinateReferenceSystem crs) { |
| if (sourceCRS == null) { |
| sourceCRS = crs; |
| } else if (!sourceCRS.equals(crs)) { // Could be defined by ConcatenatedOperation. |
| ImplementationHelper.propertyAlreadySet(AbstractCoordinateOperation.class, "setSource", "sourceCRS"); |
| } |
| } |
| |
| /** |
| * Invoked by JAXB for getting the target CRS to marshal. |
| */ |
| @XmlElement(name = "targetCRS") |
| private CoordinateReferenceSystem getTarget() { |
| return isDefiningConversion() ? null : getTargetCRS(); |
| } |
| |
| /** |
| * Invoked by JAXB at unmarshalling time for setting the target CRS. |
| */ |
| private void setTarget(final CoordinateReferenceSystem crs) { |
| if (targetCRS == null) { |
| targetCRS = crs; |
| } else if (!targetCRS.equals(crs)) { // Could be defined by ConcatenatedOperation. |
| ImplementationHelper.propertyAlreadySet(AbstractCoordinateOperation.class, "setTarget", "targetCRS"); |
| } |
| } |
| |
| /** |
| * Invoked by JAXB only at marshalling time. |
| */ |
| @XmlElement(name = "coordinateOperationAccuracy") |
| private PositionalAccuracy[] getAccuracy() { |
| final Collection<PositionalAccuracy> accuracy = getCoordinateOperationAccuracy(); |
| final int size = accuracy.size(); |
| return (size != 0) ? accuracy.toArray(new PositionalAccuracy[size]) : null; |
| } |
| |
| /** |
| * Invoked by JAXB only at unmarshalling time. |
| */ |
| private void setAccuracy(final PositionalAccuracy[] values) { |
| if (coordinateOperationAccuracy == null) { |
| coordinateOperationAccuracy = UnmodifiableArrayList.wrap(values); |
| } else { |
| ImplementationHelper.propertyAlreadySet(AbstractCoordinateOperation.class, "setAccuracy", "coordinateOperationAccuracy"); |
| } |
| } |
| |
| /** |
| * Invoked by JAXB after unmarshalling. |
| * May be overridden by subclasses. |
| */ |
| void afterUnmarshal(Unmarshaller unmarshaller, Object parent) { |
| computeTransientFields(); |
| } |
| } |