blob: 2849fc7d94f344d0ab82e7a0b7e59e5fee4c2fe4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.referencing.operation.transform;
import java.util.List;
import java.util.Arrays;
import java.util.Optional;
import java.util.logging.Logger;
import java.awt.geom.AffineTransform;
import org.opengis.util.FactoryException;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.referencing.operation.OperationMethod;
import org.apache.sis.geometry.GeneralDirectPosition;
import org.apache.sis.parameter.Parameterized;
import org.apache.sis.referencing.operation.matrix.Matrices;
import org.apache.sis.io.wkt.Formatter;
import org.apache.sis.io.wkt.FormattableObject;
import org.apache.sis.referencing.internal.Resources;
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.system.Loggers;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ArgumentCheckByAssertion;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.LenientComparable;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.logging.Logging;
/**
* Provides a default implementation for most methods required by the {@link MathTransform} interface.
* A {@code MathTransform} is an object that actually does the work of applying a
* {@linkplain org.apache.sis.referencing.operation.DefaultFormula formula} to coordinate values.
* The math transform does not know or care how the coordinates relate to positions in the real world.
* For example if an affine transform scales <var>z</var> values by a factor of 1000,
* then it could be converting metres to millimetres, or it could be converting kilometres to metres.
*
* <p>{@code AbstractMathTransform} provides a convenient base class from which {@code MathTransform} implementations
* can be easily derived. It also defines a few additional SIS-specific methods for convenience of performance.
* The simplest way to implement this abstract class is to provide an implementation for the following methods only:</p>
*
* <ul>
* <li>{@link #getSourceDimensions()}</li>
* <li>{@link #getTargetDimensions()}</li>
* <li>{@link #transform(double[], int, double[], int, boolean)}</li>
* </ul>
*
* However, more performance may be gained by overriding the other {@code transform(…)} methods as well.
*
* <h2>Immutability and thread safety</h2>
* All Apache SIS implementations of {@code MathTransform} are immutable and thread-safe.
* It is highly recommended that third-party implementations be immutable and thread-safe too.
* This means that unless otherwise noted in the javadoc, {@code MathTransform} instances can
* be shared by many objects and passed between threads without synchronization.
*
* <h2>Serialization</h2>
* {@code MathTransform} may or may not be serializable, at implementation choices.
* Most Apache SIS implementations are serializable, but the serialized objects are not guaranteed to be compatible
* with future SIS versions. Serialization should be used only for short term storage or RMI between applications
* running the same SIS version.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 1.5
*
* @see DefaultMathTransformFactory
* @see org.apache.sis.referencing.operation.AbstractCoordinateOperation
*
* @since 0.5
*/
public abstract class AbstractMathTransform extends FormattableObject
implements MathTransform, Parameterized, LenientComparable
{
/**
* The logger for coordinate operations.
*/
static final Logger LOGGER = Logger.getLogger(Loggers.COORDINATE_OPERATION);
/**
* Maximum buffer size when creating temporary arrays. Must not be too big, otherwise the
* cost of allocating the buffer may be greater than the benefit of transforming array of
* coordinates. Remember that double number occupy 8 bytes, so a buffer of size 512 will
* actually consumes 4 kb of memory.
*/
static final int MAXIMUM_BUFFER_SIZE = 512;
/**
* Maximum number of {@link TransformException}s to catch while transforming a block of
* {@value #MAXIMUM_BUFFER_SIZE} coordinate values in an array. The default implementation of
* {@code transform} methods set un-transformable coordinates to {@linkplain Double#NaN NaN}
* before to let the exception propagate. However if more then {@value} exceptions occur in
* a block of {@value #MAXIMUM_BUFFER_SIZE} <em>coordinates</em> (not coordinate tuples),
* then we will give up. We put a limit in order to avoid slowing down the application
* too much if a whole array is not transformable.
*
* <p>Note that in case of failure, the first {@code TransformException} is still propagated;
* we do not "eat" it. We just set the coordinates to {@code NaN} before to let the propagation
* happen. If no exception handling should be performed at all, then {@code MAXIMUM_FAILURES}
* can be set to 0.</p>
*
* <p>Having {@code MAXIMUM_BUFFER_SIZE} sets to 512 and {@code MAXIMUM_FAILURES} sets to 32
* means that we tolerate about 6.25% of un-transformable points.</p>
*/
static final int MAXIMUM_FAILURES = 32;
/**
* The cached hash code value, or 0 if not yet computed. This field is calculated only when
* first needed. We do not declare it {@code volatile} because it is not a big deal if this
* field is calculated many times, and the same value should be produced by all computations.
* The only possible outdated value is 0, which is okay.
*/
private transient int hashCode;
/**
* Constructor for subclasses.
*/
protected AbstractMathTransform() {
}
/**
* Returns the number of dimensions of input points.
*
* @return the number of dimensions of input points.
*
* @see org.apache.sis.referencing.operation.DefaultOperationMethod#getSourceDimensions()
*/
@Override
public abstract int getSourceDimensions();
/**
* Returns the number of dimensions of output points.
*
* @return the number of dimensions of output points.
*
* @see org.apache.sis.referencing.operation.DefaultOperationMethod#getTargetDimensions()
*/
@Override
public abstract int getTargetDimensions();
/**
* Returns the ranges of coordinate values which can be used as inputs.
* They are limits where the transform is mathematically and numerically applicable.
* This is <em>not</em> the domain of validity for which a coordinate reference system has been defined,
* because this method ignores "real world" considerations such as datum and country boundaries.
*
* <p>This method is for allowing callers to crop their data for removing areas that may cause numerical problems.
* For example, results of Mercator projection tend to infinity when the latitude value approaches a pole.
* For avoiding data structures with unreasonably large values or {@link Double#NaN},
* we commonly crop data to some arbitrary maximal latitude value (typically 80 or 84°) before projection.
* Those limits are arbitrary, the transform does not become suddenly invalid after a limit.
* The {@link DomainDefinition} gives some controls on the criteria for choosing a limit.</p>
*
* <p>Many transforms, in particular all affine transforms, have no mathematical limits.
* Consequently, the default implementation returns an empty value.
* Again it does not mean that the {@linkplain org.apache.sis.referencing.operation.AbstractCoordinateOperation
* coordinate operation} has no geospatial domain of validity, but the latter is not the purpose of this method.
* This method is (for example) for preventing a viewer to crash when attempting to render a world-wide image.</p>
*
* <p>Callers do not need to search through {@linkplain MathTransforms#getSteps(MathTransform) transform steps}.
* SIS implementation of {@link MathTransforms#concatenate(MathTransform, MathTransform) concatenated transforms}
* do that automatically.</p>
*
* @param criteria controls the definition of transform domain.
* @return estimation of a domain where this transform is considered numerically applicable.
* @throws TransformException if the domain cannot be estimated.
*
* @see MathTransforms#getDomain(MathTransform)
* @see org.opengis.referencing.operation.CoordinateOperation#getDomainOfValidity()
*
* @since 1.3
*/
public Optional<Envelope> getDomain(DomainDefinition criteria) throws TransformException {
ArgumentChecks.ensureNonNull("criteria", criteria);
return Optional.empty();
}
/**
* Returns the parameter descriptors for this math transform, or {@code null} if unknown.
*
* <h4>Relationship with ISO 19111</h4>
* This method is similar to {@link OperationMethod#getParameters()}, except that typical
* {@link MathTransform} implementations return parameters in standard units (usually
* {@linkplain org.apache.sis.measure.Units#METRE metres} or
* {@linkplain org.apache.sis.measure.Units#DEGREE decimal degrees}).
*
* @return the parameter descriptors for this math transform, or {@code null} if unspecified.
*
* @see org.apache.sis.referencing.operation.DefaultOperationMethod#getParameters()
*/
@Override
public ParameterDescriptorGroup getParameterDescriptors() {
final ContextualParameters parameters = getContextualParameters();
return (parameters != null) ? parameters.getDescriptor() : null;
}
/**
* Returns the parameter values for this math transform, or {@code null} if unknown.
* This is not necessarily the parameters that the user specified at construction time,
* since implementations may have applied normalizations.
*
* <h4>Normalized and contextual parameters</h4>
* Most Apache SIS implementations of map projections perform their calculations on an ellipsoid
* having a semi-major axis length of 1. In such cases, the group returned by this method contains
* a {@code "semi_major"} parameter with a value of 1. If the real axis length is desired, we need
* to take in account the context of this math transform, i.e. the scales and offsets applied before
* and after this transform. This information is provided by {@link #getContextualParameters()}.
*
* @return the parameter values for this math transform, or {@code null} if unspecified.
* Note that those parameters may be normalized (e.g. represent a transformation
* of an ellipsoid of semi-major axis length of 1).
*
* @see #getContextualParameters()
* @see org.apache.sis.referencing.operation.DefaultConversion#getParameterValues()
*/
@Override
public ParameterValueGroup getParameterValues() {
/*
* Do NOT try to infer the parameters from getContextualParameters(). This is usually not appropriate
* because if ContextualParameters declares "normalize" and "denormalize" affine transforms, then those
* transforms need to be taken in account in a way that only the subclass know.
*/
return null;
}
/**
* Returns the parameters for a sequence of <i>normalize</i> → {@code this} → <i>denormalize</i>
* transforms (<i>optional operation</i>).
*
* Subclasses can override this method if they choose to split their computation in linear and non-linear parts.
* Such split is optional: it can leads to better performance (because SIS can concatenate efficiently consecutive
* linear transforms), but should not change significantly the result (ignoring differences in rounding errors).
* If a split has been done, then this {@code MathTransform} represents only the non-linear step and Apache SIS
* needs this method for reconstructing the parameters of the complete transform.
*
* @return the parameter values for the sequence of <i>normalize</i> → {@code this} → <i>denormalize</i>
* transforms, or {@code null} if unspecified.
* Callers should not modify the returned parameters, since modifications (if allowed)
* will generally not be reflected back in this {@code MathTransform}.
*
* @since 0.6
*/
protected ContextualParameters getContextualParameters() {
return null;
}
/**
* Tests whether this transform does not move any points.
* The default implementation always returns {@code false}.
*/
@Override
public boolean isIdentity() {
return false;
}
/**
* Constructs an error message for the {@link MismatchedDimensionException}.
*
* @param argument the argument name with the wrong number of dimensions.
* @param expected the expected dimension.
* @param dimension the wrong dimension.
*/
static MismatchedDimensionException mismatchedDimension(final String argument, final int expected, final int dimension) {
return new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, argument, expected, dimension));
}
/**
* Transforms the specified {@code ptSrc} and stores the result in {@code ptDst}.
* The default implementation performs the following steps:
*
* <ul>
* <li>Ensures that the dimension of the given points are consistent with the
* {@linkplain #getSourceDimensions() source} and {@linkplain #getTargetDimensions()
* target dimensions} of this math transform.</li>
* <li>Delegates to the {@link #transform(double[], int, double[], int, boolean)} method.</li>
* </ul>
*
* This method does not update the associated {@link org.opengis.referencing.crs.CoordinateReferenceSystem} value.
*
* @param ptSrc the coordinate tuple to be transformed.
* @param ptDst the coordinate tuple that stores the result of transforming {@code ptSrc}, or {@code null}.
* @return the coordinate tuple after transforming {@code ptSrc} and storing the result in {@code ptDst},
* or a newly created point if {@code ptDst} was null.
* @throws MismatchedDimensionException if {@code ptSrc} or {@code ptDst} doesn't have the expected dimension.
* @throws TransformException if the point cannot be transformed.
*/
@Override
public DirectPosition transform(final DirectPosition ptSrc, DirectPosition ptDst) throws TransformException {
final int dimSource = getSourceDimensions();
final int dimTarget = getTargetDimensions();
ArgumentChecks.ensureDimensionMatches("ptSrc", dimSource, ptSrc);
if (ptDst != null) {
ArgumentChecks.ensureDimensionMatches("ptDst", dimTarget, ptDst);
/*
* Transforms the coordinates using a temporary 'double[]' buffer,
* and copies the transformation result in the destination position.
*/
final double[] array;
if (dimSource >= dimTarget) {
array = ptSrc.getCoordinate();
} else {
array = new double[dimTarget];
for (int i=dimSource; --i>=0;) {
array[i] = ptSrc.getOrdinate(i);
}
}
transform(array, 0, array, 0, false);
for (int i=0; i<dimTarget; i++) {
ptDst.setOrdinate(i, array[i]);
}
} else {
/*
* Destination not set. We are going to create the destination here. Since we know that the
* destination will be the SIS implementation, write directly into the `coordinates` array.
*/
final GeneralDirectPosition destination = new GeneralDirectPosition(dimTarget);
final double[] source;
if (dimSource <= dimTarget) {
source = destination.coordinates;
for (int i=0; i<dimSource; i++) {
source[i] = ptSrc.getOrdinate(i);
}
} else {
source = ptSrc.getCoordinate();
}
transform(source, 0, destination.coordinates, 0, false);
ptDst = destination;
}
return ptDst;
}
/**
* Transforms a single coordinate tuple in an array, and optionally computes the transform
* derivative at that location. Invoking this method is conceptually equivalent to running
* the following:
*
* {@snippet lang="java" :
* Matrix derivative = null;
* if (derivate) {
* double[] coordinates = Arrays.copyOfRange(srcPts, srcOff, srcOff + getSourceDimensions());
* derivative = this.derivative(new GeneralDirectPosition(coordinates));
* }
* this.transform(srcPts, srcOff, dstPts, dstOff, 1); // May overwrite srcPts.
* return derivative;
* }
*
* However, this method provides two advantages:
*
* <ul>
* <li>It is usually easier to implement for {@code AbstractMathTransform} subclasses.
* The default {@link #transform(double[], int, double[], int, int)} method implementation will invoke this
* method in a loop, taking care of the {@linkplain IterationStrategy iteration strategy} depending on the
* argument value.</li>
*
* <li>When both the transformed point and its derivative are needed, this method may be significantly faster than
* invoking the {@code transform} and {@code derivative} methods separately because many internal calculations are
* the same. Computing those two information in a single step can help to reduce redundant calculation.</li>
* </ul>
*
* <h4>Note for implementers</h4>
* The source and destination may overlap. Consequently, implementers must read all source
* coordinate values before to start writing the transformed coordinates in the destination array.
*
* @param srcPts the array containing the source coordinates (cannot be {@code null}).
* @param srcOff the offset to the point to be transformed in the source array.
* @param dstPts the array into which the transformed coordinates is returned. May be the same as {@code srcPts}.
* May be {@code null} if only the derivative matrix is desired.
* @param dstOff the offset to the location of the transformed point that is stored in the destination array.
* @param derivate {@code true} for computing the derivative, or {@code false} if not needed.
* @return the matrix of the transform derivative at the given source position,
* or {@code null} if the {@code derivate} argument is {@code false}.
* @throws TransformException if the point cannot be transformed or
* if a problem occurred while calculating the derivative.
*
* @see #derivative(DirectPosition)
* @see #transform(DirectPosition, DirectPosition)
* @see MathTransforms#derivativeAndTransform(MathTransform, double[], int, double[], int)
*/
public abstract Matrix transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, boolean derivate)
throws TransformException;
/**
* Transforms a list of coordinate tuples. This method is provided for efficiently transforming many points.
* The supplied array of coordinate values will contain packed coordinate values.
*
* <h4>Example</h4>
* If the source dimension is 3, then the coordinates will be packed in this order:
* (<var>x₀</var>,<var>y₀</var>,<var>z₀</var>,
* <var>x₁</var>,<var>y₁</var>,<var>z₁</var> …).
*
* <h4>Implementation note</h4>
* The default implementation invokes {@link #transform(double[], int, double[], int, boolean)} in a loop,
* using an {@linkplain IterationStrategy iteration strategy} determined from the arguments for iterating
* over the points. For creating a more efficient implementation,
* see {@link IterationStrategy} javadoc for a method skeleton.
*
* @param srcPts the array containing the source point coordinates.
* @param srcOff the offset to the first point to be transformed in the source array.
* @param dstPts the array into which the transformed point coordinates are returned.
* May be the same as {@code srcPts}.
* @param dstOff the offset to the location of the first transformed point that is stored in the destination array.
* @param numPts the number of point objects to be transformed.
* @throws TransformException if a point cannot be transformed. Some implementations will stop at the first failure,
* wile some other implementations will fill the untransformable points with {@linkplain Double#NaN} values,
* continue and throw the exception only at end. Implementations that fall in the latter case should set the
* {@linkplain TransformException#getLastCompletedTransform last completed transform} to {@code this}.
*/
@Override
public void transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts)
throws TransformException
{
if (numPts <= 0) {
return;
}
/*
* If case of overlapping source and destination arrays, determine if we should iterate
* over the coordinates in ascending/descending order or copy the data in a temporary buffer.
* The "offFinal" and "dstFinal" variables will be used only in the BUFFER_TARGET case.
*/
double[] dstFinal = null;
int offFinal = 0;
int srcInc = getSourceDimensions();
int dstInc = getTargetDimensions();
if (srcPts == dstPts) {
switch (IterationStrategy.suggest(srcOff, srcInc, dstOff, dstInc, numPts)) {
case ASCENDING: {
break;
}
case DESCENDING: {
srcOff += (numPts-1) * srcInc; srcInc = -srcInc;
dstOff += (numPts-1) * dstInc; dstInc = -dstInc;
break;
}
default: // Following should alway work even for unknown cases.
case BUFFER_SOURCE: {
srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*srcInc);
srcOff = 0;
break;
}
case BUFFER_TARGET: {
dstFinal = dstPts; dstPts = new double[numPts * dstInc];
offFinal = dstOff; dstOff = 0;
break;
}
}
}
/*
* Now apply the coordinate transformation, invoking the user-overrideable method
* for each individual point. In case of failure, we will set the coordinates to NaN
* and continue with other points, up to some maximal number of failures.
*/
TransformException failure = null;
int failureCount = 0; // Count coordinates, not coordinate tuples.
int blockStart = 0;
do {
try {
transform(srcPts, srcOff, dstPts, dstOff, false);
} catch (TransformException exception) {
/*
* If an exception occurred, let it propagate if we reached the maximum amount
* of exceptions we try to handle. We do NOT invoke setLastCompletedTransform
* in this case since we gave up.
*/
failureCount += Math.abs(srcInc);
if (failureCount > MAXIMUM_FAILURES) {
throw failure;
}
/*
* Otherwise set the coordinate values to NaN and count the number of exceptions,
* so we know when to give up if there is too much of them. The first exception
* will be propagated at the end of this method.
*/
Arrays.fill(dstPts, dstOff, dstOff + Math.abs(dstInc), Double.NaN);
if (failure == null) {
failure = exception; // Keep only the first failure.
blockStart = srcOff;
} else {
failure.addSuppressed(exception);
if (Math.abs(srcOff - blockStart) > MAXIMUM_BUFFER_SIZE) {
failureCount = 0; // We started a new block of coordinates.
blockStart = srcOff;
}
}
}
srcOff += srcInc;
dstOff += dstInc;
} while (--numPts != 0);
if (dstFinal != null) {
System.arraycopy(dstPts, 0, dstFinal, offFinal, dstPts.length);
}
/*
* If some points failed to be transformed, let the first exception propagate.
* But before doing so we declare that this transform has nevertheless be able
* to process all coordinate tuples, setting them to NaN when transform failed.
*/
if (failure != null) {
failure.setLastCompletedTransform(this);
throw failure;
}
}
/**
* Transforms a list of coordinate tuples. The default implementation delegates
* to {@link #transform(double[], int, double[], int, int)} using a temporary array of doubles.
*
* <h4>Implementation note</h4>
* See {@link IterationStrategy} javadoc for a method skeleton.
*
* @param srcPts the array containing the source point coordinates.
* @param srcOff the offset to the first point to be transformed in the source array.
* @param dstPts the array into which the transformed point coordinates are returned.
* May be the same as {@code srcPts}.
* @param dstOff the offset to the location of the first transformed point that is stored in the destination array.
* @param numPts the number of point objects to be transformed.
* @throws TransformException if a point cannot be transformed. Some implementations will stop at the first failure,
* wile some other implementations will fill the un-transformable points with {@link Float#NaN} values,
* continue and throw the exception only at end. Implementations that fall in the latter case should set
* the {@linkplain TransformException#getLastCompletedTransform last completed transform} to {@code this}.
*/
@Override
public void transform(float[] srcPts, int srcOff, float[] dstPts, int dstOff, int numPts)
throws TransformException
{
if (numPts <= 0) {
return;
}
final int dimSource = getSourceDimensions();
final int dimTarget = getTargetDimensions();
final int dimLargest = Math.max(dimSource, dimTarget);
/*
* Computes the number of points in the buffer in such a way that the buffer
* can contain at least one point and is not larger than MAXIMUM_BUFFER_SIZE
* (except if a single point is larger than that).
*/
int numBufferedPts = numPts;
int bufferSize = numPts * dimLargest;
if (bufferSize > MAXIMUM_BUFFER_SIZE) {
numBufferedPts = Math.max(1, MAXIMUM_BUFFER_SIZE / dimLargest);
bufferSize = numBufferedPts * dimLargest;
}
/*
* We need to check if writing the transformed coordinates in the same array as the source
* coordinates will cause an overlapping problem. However, we can consider the whole buffer as
* if it was a single coordinate tuple with a very large dimension. Doing so increase the chances
* that IterationStrategy.suggest(...) doesn't require us another buffer (hint: the -1 in
* suggest(...) mathematic matter and reflect the contract saying that the input coordinates
* must be fully read before the output coordinates is written - which is the behavior we get
* with our buffer).
*/
int srcInc = dimSource * numBufferedPts;
int dstInc = dimTarget * numBufferedPts;
int srcStop = srcInc; // src|dstStop will be used and modified in the do..while loop later.
int dstStop = dstInc;
if (srcPts == dstPts) {
final int numPass = (numPts + numBufferedPts-1) / numBufferedPts; // Round toward higher integer.
switch (IterationStrategy.suggest(srcOff, srcInc, dstOff, dstInc, numPass)) {
case ASCENDING: {
break;
}
case DESCENDING: {
final int delta = numPts - numBufferedPts;
srcOff += delta * dimSource;
dstOff += delta * dimTarget;
srcInc = -srcInc;
dstInc = -dstInc;
break;
}
default: {
srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts*dimSource);
srcOff = 0;
break;
}
}
}
/*
* Computes the offset of the first source coordinates in the buffer. The offset of the
* first destination coordinates will always be zero. We compute the source offset in
* such a way that the default transform(double[],int,double[],int,int) implementation
* should never needs to copy the source coordinates in yet another temporary buffer.
* We will verify that with an assert statement inside the do loop.
*/
final int bufferedSrcOff = (dimSource >= dimTarget) ? 0 : dstStop - srcStop;
final double[] buffer = new double[bufferSize];
TransformException failure = null;
do {
if (numPts < numBufferedPts) {
numBufferedPts = numPts;
srcStop = numPts * dimSource;
dstStop = numPts * dimTarget;
if (srcInc < 0) {
/*
* If we were applying IterationStrategy.DESCENDING, then srcOff and dstOff
* may be negative at this point because the last pass may not fill all the
* buffer space. We need to apply the correction below.
*/
srcOff -= (srcStop + srcInc);
dstOff -= (dstStop + dstInc);
}
}
for (int i=0; i<srcStop; i++) {
buffer[bufferedSrcOff + i] = srcPts[srcOff + i];
}
assert !IterationStrategy.suggest(bufferedSrcOff, dimSource, 0, dimTarget, numBufferedPts).needBuffer;
try {
transform(buffer, bufferedSrcOff, buffer, 0, numBufferedPts);
} catch (TransformException exception) {
/*
* If an exception occurred but the transform nevertheless declares having been
* able to process all coordinate tuples (setting to NaN those that cannot be
* transformed), we will keep the first exception (to be propagated at the end
* of this method) and continue. Otherwise we will stop immediately.
*/
if (exception.getLastCompletedTransform() != this) {
throw exception;
} else if (failure == null) {
failure = exception; // Keep only the first exception.
} else {
failure.addSuppressed(exception);
}
}
for (int i=0; i<dstStop; i++) {
dstPts[dstOff + i] = (float) buffer[i];
}
srcOff += srcInc;
dstOff += dstInc;
numPts -= numBufferedPts;
} while (numPts != 0);
if (failure != null) {
throw failure;
}
}
/**
* Transforms a list of coordinate tuples. The default implementation delegates
* to {@link #transform(double[], int, double[], int, int)} using a temporary array of doubles.
*
* @param srcPts the array containing the source point coordinates.
* @param srcOff the offset to the first point to be transformed in the source array.
* @param dstPts the array into which the transformed point coordinates are returned.
* @param dstOff the offset to the location of the first transformed point that is stored in the destination array.
* @param numPts the number of point objects to be transformed.
* @throws TransformException if a point cannot be transformed. Some implementations will stop at the first failure,
* wile some other implementations will fill the untransformable points with {@linkplain Float#NaN} values,
* continue and throw the exception only at end. Implementations that fall in the latter case should set the
* {@linkplain TransformException#getLastCompletedTransform last completed transform} to {@code this}.
*/
@Override
public void transform(final double[] srcPts, int srcOff,
final float [] dstPts, int dstOff, int numPts)
throws TransformException
{
if (numPts <= 0) {
return;
}
final int dimSource = getSourceDimensions();
final int dimTarget = getTargetDimensions();
int numBufferedPts = numPts;
int bufferSize = numPts * dimTarget;
if (bufferSize > MAXIMUM_BUFFER_SIZE) {
numBufferedPts = Math.max(1, MAXIMUM_BUFFER_SIZE / dimTarget);
bufferSize = numBufferedPts * dimTarget;
}
int srcLength = numBufferedPts * dimSource;
int dstLength = numBufferedPts * dimTarget;
final double[] buffer = new double[bufferSize];
TransformException failure = null;
do {
if (numPts < numBufferedPts) {
numBufferedPts = numPts;
srcLength = numPts * dimSource;
dstLength = numPts * dimTarget;
}
try {
transform(srcPts, srcOff, buffer, 0, numBufferedPts);
} catch (TransformException exception) {
// Same comment as in transform(float[], ...,float[], ...)
if (exception.getLastCompletedTransform() != this) {
throw exception;
} else if (failure == null) {
failure = exception;
} else {
failure.addSuppressed(exception);
}
}
for (int i=0; i<dstLength; i++) {
dstPts[dstOff++] = (float) buffer[i];
}
srcOff += srcLength;
numPts -= numBufferedPts;
} while (numPts != 0);
if (failure != null) {
throw failure;
}
}
/**
* Transforms a list of coordinate tuples. The default implementation delegates
* to {@link #transform(double[], int, double[], int, int)} using a temporary array of doubles
* if necessary.
*
* @param srcPts the array containing the source point coordinates.
* @param srcOff the offset to the first point to be transformed in the source array.
* @param dstPts the array into which the transformed point coordinates are returned.
* @param dstOff the offset to the location of the first transformed point that is stored in the destination array.
* @param numPts the number of point objects to be transformed.
* @throws TransformException if a point cannot be transformed. Some implementations will stop at the first failure,
* wile some other implementations will fill the untransformable points with {@linkplain Double#NaN} values,
* continue and throw the exception only at end. Implementations that fall in the latter case should set the
* {@linkplain TransformException#getLastCompletedTransform last completed transform} to {@code this}.
*/
@Override
public void transform(final float [] srcPts, int srcOff,
final double[] dstPts, int dstOff, int numPts)
throws TransformException
{
if (numPts <= 0) {
return;
}
final int dimSource = getSourceDimensions();
final int dimTarget = getTargetDimensions();
if (dimSource == dimTarget) {
final int n = numPts * dimSource;
for (int i=0; i<n; i++) {
dstPts[dstOff + i] = srcPts[srcOff + i];
}
transform(dstPts, dstOff, dstPts, dstOff, numPts);
return;
}
int numBufferedPts = numPts;
int bufferSize = numPts * dimSource;
if (bufferSize > MAXIMUM_BUFFER_SIZE) {
numBufferedPts = Math.max(1, MAXIMUM_BUFFER_SIZE / dimSource);
bufferSize = numBufferedPts * dimSource;
}
int srcLength = numBufferedPts * dimSource;
int dstLength = numBufferedPts * dimTarget;
final double[] buffer = new double[bufferSize];
TransformException failure = null;
do {
if (numPts < numBufferedPts) {
numBufferedPts = numPts;
srcLength = numPts * dimSource;
dstLength = numPts * dimTarget;
}
for (int i=0; i<srcLength; i++) {
buffer[i] = srcPts[srcOff++];
}
try {
transform(buffer, 0, dstPts, dstOff, numBufferedPts);
} catch (TransformException exception) {
// Same comment as in transform(float[], ...,float[], ...)
if (exception.getLastCompletedTransform() != this) {
throw exception;
} else if (failure == null) {
failure = exception;
} else {
failure.addSuppressed(exception);
}
}
dstOff += dstLength;
numPts -= numBufferedPts;
} while (numPts != 0);
if (failure != null) {
throw failure;
}
}
/**
* Gets the derivative of this transform at a point.
* The default implementation performs the following steps:
*
* <ul>
* <li>Ensure that the {@code point} dimension is equal to this math transform
* {@linkplain #getSourceDimensions() source dimensions}.</li>
* <li>Copy the coordinates in a temporary array and pass that array to the
* {@link #transform(double[], int, double[], int, boolean)} method,
* with the {@code derivate} boolean argument set to {@code true}.</li>
* <li>If the latter method returned a non-null matrix, returns that matrix.
* Otherwise throws {@link TransformException}.</li>
* </ul>
*
* @param point the coordinate tuple where to evaluate the derivative.
* @return the derivative at the specified point (never {@code null}).
* @throws NullPointerException if the derivative depends on coordinates and {@code point} is {@code null}.
* @throws MismatchedDimensionException if {@code point} does not have the expected dimension.
* @throws TransformException if the derivative cannot be evaluated at the specified point.
*/
@Override
public Matrix derivative(final DirectPosition point) throws TransformException {
final int dimSource = getSourceDimensions();
final double[] coordinates = point.getCoordinate();
if (coordinates.length != dimSource) {
throw mismatchedDimension("point", dimSource, coordinates.length);
}
final Matrix derivative = transform(coordinates, 0, null, 0, true);
if (derivative == null) {
throw new TransformException(Resources.format(Resources.Keys.CanNotComputeDerivative));
}
return derivative;
}
/**
* Returns the inverse transform of this object. The default implementation returns
* {@code this} if this transform is an {@linkplain #isIdentity() identity} transform,
* or throws an exception otherwise. Subclasses should override this method.
*
* <h4>Implementation note</h4>
* The {@link Inverse} inner class can be used as a base for inverse transform implementations.
*/
@Override
public MathTransform inverse() throws NoninvertibleTransformException {
if (isIdentity()) {
return this;
}
throw new NoninvertibleTransformException(Resources.format(Resources.Keys.NonInvertibleTransform));
}
/**
* Returns {@code true} if {@code tr1} is the inverse of {@code tr2}.
* If this method is unsure, it conservatively returns {@code false}.
* The transform that may be inverted is {@code tr1}.
*
* @param tr1 the transform to inverse.
* @param tr2 the transform that may be the inverse of {@code tr1}.
* @return whether this transform is the inverse of the given transform. If unsure, {@code false}.
*/
static boolean isInverseEquals(MathTransform tr1, final MathTransform tr2) {
if (tr1.getSourceDimensions() != tr2.getTargetDimensions() ||
tr1.getTargetDimensions() != tr2.getSourceDimensions())
{
return false;
}
if ((tr1 instanceof LinearTransform) != (tr2 instanceof LinearTransform)) {
// For avoiding creation of inverse transform for a result that should be false.
return false;
}
try {
tr1 = tr1.inverse();
} catch (NoninvertibleTransformException e) {
Logging.recoverableException(LOGGER, AbstractMathTransform.class, "isInverseEquals", e);
return false;
}
if (tr1 instanceof LenientComparable) {
return ((LenientComparable) tr1).equals(tr2, ComparisonMode.APPROXIMATE);
}
if (tr2 instanceof LenientComparable) {
return ((LenientComparable) tr2).equals(tr1, ComparisonMode.APPROXIMATE);
}
return tr1.equals(tr2);
}
/**
* Concatenates or pre-concatenates in an optimized way this math transform with the given one, if possible.
* If an optimization is possible, a new math transform is created to perform the combined transformation.
* The {@code applyOtherFirst} value determines the transformation order as bellow:
*
* <ul>
* <li>If {@code applyOtherFirst} is {@code true}, then transforming a point
* <var>p</var> by the combined transform is equivalent to first transforming
* <var>p</var> by {@code other} and then transforming the result by {@code this}.</li>
* <li>If {@code applyOtherFirst} is {@code false}, then transforming a point
* <var>p</var> by the combined transform is equivalent to first transforming
* <var>p</var> by {@code this} and then transforming the result by {@code other}.</li>
* </ul>
*
* If no optimization is available for the combined transform, then this method returns {@code null}.
*
* @param applyOtherFirst {@code true} if the transformation order is {@code other} followed by {@code this}, or
* {@code false} if the transformation order is {@code this} followed by {@code other}.
* @param other the other math transform to (pre-)concatenate with this transform.
* @param factory the factory which is (indirectly) invoking this method, or {@code null} if none.
* @return the math transforms combined in an optimized way, or {@code null} if no such optimization is available.
* @throws FactoryException if an error occurred while combining the transforms.
*
* @since 0.8
*
* @deprecated Replaced by {@link #tryConcatenate(Joiner)}.
* See <a href="https://issues.apache.org/jira/browse/SIS-595">SIS-595</a>.
*/
@Deprecated(forRemoval=true, since="1.5")
protected MathTransform tryConcatenate(boolean applyOtherFirst, MathTransform other, MathTransformFactory factory)
throws FactoryException
{
MathTransform first = this;
if (applyOtherFirst) {
first = other;
other = this;
}
final var context = new Joiner(List.of(first, other), applyOtherFirst ? 1 : 0, ReferencingUtilities.nonNull(factory));
tryConcatenate(context);
return context.replacement;
}
/**
* Concatenates or pre-concatenates in an optimized way this math transform with its neighbor, if possible.
* If an optimization is possible, a new math transform is created to perform the combined transformation.
* If no optimization is available, the concatenation will be prepared by {@link DefaultMathTransformFactory}
* using a generic implementation.
*
* <p>The default implementation checks if a neighbor transform is the inverse of this transform. This method is
* ought to be overridden by subclasses capable of concatenating some combination of transforms in a special way.
* {@link LinearTransform} implementations do not need to override this method because matrix multiplications
* will be handled automatically. This method does not need to handle the {@link #isIdentity()} case neither.</p>
*
* @param context information about the neighbor transforms, and the object where to set the result.
* @throws FactoryException if an error occurred while combining the transforms.
*
* @see DefaultMathTransformFactory#createConcatenatedTransform(MathTransform, MathTransform)
*
* @since 1.5
*/
protected void tryConcatenate(final Joiner context) throws FactoryException {
int relativeIndex = +1;
do {
final MathTransform other = context.getTransform(relativeIndex).orElse(null);
if (other != null && isInverseEquals(this, other)) {
int dimension = (relativeIndex < 0) ? getTargetDimensions() : getSourceDimensions();
context.replace(relativeIndex, MathTransforms.identity(dimension));
break;
}
} while ((relativeIndex = -relativeIndex) < 0);
}
/**
* Information about how to concatenate the enclosing transform with the previous or next transforms.
* Those information give an opportunity for {@link AbstractMathTransform} implementations to replace
* the default concatenation algorithm by an optimized alternative. Examples of optimizations may be:
* detecting when two consecutive non-linear operations are equivalent to a third non-linear operation,
* or rewriting and moving linear transforms before or after the enclosing transform (such moves can
* cause two linear transforms to become consecutive, which enable their efficient merging).
*
* <h2>Usage</h2>
* Implementations can inspect the surrounding transforms by calls to {@link #getTransform(int)} or
* {@link #isLinear(int, boolean)}. If they decide to replace the enclosing transform and a neighbor
* transform by a single optimized transform, they can call {@link #replace(int, MathTransform)}.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.5
*
* @see #tryConcatenate(Joiner)
*
* @since 1.5
*/
protected static final class Joiner {
/**
* Partial or complete list of coordinate operations to concatenate.
* This list may contain only the neighbor steps.
*/
private final List<MathTransform> steps;
/**
* Index of the enclosing transform in the {@link #steps} list.
*/
private final int indexOfThis;
/**
* The factory to use for creating the concatenated transform. Never null.
*/
public final MathTransformFactory factory;
/**
* The concatenated transform to use as a replacement, or {@code null} if none.
*/
MathTransform replacement;
/**
* -1 if {@link #replacement} replaces also the previous transform, or
* +1 if it replaces also the next transform.
*/
int replacementBound;
/**
* Creates new concatenation information.
*
* @param steps partial or complete list of coordinate operations to concatenate.
* @param indexOfThis index of the enclosing transform in the {@code steps} list.
* @param factory the factory to use for creating the concatenated transform.
*/
Joiner(final List<MathTransform> steps, final int indexOfThis, final MathTransformFactory factory) {
this.factory = factory;
this.steps = steps;
this.indexOfThis = indexOfThis;
}
/**
* Returns the transform before or after the enclosing transform. By definition, invoking this method
* with a relative index of 0 always returns the enclosing transform. Invoking this method with other
* values may or may not return a transform, because {@code Joiner} may have information about only
* a subset of the transform steps. If present, a relative index of -1 returns the previous transform
* while a relative index of +1 returns the next transform.
*
* @param relativeIndex index of the transform to get, relatively to the enclosing transform.
* @return the requested transform. An empty value does not necessarily mean that the transform does not exist.
*/
public Optional<MathTransform> getTransform(int relativeIndex) {
relativeIndex += indexOfThis;
if (relativeIndex >= 0 && relativeIndex < steps.size()) {
return Optional.of(steps.get(relativeIndex));
} else {
return Optional.empty();
}
}
/**
* If a neighbor transform is linear and located between a transform and its inverse, returns it as a matrix.
* This method checks for a sequence of <var>forward</var> → <var>affine</var> → <var>reverse</var> transforms,
* where the <var>forward</var> and <var>reverse</var> steps are the inverse of each other.
* If such sequence is found, the matrix of the <var>affine</var> step is returned.
*
* @param relativeIndex relative index of the affine step.
* @return the matrix of the affine transform step if it is located
* between transforms that are the inverse of each other.
*/
public Optional<Matrix> getMiddleMatrix(final int relativeIndex) {
if (Math.abs(relativeIndex) == 1) {
final MathTransform affine = getTransform(relativeIndex).orElse(null);
final List<MathTransform> subSteps = MathTransforms.getSteps(affine);
if (subSteps.size() == 2) {
final int oi = Math.max(0, relativeIndex);
final MathTransform inverse = subSteps.get(oi);
if (isInverseEquals(steps.get(indexOfThis), inverse)) {
return Optional.ofNullable(MathTransforms.getMatrix(subSteps.get(oi ^ 1)));
}
}
}
return Optional.empty();
}
/**
* Tests whether the transform before or after the enclosing transform is linear.
* Invoking this method is equivalent to invoking {@link #getTransform(int)} and testing whether
* {@link MathTransforms#getMatrix(MathTransform)} returns a non-null value for that transform.
*
* @param relativeIndex index of the transform to test, relatively to the enclosing transform.
* @param ifUnknown value to return if the math transform at the given index is unknown.
* @return whether the transform is linear, or {@code ifNone} if unknown.
*/
public boolean isLinear(int relativeIndex, boolean ifUnknown) {
relativeIndex += indexOfThis;
if (relativeIndex < 0 || relativeIndex >= steps.size()) {
return ifUnknown;
}
MathTransform step = steps.get(relativeIndex);
return (step instanceof LinearTransform) || (step instanceof AffineTransform);
}
/**
* Requests to replace the enclosing transform and neighbor transforms by the given transform.
* The {@code bound} argument specifies which neighbors are replaced by the specified transform:
*
* <ul class="verbose">
* <li>If -1, then transforming a point <var>p</var> by {@code concatenation} shall be equivalent
* to first transforming <var>p</var> by {@code getTransform(-1)}, and then transforming the
* result by {@code getTransform(0)}.</li>
* <li>If +1, then transforming a point <var>p</var> by {@code concatenation} shall be equivalent
* to first transforming <var>p</var> by {@code getTransform(0)}, and then transforming the
* result by {@code getTransform(+1)}.</li>
* <li>If 0, then only the enclosing transform is replaced. Neighbor are unchanged.</li>
* </ul>
*
* This method can be invoked only once per {@code Joiner} instance.
*
* @param bound relative index of first (if negative) or last (if positive) transform to replace.
* @param concatenation the transform to use instead of the enclosing and neighbor ones.
* @throws IllegalStateException if a {@code replace(…)} method has already been invoked.
*/
public void replace(final int bound, final MathTransform concatenation) {
if (replaced()) {
throw new IllegalStateException(Errors.format(Errors.Keys.AlreadyInitialized_1, "concatenation"));
}
ArgumentChecks.ensureNonNull("concatenation", concatenation);
ArgumentChecks.ensureBetween("bound", -1, +1, bound);
replacement = concatenation;
replacementBound = bound;
}
/**
* {@return whether a concatenated transform has been specified}.
* This is initially {@code false}, and become {@code true} after a {@code replace(…)} call.
*/
public boolean replaced() {
return replacement != null;
}
}
/**
* Returns a hash value for this transform. This method invokes {@link #computeHashCode()}
* when first needed and caches the value for future invocations. Subclasses shall override
* {@code computeHashCode()} instead of this method.
*
* @return the hash code value. This value may change between different execution of the Apache SIS library.
*/
@Override
public final int hashCode() { // No need to synchronize; ok if invoked twice.
int hash = hashCode;
if (hash == 0) {
hash = computeHashCode();
if (hash == 0) {
hash = -1;
}
hashCode = hash;
}
assert hash == -1 || hash == computeHashCode() : this;
return hash;
}
/**
* Computes a hash value for this transform. This method is invoked by {@link #hashCode()} when first needed.
*
* @return the hash code value. This value may change between different execution of the Apache SIS library.
*/
protected int computeHashCode() {
return getClass().hashCode() + getSourceDimensions() + 31 * getTargetDimensions();
}
/**
* Compares the specified object with this math transform for strict equality.
* This method is implemented as below (omitting assertions):
*
* {@snippet lang="java" :
* return equals(other, ComparisonMode.STRICT);
* }
*
* @param object the object to compare with this transform.
* @return {@code true} if the given object is a transform of the same class and using the same parameter values.
* @throws AssertionError if assertions are enabled and the objects are equal but their hash codes are different.
*/
@Override
@ArgumentCheckByAssertion
public final boolean equals(final Object object) {
final boolean eq = equals(object, ComparisonMode.STRICT);
// If objects are equal, then they must have the same hash code value.
assert !eq || computeHashCode() == ((AbstractMathTransform) object).computeHashCode() : this;
return eq;
}
/**
* Compares the specified object with this math transform for equality.
* Two math transforms are considered equal if, given identical source positions, their
* {@linkplain #transform(DirectPosition,DirectPosition) transformed} positions would be
* equal or {@link ComparisonMode#APPROXIMATE approximately} equal.
* This method may conservatively returns {@code false} if unsure.
*
* <p>The default implementation returns {@code true} if the following conditions are met:</p>
* <ul>
* <li>{@code object} is an instance of the same class as {@code this}. We require the
* same class because there is no interface for the various kinds of transform.</li>
* <li>If the hash code value has already been {@linkplain #computeHashCode() computed} for both
* instances, their values are the same <i>(opportunist performance enhancement)</i>.</li>
* <li>The {@linkplain #getContextualParameters() contextual parameters} are equal according
* the given comparison mode.</li>
* </ul>
*
* @param object the object to compare with this transform.
* @param mode the strictness level of the comparison. Default to {@link ComparisonMode#STRICT STRICT}.
* @return {@code true} if the given object is considered equals to this math transform.
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
/*
* Do not check 'object==this' here, since this
* optimization is usually done in subclasses.
*/
if (object != null && getClass() == object.getClass()) {
final AbstractMathTransform that = (AbstractMathTransform) object;
/*
* If the classes are the same, then the hash codes should be computed in the same way. Since those
* codes are cached, this is an efficient way to quickly check if the two objects are different.
*/
if (!mode.isApproximate()) {
final int tc = hashCode;
if (tc != 0) {
final int oc = that.hashCode;
if (oc != 0 && tc != oc) {
return false;
}
}
}
// See the policy documented in the LenientComparable javadoc.
if (mode.isIgnoringMetadata()) {
return true;
}
/*
* We do not compare getParameters() because they usually duplicate the internal fields.
* Contextual parameters, on the other hand, typically contain new information.
*/
return Utilities.deepEquals(this.getContextualParameters(),
that.getContextualParameters(), mode);
}
return false;
}
/**
* Given a transformation chain, replaces the elements around {@code transforms.get(index)} transform by
* alternative objects to use when formatting WKT. The replacement is performed in-place in the given list.
*
* <p>This method shall replace only the previous element and the few next elements that need
* to be changed as a result of the previous change. This method is not expected to continue
* the iteration after the changes that are of direct concern to this object.</p>
*
* <p>This method is invoked only by {@link ConcatenatedTransform#getPseudoSteps()} in order to
* get the {@link ParameterValueGroup} of a map projection, or to format a {@code PROJCS} WKT.</p>
*
* @param transforms the full chain of concatenated transforms.
* @param index the index of this transform in the {@code transforms} chain.
* @param inverse always {@code false}, except if we are formatting the inverse transform.
* @return index of this transform in the {@code transforms} chain after processing.
*
* @see ConcatenatedTransform#getPseudoSteps()
*/
int beforeFormat(final List<Object> transforms, final int index, final boolean inverse) {
assert unwrap(transforms.get(index), inverse) == this;
final ContextualParameters parameters = getContextualParameters();
return (parameters != null) ? parameters.beforeFormat(transforms, index, inverse) : index;
}
/**
* Formats the inner part of a <i>Well Known Text</i> version 1 (WKT 1) element.
* The default implementation formats all parameter values returned by {@link #getParameterValues()}.
* The parameter group name is used as the math transform name.
*
* <h4>Compatibility note</h4>
* {@code Param_MT} is defined in the WKT 1 specification only.
* If the {@linkplain Formatter#getConvention() formatter convention} is set to WKT 2,
* then this method silently uses the WKT 1 convention without raising an error
* (unless this {@code MathTransform} cannot be formatted as valid WKT 1 neither).
*
* @param formatter the formatter to use.
* @return the WKT element name, which is {@code "Param_MT"} in the default implementation.
*
* @see DefaultMathTransformFactory#createFromWKT(String)
*/
@Override
protected String formatTo(final Formatter formatter) {
WKTUtilities.appendParamMT(getParameterValues(), formatter);
return WKTKeywords.Param_MT;
}
/**
* Unwraps the given object if it is expected to be an inverse transform.
* This is used for assertions only.
*/
private static Object unwrap(final Object object, final boolean inverse) {
return inverse ? ((Inverse) object).inverse() : object;
}
/**
* Base class for implementations of inverse math transforms.
* Subclasses need to implement the {@link #inverse()} method.
*
* <h2>Serialization</h2>
* This object may or may not be serializable, at implementation choices.
* Most Apache SIS implementations are serializable, but the serialized objects are not guaranteed to be compatible
* with future SIS versions. Serialization should be used only for short term storage or RMI between applications
* running the same SIS version.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 1.0
* @since 0.5
*/
protected abstract static class Inverse extends AbstractMathTransform {
/**
* Constructs an inverse math transform.
*/
protected Inverse() {
}
/**
* Gets the dimension of input points.
* The default implementation returns the dimension of output points
* of the {@linkplain #inverse() inverse} math transform.
*
* @return {@inheritDoc}
*/
@Override
public int getSourceDimensions() {
return inverse().getTargetDimensions();
}
/**
* Gets the dimension of output points.
* The default implementation returns the dimension of input points
* of the {@linkplain #inverse() inverse} math transform.
*
* @return {@inheritDoc}
*/
@Override
public int getTargetDimensions() {
return inverse().getSourceDimensions();
}
/**
* Returns the ranges of coordinate values which can be used as inputs.
* The default implementation invokes {@code inverse().getDomain(criteria)}
* and transforms the returned envelope.
*
* @param criteria controls the definition of transform domain.
* @return estimation of a domain where this transform is considered numerically applicable.
* @throws TransformException if the domain cannot be estimated.
*
* @since 1.3
*/
@Override
public Optional<Envelope> getDomain(final DomainDefinition criteria) throws TransformException {
final MathTransform inverse = inverse();
if (inverse instanceof AbstractMathTransform) {
final Optional<Envelope> domain = ((AbstractMathTransform) inverse).getDomain(criteria);
return Optional.ofNullable(criteria.intersectOrTransform(domain.orElse(null), inverse));
}
return Optional.empty();
}
/**
* Gets the derivative of this transform at a point.
* The default implementation computes the inverse of the matrix
* returned by the {@linkplain #inverse() inverse} math transform.
*
* @return {@inheritDoc}
* @throws NullPointerException if the derivative depends on coordinates and {@code point} is {@code null}.
* @throws MismatchedDimensionException if {@code point} does not have the expected dimension.
* @throws TransformException if the derivative cannot be evaluated at the specified point.
*/
@Override
public Matrix derivative(DirectPosition point) throws TransformException {
if (point != null) {
point = this.transform(point, null);
}
return Matrices.inverse(inverse().derivative(point));
}
/**
* Returns the inverse of this math transform.
* The returned transform should be the enclosing math transform.
*
* @return the inverse of this transform.
*/
@Override
public abstract MathTransform inverse();
/**
* Tests whether this transform does not move any points.
* The default implementation delegates this tests to the {@linkplain #inverse() inverse} math transform.
*
* @return {@inheritDoc}
*/
@Override
public boolean isIdentity() {
return inverse().isIdentity();
}
/**
* {@inheritDoc}
*
* @return {@inheritDoc}
*/
@Override
protected int computeHashCode() {
return super.computeHashCode() + 31 * inverse().hashCode();
}
/**
* Compares the specified object with this inverse math transform for equality.
* The default implementation tests if {@code object} in an instance of the same class as {@code this},
* and if so compares their {@linkplain #inverse() inverse} {@code MathTransform}.
*
* @return {@inheritDoc}
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
if (object == this) {
return true; // Slight optimization
}
if (object != null && object.getClass() == getClass()) {
final MathTransform other = ((Inverse) object).inverse();
final MathTransform inverse = inverse();
if (inverse instanceof LenientComparable) {
return ((LenientComparable) inverse).equals(other, mode);
} else {
return inverse.equals(other);
}
} else {
return false;
}
}
/**
* Same work as {@link AbstractMathTransform#beforeFormat(List, int, boolean)}
* but with the knowledge that this transform is an inverse transform.
*/
@Override
int beforeFormat(final List<Object> transforms, final int index, final boolean inverse) {
final ContextualParameters parameters = getContextualParameters();
if (parameters != null) {
return parameters.beforeFormat(transforms, index, inverse);
} else {
final MathTransform inv = inverse();
if (inv instanceof AbstractMathTransform) {
return ((AbstractMathTransform) inv).beforeFormat(transforms, index, !inverse);
} else {
return index;
}
}
}
/**
* Formats the inner part of a <i>Well Known Text</i> version 1 (WKT 1) element.
* If this inverse math transform has any parameter values, then this method formats
* the WKT as in the {@linkplain AbstractMathTransform#formatWKT super-class method}.
* Otherwise this method formats the math transform as an {@code "Inverse_MT"} entity.
*
* <h4>Compatibility note</h4>
* {@code Param_MT} and {@code Inverse_MT} are defined in the WKT 1 specification only.
*
* @param formatter the formatter to use.
* @return the WKT element name, which is {@code "Param_MT"} or
* {@code "Inverse_MT"} in the default implementation.
*/
@Override
protected String formatTo(final Formatter formatter) {
final ParameterValueGroup parameters = getParameterValues();
if (parameters != null) {
WKTUtilities.appendParamMT(parameters, formatter);
return WKTKeywords.Param_MT;
} else {
formatter.newLine();
formatter.append((FormattableObject) inverse());
return WKTKeywords.Inverse_MT;
}
}
}
}