/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* This package contains documentation from OpenGIS specifications.
* OpenGIS consortium's work is fully acknowledged here.
*/
package org.geotools.referencing.operation.transform;
import java.io.Serializable;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.AffineTransform;
import java.awt.geom.IllegalPathStateException;
import javax.vecmath.SingularMatrixException;
import javax.measure.unit.NonSI;
import javax.measure.unit.SI;
import org.opengis.metadata.Identifier;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.Operation;
import org.opengis.referencing.operation.OperationMethod;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.geometry.DirectPosition;
import org.opengis.parameter.InvalidParameterValueException;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
import org.geotools.referencing.wkt.Formatter;
import org.geotools.referencing.wkt.Formattable;
import org.geotools.referencing.operation.matrix.XMatrix;
import org.geotools.referencing.operation.matrix.Matrix1;
import org.geotools.referencing.operation.matrix.GeneralMatrix;
import org.geotools.referencing.operation.matrix.MatrixFactory;
import org.geotools.geometry.GeneralDirectPosition;
import org.geotools.resources.geometry.ShapeUtilities;
import org.geotools.resources.Classes;
import org.geotools.util.Utilities;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.i18n.VocabularyKeys;
/**
* Provides a default implementation for most methods required by the {@link MathTransform}
* interface. {@code AbstractMathTransform} provides a convenient base class from which other
* transform classes can be easily derived. In addition, {@code AbstractMathTransform} implements
* methods required by the {@link MathTransform2D} interface, but <strong>does not</strong>
* implements {@code MathTransform2D}. Subclasses must declare {@code implements MathTransform2D}
* themself if they know to maps two-dimensional coordinate systems.
*
* @since 2.0
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @tutorial http://docs.codehaus.org/display/GEOTOOLS/Coordinate+Transformation+Parameters
*/
public abstract class AbstractMathTransform extends Formattable implements MathTransform {
/**
* Constructs a math transform.
*/
protected AbstractMathTransform() {
}
/**
* Returns a name for this math transform (never {@code null}). This convenience methods
* returns the name of the {@linkplain #getParameterDescriptors parameter descriptors} if
* any, or the short class name otherwise.
*
* @return A name for this math transform (never {@code null}).
*
* @since 2.5
*/
public String getName() {
final ParameterDescriptorGroup descriptor = getParameterDescriptors();
if (descriptor != null) {
final Identifier identifier = descriptor.getName();
if (identifier != null) {
final String code = identifier.getCode();
if (code != null) {
return code;
}
}
}
return Classes.getShortClassName(this);
}
/**
* Gets the dimension of input points.
*/
public abstract int getSourceDimensions();
/**
* Gets the dimension of output points.
*/
public abstract int getTargetDimensions();
/**
* Returns the parameter descriptors for this math transform, or {@code null} if unknow. This
* method is similar to {@link OperationMethod#getParameters}, except that {@code MathTransform}
* returns parameters in standard units (usually {@linkplain SI#METER meters} or
* {@linkplain NonSI#DEGREE_ANGLE decimal degrees}).
*
* @return The parameter descriptors for this math transform, or {@code null}.
*
* @see OperationMethod#getParameters
*/
public ParameterDescriptorGroup getParameterDescriptors() {
return null;
}
/**
* Returns the parameter values for this math transform, or {@code null} if unknow. This method
* is similar to {@link Operation#getParameterValues}, except that {@code MathTransform} returns
* parameters in standard units (usually {@linkplain SI#METER meters} or
* {@linkplain NonSI#DEGREE_ANGLE decimal degrees}). Since this method returns a copy of the
* parameter values, any change to a value will have no effect on this math transform.
*
* @return A copy of the parameter values for this math transform, or {@code null}.
*
* @see Operation#getParameterValues
*/
public ParameterValueGroup getParameterValues() {
return null;
}
/**
* Tests whether this transform does not move any points.
* The default implementation always returns {@code false}.
*/
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 dimension The wrong dimension.
* @param expected The expected dimension.
*/
private static String constructMessage(final String argument,
final int dimension,
final int expected)
{
return Errors.format(ErrorKeys.MISMATCHED_DIMENSION_$3, argument, dimension, expected);
}
/**
* Transforms the specified {@code ptSrc} and stores the result in {@code ptDst}.
* The default implementation invokes {@link #transform(double[],int,double[],int,int)}
* using a temporary array of doubles.
*
* @param ptSrc The specified coordinate point to be transformed.
* @param ptDst The specified coordinate point that stores the result of transforming
* {@code ptSrc}, or {@code null}.
* @return The coordinate point after transforming {@code ptSrc} and storing the result in
* {@code ptDst}.
* @throws MismatchedDimensionException If this transform doesn't map two-dimensional
* coordinate systems.
* @throws TransformException If the point can't be transformed.
*
* @see MathTransform2D#transform(Point2D,Point2D)
*/
public Point2D transform(final Point2D ptSrc, final Point2D ptDst) throws TransformException {
int dim;
if ((dim = getSourceDimensions()) != 2) {
throw new MismatchedDimensionException(constructMessage("ptSrc", 2, dim));
}
if ((dim = getTargetDimensions()) != 2) {
throw new MismatchedDimensionException(constructMessage("ptDst", 2, dim));
}
final double[] ord = new double[] {ptSrc.getX(), ptSrc.getY()};
this.transform(ord, 0, ord, 0, 1);
if (ptDst != null) {
ptDst.setLocation(ord[0], ord[1]);
return ptDst;
} else {
return new Point2D.Double(ord[0], ord[1]);
}
}
/**
* Transforms the specified {@code ptSrc} and stores the result in {@code ptDst}. The default
* implementation delegates to {@link #transform(double[],int,double[],int,int)}.
*/
public DirectPosition transform(final DirectPosition ptSrc, DirectPosition ptDst)
throws TransformException
{
int dimPoint = ptSrc.getDimension();
final int dimSource = getSourceDimensions();
final int dimTarget = getTargetDimensions();
if (dimPoint != dimSource) {
throw new MismatchedDimensionException(constructMessage("ptSrc", dimPoint, dimSource));
}
if (ptDst != null) {
dimPoint = ptDst.getDimension();
if (dimPoint != dimTarget) {
throw new MismatchedDimensionException(constructMessage("ptDst", dimPoint, dimTarget));
}
/*
* Transforms the coordinates using a temporary 'double[]' buffer,
* and copy 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, 1);
for (int i=dimTarget; --i>=0;) {
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 Geotools implementation, write directly into the
* 'ordinates' array.
*/
final GeneralDirectPosition destination;
ptDst = destination = new GeneralDirectPosition(dimTarget);
final double[] source;
if (dimSource <= dimTarget) {
source = destination.ordinates;
for (int i=dimSource; --i>=0;) {
source[i] = ptSrc.getOrdinate(i);
}
} else {
source = ptSrc.getCoordinate();
}
transform(source, 0, destination.ordinates, 0, 1);
}
return ptDst;
}
/**
* Transforms a list of coordinate point ordinal values. The default implementation
* invokes {@link #transform(double[],int,double[],int,int)} using a temporary array
* of doubles.
*/
public void transform(final float[] srcPts, final int srcOff,
final float[] dstPts, final int dstOff, final int numPts)
throws TransformException
{
final int dimSource = getSourceDimensions();
final int dimTarget = getTargetDimensions();
final double[] tmpPts = new double[numPts * Math.max(dimSource, dimTarget)];
for (int i=numPts*dimSource; --i>=0;) {
tmpPts[i] = srcPts[srcOff+i];
}
transform(tmpPts, 0, tmpPts, 0, numPts);
for (int i=numPts*dimTarget; --i>=0;) {
dstPts[dstOff+i] = (float) tmpPts[i];
}
}
/**
* Transforms a list of coordinate point ordinal values. The default implementation
* invokes {@link #transform(double[],int,double[],int,int)} using a temporary array
* of doubles.
*
* @since 2.5
*/
public void transform(final double[] srcPts, final int srcOff,
final float [] dstPts, final int dstOff, final int numPts)
throws TransformException
{
final int dimSource = getSourceDimensions();
final int dimTarget = getTargetDimensions();
final double[] tmpPts = new double[numPts * Math.max(dimSource, dimTarget)];
System.arraycopy(srcPts, srcOff, tmpPts, 0, numPts * dimSource);
transform(tmpPts, 0, tmpPts, 0, numPts);
for (int i=numPts*dimTarget; --i>=0;) {
dstPts[dstOff+i] = (float) tmpPts[i];
}
}
/**
* Transforms a list of coordinate point ordinal values. The default implementation
* delegates to {@link #transform(double[],int,double[],int,int)}.
*
* @since 2.5
*/
public void transform(final float [] srcPts, final int srcOff,
final double[] dstPts, final int dstOff, final int numPts)
throws TransformException
{
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);
} else {
final double[] tmpPts = new double[numPts*dimSource];
for (int i=tmpPts.length; --i>=0;) {
tmpPts[i] = srcPts[srcOff+i];
}
transform(tmpPts, 0, dstPts, 0, numPts);
}
}
/**
* Transform the specified shape. The default implementation computes
* quadratic curves using three points for each shape segments.
*
* @param shape Shape to transform.
* @return Transformed shape, or {@code shape} if this transform is the identity transform.
* @throws IllegalStateException if this transform doesn't map 2D coordinate systems.
* @throws TransformException if a transform failed.
*
* @see MathTransform2D#createTransformedShape(Shape)
*/
public Shape createTransformedShape(final Shape shape) throws TransformException {
return isIdentity() ? shape : createTransformedShape(shape, null, null, ShapeUtilities.PARALLEL);
}
/**
* Transforms a geometric shape. This method always copy transformed coordinates in a new
* object. The new object is usually a {@link GeneralPath}, but may also be a {@link Line2D}
* or a {@link QuadCurve2D} if such simplification is possible.
*
* @param shape The geometric shape to transform.
* @param preTransform An optional affine transform to apply <em>before</em> the
* transformation using {@code this}, or {@code null} if none.
* @param postTransform An optional affine transform to apply <em>after</em> the transformation
* using {@code this}, or {@code null} if none.
* @param orientation Base line of quadratic curves. Must be
* {@link ShapeUtilities#HORIZONTAL} or {@link ShapeUtilities#PARALLEL}).
*
* @return The transformed geometric shape.
* @throws MismatchedDimensionException if this transform doesn't is not two-dimensional.
* @throws TransformException If a transformation failed.
*
* @todo Use double precision when we will be allowed to target Java 6.
*/
final Shape createTransformedShape(final Shape shape,
final AffineTransform preTransform,
final AffineTransform postTransform,
final int orientation)
throws TransformException
{
int dim;
if ((dim=getSourceDimensions())!=2 || (dim=getTargetDimensions())!=2) {
throw new MismatchedDimensionException(constructMessage("shape", 2, dim));
}
final PathIterator it = shape.getPathIterator(preTransform);
final GeneralPath path = new GeneralPath(it.getWindingRule());
final Point2D.Float ctrl = new Point2D.Float();
final double[] buffer = new double[6];
double ax=0, ay=0; // Coordinate of the last point before transform.
double px=0, py=0; // Coordinate of the last point after transform.
for (; !it.isDone(); it.next()) {
switch (it.currentSegment(buffer)) {
default: {
throw new IllegalPathStateException();
}
case PathIterator.SEG_CLOSE: {
/*
* Closes the geometric shape and continues the loop. We use the 'continue'
* instruction here instead of 'break' because we don't want to execute the
* code after the switch (addition of transformed points into the path - there
* is no such point in a SEG_CLOSE).
*/
path.closePath();
continue;
}
case PathIterator.SEG_MOVETO: {
/*
* Transforms the single point and adds it to the path. We use the 'continue'
* instruction here instead of 'break' because we don't want to execute the
* code after the switch (addition of a line or a curve - there is no such
* curve to add here; we are just moving the cursor).
*/
ax = buffer[0];
ay = buffer[1];
transform(buffer, 0, buffer, 0, 1);
px = buffer[0];
py = buffer[1];
path.moveTo((float) px, (float) py);
continue;
}
case PathIterator.SEG_LINETO: {
/*
* Inserts a new control point at 'buffer[0,1]'. This control point will
* be initialised with coordinates in the middle of the straight line:
*
* x = 0.5*(x1+x2)
* y = 0.5*(y1+y2)
*
* This point will be transformed after the 'switch', which is why we use
* the 'break' statement here instead of 'continue' as in previous case.
*/
buffer[0] = 0.5*(ax + (ax=buffer[0]));
buffer[1] = 0.5*(ay + (ay=buffer[1]));
buffer[2] = ax;
buffer[3] = ay;
break;
}
case PathIterator.SEG_QUADTO: {
/*
* Replaces the control point in 'buffer[0,1]' by a new control point lying
* on the quadratic curve. Coordinates for a point in the middle of the curve
* can be computed with:
*
* x = 0.5*(ctrlx + 0.5*(x1+x2))
* y = 0.5*(ctrly + 0.5*(y1+y2))
*
* There is no need to keep the old control point because it was not lying
* on the curve.
*/
buffer[0] = 0.5*(buffer[0] + 0.5*(ax + (ax=buffer[2])));
buffer[1] = 0.5*(buffer[1] + 0.5*(ay + (ay=buffer[3])));
break;
}
case PathIterator.SEG_CUBICTO: {
/*
* Replaces the control point in 'buffer[0,1]' by a new control point lying
* on the cubic curve. Coordinates for a point in the middle of the curve
* can be computed with:
*
* x = 0.25*(1.5*(ctrlx1+ctrlx2) + 0.5*(x1+x2));
* y = 0.25*(1.5*(ctrly1+ctrly2) + 0.5*(y1+y2));
*
* There is no need to keep the old control point because it was not lying
* on the curve.
*
* NOTE: Le point calculé est bien sur la courbe, mais n'est pas
* nécessairement représentatif. Cet algorithme remplace les
* deux points de contrôles par un seul, ce qui se traduit par
* une perte de souplesse qui peut donner de mauvais résultats
* si la courbe cubique était bien tordue. Projeter une courbe
* cubique ne me semble pas être un problème simple, mais ce
* cas devrait être assez rare. Il se produira le plus souvent
* si on essaye de projeter un cercle ou une ellipse, auxquels
* cas l'algorithme actuel donnera quand même des résultats
* tolérables.
*/
buffer[0] = 0.25*(1.5*(buffer[0]+buffer[2]) + 0.5*(ax + (ax=buffer[4])));
buffer[1] = 0.25*(1.5*(buffer[1]+buffer[3]) + 0.5*(ay + (ay=buffer[5])));
buffer[2] = ax;
buffer[3] = ay;
break;
}
}
/*
* Applies the transform on the point in the buffer, and append the transformed points
* to the general path. Try to add them as a quadratic line, or as a straight line if
* the computed control point is colinear with the starting and ending points.
*/
transform(buffer, 0, buffer, 0, 2);
final Point2D ctrlPoint = ShapeUtilities.parabolicControlPoint(px, py,
buffer[0], buffer[1],
buffer[2], buffer[3],
orientation, ctrl);
px = buffer[2];
py = buffer[3];
if (ctrlPoint != null) {
assert ctrl == ctrlPoint;
path.quadTo(ctrl.x, ctrl.y, (float) px, (float) py);
} else {
path.lineTo((float) px, (float) py);
}
}
/*
* La projection de la forme géométrique est terminée. Applique
* une transformation affine si c'était demandée, puis retourne
* une version si possible simplifiée de la forme géométrique.
*/
if (postTransform != null) {
path.transform(postTransform);
}
return ShapeUtilities.toPrimitive(path);
}
/**
* Gets the derivative of this transform at a point. The default implementation always
* throw an exception. Subclasses that implement the {@link MathTransform2D} interface
* should override this method. Other subclasses should override
* {@link #derivative(DirectPosition)} instead.
*
* @param point The coordinate point where to evaluate the derivative.
* @return The derivative at the specified point as a 2×2 matrix.
* @throws MismatchedDimensionException if the input dimension is not 2.
* @throws TransformException if the derivative can't be evaluated at the specified point.
*
* @see MathTransform2D#derivative(Point2D)
*/
public Matrix derivative(final Point2D point) throws TransformException {
final int dimSource = getSourceDimensions();
if (dimSource != 2) {
throw new MismatchedDimensionException(constructMessage("point", 2, dimSource));
}
throw new TransformException(Errors.format(ErrorKeys.CANT_COMPUTE_DERIVATIVE));
}
/**
* Gets the derivative of this transform at a point. The default implementation
* ensure that {@code point} has a valid dimension. Next, it try to delegate
* the work to an other method:
*
* <ul>
* <li>If the input dimension is 2, then this method delegates the work to
* {@link #derivative(Point2D)}.</li>
* <li>If this object is an instance of {@link MathTransform1D}, then this
* method delegates the work to {@link MathTransform1D#derivative(double)
* derivative(double)}.</li>
* </ul>
*
* Otherwise, a {@link TransformException} is thrown.
*
* @param point The coordinate point where to evaluate the derivative.
* @return The derivative at the specified point (never {@code null}).
* @throws NullPointerException if the derivative dependents on coordinate
* and {@code point} is {@code null}.
* @throws MismatchedDimensionException if {@code point} doesn't have
* the expected dimension.
* @throws TransformException if the derivative can't be evaluated at the
* specified point.
*/
public Matrix derivative(final DirectPosition point) throws TransformException {
final int dimSource = getSourceDimensions();
if (point == null) {
if (dimSource == 2) {
return derivative((Point2D) null);
}
} else {
final int dimPoint = point.getDimension();
if (dimPoint != dimSource) {
throw new MismatchedDimensionException(constructMessage("point", dimPoint, dimSource));
}
if (dimSource == 2) {
if (point instanceof Point2D) {
return derivative((Point2D) point);
}
return derivative(new Point2D.Double(point.getOrdinate(0), point.getOrdinate(1)));
}
if (this instanceof MathTransform1D) {
return new Matrix1(((MathTransform1D) this).derivative(point.getOrdinate(0)));
}
}
throw new TransformException(Errors.format(ErrorKeys.CANT_COMPUTE_DERIVATIVE));
}
/**
* Creates the inverse transform of this object.
* The default implementation returns {@code this} if this transform is an identity
* transform, and throws a {@link NoninvertibleTransformException} otherwise. Subclasses
* should override this method.
*/
public MathTransform inverse() throws NoninvertibleTransformException {
if (isIdentity()) {
return this;
}
throw new NoninvertibleTransformException(Errors.format(ErrorKeys.NONINVERTIBLE_TRANSFORM));
}
/**
* Concatenates in an optimized way a {@link MathTransform} {@code other} to this
* {@code MathTransform}. A new math transform is created to perform the combined
* transformation. The {@code applyOtherFirst} value determine 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 the
* original transform {@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 the original transform {@code this} and then transforming
* the result by {@code other}.</li>
* </ul>
*
* If no special optimization is available for the combined transform, then this method
* returns {@code null}. In the later case, the concatenation will be prepared by
* {@link DefaultMathTransformFactory} using a generic {@link ConcatenatedTransform}.
*
* The default implementation always returns {@code null}. This method is ought to be
* overridden by subclasses capable of concatenating some combinaison of transforms in a
* special way. Examples are {@link ExponentialTransform1D} and {@link LogarithmicTransform1D}.
*
* @param other The math transform to apply.
* @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}.
* @return The combined math transform, or {@code null} if no optimized combined
* transform is available.
*/
MathTransform concatenate(final MathTransform other, final boolean applyOtherFirst) {
return null;
}
/**
* Returns a hash value for this transform.
*/
@Override
public int hashCode() {
return getSourceDimensions() + 37*getTargetDimensions();
}
/**
* Compares the specified object with this math transform for equality.
* The default implementation checks if {@code object} is an instance
* of the same class than {@code this} and use the same parameter descriptor.
* Subclasses should override this method in order to compare internal fields.
*
* @param object The object to compare with this transform.
* @return {@code true} if the given object is a transform of the same class
* and if, given identical source position, the
* {@linkplain #transform(DirectPosition,DirectPosition) transformed}
* position would be the equals.
*/
@Override
public boolean equals(final Object object) {
// Do not check 'object==this' here, since this
// optimization is usually done in subclasses.
if (object!=null && getClass().equals(object.getClass())) {
final AbstractMathTransform that = (AbstractMathTransform) object;
return Utilities.equals(this.getParameterDescriptors(),
that.getParameterDescriptors());
}
return false;
}
/**
* Format the inner part of a
* <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
* Known Text</cite> (WKT)</A> element. The default implementation formats all parameter values
* returned by {@link #getParameterValues}. The parameter group name is used as the math
* transform name.
*
* @param formatter The formatter to use.
* @return The WKT element name, which is {@code "PARAM_MT"} in the default implementation.
*/
@Override
protected String formatWKT(final Formatter formatter) {
final ParameterValueGroup parameters = getParameterValues();
if (parameters != null) {
formatter.append(formatter.getName(parameters.getDescriptor()));
formatter.append(parameters);
}
return "PARAM_MT";
}
/**
* Makes sure that an argument is non-null. This is a
* convenience method for subclass constructors.
*
* @param name Argument name.
* @param object User argument.
* @throws InvalidParameterValueException if {@code object} is null.
*/
protected static void ensureNonNull(final String name, final Object object)
throws InvalidParameterValueException
{
if (object == null) {
throw new InvalidParameterValueException(Errors.format(
ErrorKeys.NULL_ARGUMENT_$1, name), name, object);
}
}
/**
* Checks if source coordinates need to be copied before to apply the transformation.
* This convenience method is provided for {@code transform(...)} method implementation.
* This method make the following assumptions:
* <P>
* <UL>
* <LI>Coordinates will be iterated from lower index to upper index.</LI>
* <LI>Coordinates are read and writen in shrunk. For example (longitude,latitude,height)
* values for one coordinate are read together, and the transformed (x,y,z) values are
* written together only after.</LI>
* </UL>
* <P>
* However, this method does not assumes that source and target dimension are the same (in the
* special case where source and target dimension are always the same, a simplier and more
* efficient check is possible). The following example prepares a transformation from 2
* dimensional points to three dimensional points:
* <P>
* <blockquote><pre>
* public void transform(double[] srcPts, int srcOff,
* double[] dstPts, int dstOff, int numPts)
* {
* if (srcPts==dstPts && <strong>needCopy</strong>(srcOff, 2, dstOff, 3, numPts) {
* final double[] old = srcPts;
* srcPts = new double[numPts*2];
* System.arraycopy(old, srcOff, srcPts, 0, srcPts.length);
* srcOff = 0;
* }
* }</pre><blockquote>
*
* <strong>This method is for internal usage by the referencing module only. Do not use!
* It will be replaced by a different mechanism in a future GeoTools version.</strong>
*
* @param srcOff The offset in the source coordinate array.
* @param dimSource The dimension of input points.
* @param dstOff The offset in the destination coordinate array.
* @param dimTarget The dimension of output points.
* @param numPts The number of points to transform.
* @return {@code true} if the source coordinates should be copied before to apply the
* transformation in order to avoid an overlap with the destination array.
*/
protected static boolean needCopy(final int srcOff, final int dimSource,
final int dstOff, final int dimTarget, final int numPts)
{
if (numPts <= 1 || (srcOff >= dstOff && dimSource >= dimTarget)) {
/*
* Source coordinates are stored after target coordinates. If implementation
* read coordinates from lower index to upper index, then the destination will
* not overwrite the source coordinates, even if there is an overlaps.
*/
return false;
}
return srcOff < dstOff + numPts*dimTarget &&
dstOff < srcOff + numPts*dimSource;
}
/**
* Ensures that the specified longitude stay within ±π radians. This method
* is typically invoked after geographic coordinates are transformed. This method may add
* or substract some amount of 2π radians to <var>x</var>.
*
* @param x The longitude in radians.
* @return The longitude in the range ±π radians.
*/
protected static double rollLongitude(final double x) {
return x - (2*Math.PI) * Math.floor(x / (2*Math.PI) + 0.5);
}
/**
* Wraps the specified matrix in a Geotools implementation of {@link Matrix}. If {@code matrix}
* is already an instance of {@code XMatrix}, then it is returned unchanged. Otherwise, all
* elements are copied in a new {@code XMatrix} object.
*/
static XMatrix toXMatrix(final Matrix matrix) {
if (matrix instanceof XMatrix) {
return (XMatrix) matrix;
}
return MatrixFactory.create(matrix);
}
/**
* Wraps the specified matrix in a Geotools implementation of {@link Matrix}. If {@code matrix}
* is already an instance of {@code GeneralMatrix}, then it is returned unchanged. Otherwise,
* all elements are copied in a new {@code GeneralMatrix} object.
* <p>
* Before to use this method, check if a {@link XMatrix} (to be obtained with {@link #toXMatrix})
* would be suffisient. Use this method only if a {@code GeneralMatrix} is really necessary.
*/
static GeneralMatrix toGMatrix(final Matrix matrix) {
if (matrix instanceof GeneralMatrix) {
return (GeneralMatrix) matrix;
} else {
return new GeneralMatrix(matrix);
}
}
/**
* Inverts the specified matrix in place. If the matrix can't be inverted (for example
* because of a {@link SingularMatrixException}), then the exception is wrapped into a
* {@link NoninvertibleTransformException}.
*/
static Matrix invert(final Matrix matrix) throws NoninvertibleTransformException {
try {
final XMatrix m = toXMatrix(matrix);
m.invert();
return m;
} catch (SingularMatrixException exception) {
NoninvertibleTransformException e = new NoninvertibleTransformException(
Errors.format(ErrorKeys.NONINVERTIBLE_TRANSFORM));
e.initCause(exception);
throw e;
}
}
/**
* Default implementation for inverse math transform. This inner class is the inverse
* of the enclosing {@link MathTransform}. It is serializable only if the enclosing
* math transform is also serializable.
*
* @since 2.0
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
protected abstract class Inverse extends AbstractMathTransform implements Serializable {
/**
* Serial number for interoperability with different versions. This serial number is
* especilly important for inner classes, since the default {@code serialVersionUID}
* computation will not produce consistent results across implementations of different
* Java compiler. This is because different compilers may generate different names for
* synthetic members used in the implementation of inner classes. See:
*
* http://developer.java.sun.com/developer/bugParade/bugs/4211550.html
*/
private static final long serialVersionUID = 3528274816628012283L;
/**
* Constructs an inverse math transform.
*/
protected Inverse() {
}
/**
* Returns a name for this math transform (never {@code null}). The default implementation
* returns the direct transform name concatenated with localized flavor (when available)
* of "(Inverse transform)".
*
* @return A name for this math transform (never {@code null}).
*
* @since 2.5
*/
@Override
public String getName() {
return AbstractMathTransform.this.getName() +
" (" + Vocabulary.format(VocabularyKeys.INVERSE_TRANSFORM) + ')';
}
/**
* Gets the dimension of input points. The default
* implementation returns the dimension of output
* points of the enclosing math transform.
*/
public int getSourceDimensions() {
return AbstractMathTransform.this.getTargetDimensions();
}
/**
* Gets the dimension of output points. The default
* implementation returns the dimension of input
* points of the enclosing math transform.
*/
public int getTargetDimensions() {
return AbstractMathTransform.this.getSourceDimensions();
}
/**
* Gets the derivative of this transform at a point. The default
* implementation compute the inverse of the matrix returned by
* the enclosing math transform.
*/
@Override
public Matrix derivative(final Point2D point) throws TransformException {
return invert(AbstractMathTransform.this.derivative(this.transform(point, null)));
}
/**
* Gets the derivative of this transform at a point. The default
* implementation compute the inverse of the matrix returned by
* the enclosing math transform.
*/
@Override
public Matrix derivative(final DirectPosition point) throws TransformException {
return invert(AbstractMathTransform.this.derivative(this.transform(point, null)));
}
/**
* Returns the inverse of this math transform, which is the enclosing math transform.
* This behavior should not be changed since some implementation assume that the inverse
* of {@code this} is always {@code AbstractMathTransform.this}.
*/
@Override
public MathTransform inverse() {
return AbstractMathTransform.this;
}
/**
* Tests whether this transform does not move any points.
* The default implementation delegate this tests to the
* enclosing math transform.
*/
@Override
public boolean isIdentity() {
return AbstractMathTransform.this.isIdentity();
}
/**
* Returns a hash code value for this math transform.
*/
@Override
public int hashCode() {
return ~AbstractMathTransform.this.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 than {@code this}, and then test their enclosing math transforms.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
// Slight optimization
return true;
}
if (object instanceof Inverse) {
final Inverse that = (Inverse) object;
return Utilities.equals(this.inverse(), that.inverse());
} else {
return false;
}
}
/**
* Format the inner part of a
* <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
* Known Text</cite> (WKT)</A> element. If this inverse math transform
* has any parameter values, then this method format the WKT as in the
* {@linkplain AbstractMathTransform#formatWKT super-class method}. Otherwise
* this method format the math transform as an <code>"INVERSE_MT"</code> entity.
*
* @param formatter The formatter to use.
* @return The WKT element name, which is <code>"PARAM_MT"</code> or
* <code>"INVERSE_MT"</code> in the default implementation.
*/
@Override
protected String formatWKT(final Formatter formatter) {
final ParameterValueGroup parameters = getParameterValues();
if (parameters != null) {
formatter.append(formatter.getName(parameters.getDescriptor()));
formatter.append(parameters);
return "PARAM_MT";
} else {
formatter.append((Formattable) AbstractMathTransform.this);
return "INVERSE_MT";
}
}
}
}