/* * Geotools 2 - OpenSource mapping toolkit * (C) 2003, Geotools Project Managment Committee (PMC) * (C) 2001, Institut de Recherche pour le Développement * * 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; either * version 2.1 of the License, or (at your option) any later version. * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * This package contains documentation from OpenGIS specifications. * OpenGIS consortium's work is fully acknowledged here. */ package org.geotools.ct; // J2SE dependencies import java.awt.geom.AffineTransform; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.text.ParseException; import java.util.Locale; import javax.media.jai.IntegerSequence; import javax.media.jai.ParameterList; import org.geotools.cs.Projection; import org.geotools.ct.proj.Provider; import org.geotools.pt.Matrix; import org.geotools.resources.DescriptorNaming; import org.geotools.resources.XArray; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.resources.image.JAIUtilities; import org.geotools.units.Unit; import org.geotools.util.WeakHashSet; import org.opengis.ct.CT_MathTransform; import org.opengis.ct.CT_MathTransformFactory; import org.opengis.ct.CT_Parameter; import org.opengis.pt.PT_Matrix; import org.opengis.referencing.FactoryException; /** * Creates math transforms. MathTransformFactory is a low level * factory that is used to create {@link MathTransform} objects. Many high * level GIS applications will never need to use a MathTransformFactory * directly; they can use a {@link CoordinateTransformationFactory} instead. * However, the MathTransformFactory class is specified here, * since it can be used directly by applications that wish to transform other * types of coordinates (e.g. color coordinates, or image pixel coordinates). *

* A math transform is an object that actually does the work of applying * formulae to coordinate values. The math transform does not know or * care how the coordinates relate to positions in the real world. This * lack of semantics makes implementing MathTransformFactory * significantly easier than it would be otherwise. * * For example MathTransformFactory can create affine math * transforms. The affine transform applies a matrix to the coordinates * without knowing how what it is doing relates to the real world. So if * the matrix scales Z values by a factor of 1000, then it could * be converting meters into millimeters, or it could be converting kilometers * into meters. *

* Because math transforms have low semantic value (but high mathematical * value), programmers who do not have much knowledge of how GIS applications * use coordinate systems, or how those coordinate systems relate to the real * world can implement MathTransformFactory. * * The low semantic content of math transforms also means that they will be * useful in applications that have nothing to do with GIS coordinates. For * example, a math transform could be used to map color coordinates between * different color spaces, such as converting (red, green, blue) colors into * (hue, light, saturation) colors. *

* Since a math transform does not know what its source and target coordinate * systems mean, it is not necessary or desirable for a math transform object * to keep information on its source and target coordinate systems. * * @source $URL$ * @version $Id$ * @author OpenGIS (www.opengis.org) * @author Martin Desruisseaux * * @see org.opengis.ct.CT_MathTransformFactory * * @task REVISIT: Consider creating a set of package-privated methods * in AbstractMathTransform (for example * concatenante(MathTransformFactory, ...)) and moves some code in * AbstractMathTransform's sub-classes. It would simplify * MathTransformFactory but would introduces dependencies * to the factory right inside the abstract math transform. * * @deprecated Replaced by {@link org.geotools.referencing.operation.DefaultMathTransformFactory} * in the org.geotools.referencing.operation package. */ public class MathTransformFactory { /** * The default math transform factory. This factory * will be constructed only when first needed. */ private static MathTransformFactory DEFAULT; /** * The object to use for parsing Well-Known Text (WKT) strings. * Will be created only when first needed. */ private transient WKTParser parser; /** * A pool of math transform. This pool is used in order to * returns instance of existing math transforms when possible. */ static final WeakHashSet pool = new WeakHashSet(); /** * Identity transforms for dimensions ranging from to 0 to 7. Used only in order to * make the call to {@link #createIdentityTransform} faster and generate less garbages. * Elements in this array will be created only when first requested. */ private static final MathTransform[] identities = new MathTransform[8]; /** * List of registered math transforms. */ private final MathTransformProvider[] providers; /** * OpenGIS object returned by {@link #toOpenGIS}. * It may be a hard or a weak reference. */ private transient Object proxy; /** * Construct a factory using the specified providers. */ public MathTransformFactory(final MathTransformProvider[] providers) { this.providers = (MathTransformProvider[]) providers.clone(); } /** * Returns the default math transform factory. */ public static synchronized MathTransformFactory getDefault() { if (DEFAULT == null) { MathTransformProvider[] transforms = new MathTransformProvider[] { new MatrixTransform.Provider(), // Affine (default to 4x4) new GeocentricTransform.Provider(false), // Ellipsoid_To_Geocentric new GeocentricTransform.Provider(true), // Geocentric_To_Ellipsoid new MolodenskiTransform.Provider(), // Molodenski new AbridgedMolodenskiTransform.Provider(), // Abridged_Molodenski new ExponentialTransform1D.Provider(false), // Exponential new ExponentialTransform1D.Provider(true) // Logarithmic }; final int offset = transforms.length; final MathTransformProvider[] projections = Provider.getDefaults(); transforms = (MathTransformProvider[])XArray.resize(transforms, offset+projections.length); System.arraycopy(projections, 0, transforms, offset, projections.length); DEFAULT = new MathTransformFactory(transforms); } return DEFAULT; } /** * Creates an identity transform of the specified dimension. * * @param dimension The source and target dimension. * @return The identity transform. */ public MathTransform createIdentityTransform(final int dimension) { // No need to synchronize. MathTransform transform; if (dimension < identities.length) { transform = identities[dimension]; if (transform != null) { return transform; } } // Affine transform has one more row/column than dimension. transform = createAffineTransform(new Matrix(dimension+1)); if (dimension < identities.length) { identities[dimension] = transform; } return transform; } /** * Creates an affine transform from a matrix. * * @param matrix The matrix used to define the affine transform. * @return The affine transform. */ public MathTransform2D createAffineTransform(final AffineTransform matrix) { if (matrix.isIdentity()) { return MathTransform2D.IDENTITY; } return (MathTransform2D) pool.canonicalize(new AffineTransform2D(matrix)); } /** * Creates an affine transform from a matrix. * * @param matrix The matrix used to define the affine transform. * @return The affine transform. * * @see org.opengis.ct.CT_MathTransformFactory#createAffineTransform */ public MathTransform createAffineTransform(final Matrix matrix) { /* * If the user is requesting a 2D transform, delegate to the * highly optimized java.awt.geom.AffineTransform class. */ final int numRow = matrix.getNumRow(); if (matrix.isAffine()) { // Affine transform are square. switch (numRow) { case 2: return (MathTransform) pool.canonicalize( LinearTransform1D.create(matrix.getElement(0,0), // scale matrix.getElement(0,1))); // offset case 3: return createAffineTransform(matrix.toAffineTransform2D()); } } /* * The 1D and 2D cases have their own optimized identity transform, which is why * the test for identity must come after the 'isAffine()' test. If the transform * is not an identity, fallback to the general case (slower). May not be a real * affine transform, but accept it anyway... */ return (MathTransform) pool.canonicalize(matrix.isIdentity() ? (MathTransform) new IdentityTransform(numRow-1) : (MathTransform) new MatrixTransform(matrix)); } /** * Returns the underlying matrix for the specified transform, * or null if the matrix is unavailable. */ private static Matrix getMatrix(final MathTransform transform) { if (transform instanceof LinearTransform) { return ((LinearTransform) transform).getMatrix(); } if (transform instanceof AffineTransform) { return new Matrix((AffineTransform) transform); } return null; } /** * Tests if one math transform is the inverse of the other. This implementation * can't detect every case. It just detect the case when tr2 is an * instance of {@link AbstractMathTransform.Inverse}. */ private static boolean areInverse(final MathTransform tr1, final MathTransform tr2) { if (tr2 instanceof AbstractMathTransform.Inverse) { return tr1.equals(((AbstractMathTransform.Inverse) tr2).inverse()); // TODO: we could make this test more general (just compare with tr2.inverse(), // no matter if it is an instance of AbstractMathTransform.Inverse or not, // and catch the exception if one is thrown). Would it be too expensive to // create inconditionnaly the inverse transform? } return false; } /** * Creates a transform by concatenating two existing transforms. * A concatenated transform acts in the same way as applying two * transforms, one after the other. The dimension of the output * space of the first transform must match the dimension of the * input space in the second transform. If you wish to concatenate * more than two transforms, then you can repeatedly use this method. * * @param tr1 The first transform to apply to points. * @param tr2 The second transform to apply to points. * @return The concatenated transform. * * @see org.opengis.ct.CT_MathTransformFactory#createConcatenatedTransform * * @task TODO: We could add one more optimisation: if one transform is a matrix and the * other transform is a PassThroughTransform, and if the matrix as 0 elements * for all rows matching the PassThrough sub-transform, then we can get ride * of the whole PassThroughTransform object. */ public MathTransform createConcatenatedTransform(MathTransform tr1, MathTransform tr2) { if (tr1.isIdentity()) return tr2; if (tr2.isIdentity()) return tr1; /* * If both transforms use matrix, then we can create * a single transform using the concatenated matrix. */ final Matrix matrix1 = getMatrix(tr1); if (matrix1 != null) { final Matrix matrix2 = getMatrix(tr2); if (matrix2 != null) { // Compute "matrix = matrix2 * matrix1". Reuse an existing matrix object // if possible, which is always the case when both matrix are square. final int numRow = matrix2.getNumRow(); final int numCol = matrix1.getNumCol(); final Matrix matrix; if (numCol == matrix2.getNumCol()) { matrix = matrix2; matrix2.mul(matrix1); } else { matrix = new Matrix(numRow, numCol); matrix.mul(matrix2, matrix1); } // May not be really affine, but work anyway... // This call will detect and optimize the special // case where an 'AffineTransform' can be used. return createAffineTransform(matrix); } } /* * If one transform is the inverse of the * other, returns the identity transform. */ if (areInverse(tr1, tr2) || areInverse(tr2, tr1)) { assert tr1.getDimSource()==tr2.getDimTarget(); assert tr1.getDimTarget()==tr2.getDimSource(); return createIdentityTransform(tr1.getDimSource()); } /* * If one or both math transform are instance of {@link ConcatenatedTransform}, * then maybe it is possible to efficiently concatenate tr1 or * tr2 with one of step transforms. Try that... */ if (tr1 instanceof ConcatenatedTransform) { final ConcatenatedTransform ctr = (ConcatenatedTransform) tr1; tr1 = ctr.transform1; tr2 = createConcatenatedTransform(ctr.transform2, tr2); } if (tr2 instanceof ConcatenatedTransform) { final ConcatenatedTransform ctr = (ConcatenatedTransform) tr2; tr1 = createConcatenatedTransform(tr1, ctr.transform1); tr2 = ctr.transform2; } /* * Before to create a general {@link ConcatenatedTransform} object, give a * chance to {@link AbstractMathTransform} to returns an optimized object. */ if (tr1 instanceof AbstractMathTransform) { final MathTransform optimized = ((AbstractMathTransform) tr1).concatenate(tr2, false); if (optimized != null) { return (MathTransform) pool.canonicalize(optimized); } } if (tr2 instanceof AbstractMathTransform) { final MathTransform optimized = ((AbstractMathTransform) tr2).concatenate(tr1, true); if (optimized != null) { return (MathTransform) pool.canonicalize(optimized); } } return (MathTransform) pool.canonicalize(ConcatenatedTransform.create(this, tr1, tr2)); } /** * Creates a transform which passes through a subset of ordinates to another transform. * This allows transforms to operate on a subset of ordinates. For example, if you have * (latitidue,longitude,height) coordinates, then you * may wish to convert the height values from feet to meters without affecting the * latitude and longitude values. * * @param firstAffectedOrdinate Index of the first affected ordinate. * @param subTransform The sub transform. * @param numTrailingOrdinates Number of trailing ordinates to pass through. * Affected ordinates will range from firstAffectedOrdinate * inclusive to dimTarget-numTrailingOrdinates exclusive. * @return A pass through transform with the following dimensions:
*
     * Source: firstAffectedOrdinate + subTransform.getDimSource() + numTrailingOrdinates
     * Target: firstAffectedOrdinate + subTransform.getDimTarget() + numTrailingOrdinates
* * @see org.opengis.ct.CT_MathTransformFactory#createPassThroughTransform * @see #createSubTransform */ public MathTransform createPassThroughTransform(final int firstAffectedOrdinate, final MathTransform subTransform, final int numTrailingOrdinates) { if (firstAffectedOrdinate < 0) { throw new IllegalArgumentException(Errors.format( ErrorKeys.ILLEGAL_ARGUMENT_$2, "firstAffectedOrdinate", new Integer(firstAffectedOrdinate))); } if (numTrailingOrdinates < 0) { throw new IllegalArgumentException(Errors.format( ErrorKeys.ILLEGAL_ARGUMENT_$2, "numTrailingOrdinates", new Integer(numTrailingOrdinates))); } if (firstAffectedOrdinate==0 && numTrailingOrdinates==0) { return subTransform; } /* * Optimize the "Identity transform" case. */ if (subTransform.isIdentity()) { final int dimension = subTransform.getDimSource(); if (dimension == subTransform.getDimTarget()) { // The AffineTransform is easier to concatenate with other transforms. return createIdentityTransform(firstAffectedOrdinate + dimension + numTrailingOrdinates); } } /* * Special case for transformation backed by a matrix. Is is possible to use a * new matrix for such transform, instead of wrapping the sub-transform into a * PassThroughTransform object. It is faster and easier to concatenate. */ if (subTransform instanceof LinearTransform) { Matrix matrix = ((LinearTransform)subTransform).getMatrix(); matrix = PassThroughTransform.expand(matrix, firstAffectedOrdinate, numTrailingOrdinates, 1); return createAffineTransform(matrix); } /* * Construct the general PassThroughTransform object. An optimisation * for the "Pass through case" is done right in the constructor. */ return (MathTransform) pool.canonicalize(new PassThroughTransform( firstAffectedOrdinate, subTransform, numTrailingOrdinates)); } /** * Reduces the number of input dimensions for the specified transform. The remaining output * dimensions will be selected automatically according the specified input dimensions. For * example if the supplied transform has (x, y, z) * inputs and (longitude, latitude, height) outputs, then * createSubTransform(transform, new {@linkplain IntegerSequence IntegerSequence}(0,1), * null) will returns a transform with (x, y) inputs and (probably) * (longitude, latitude) outputs. This method can be used in order to * separate a {@linkplain #createConcatenatedTransform concatenation} of * {@linkplain #createPassThroughTransform pass through} transforms. * * @param transform The transform to reduces. * @param inputDimensions The input dimension to keep. This sequence can contains any integers * in the range 0 inclusive to transform.{@linkplain MathTransform#getDimSource * getDimSource()} exclusive. * @param outputDimensions An optional sequence in which to store output dimensions. If * non-null, then this sequence will be filled with output dimensions selected by * this method. The integers will be in the range 0 inclusive to * transform.{@linkplain MathTransform#getDimTarget getDimTarget()} * exclusive. * @return A transform expecting only the specified input dimensions. The following invariant * should hold: *
     *         subTransform.{@linkplain MathTransform#getDimSource getDimSource()} ==  inputDimensions.{@linkplain IntegerSequence#getNumElements getNumElements()};
     *         subTransform.{@linkplain MathTransform#getDimTarget getDimTarget()} == outputDimensions.{@linkplain IntegerSequence#getNumElements getNumElements()};
     *         
* * @throws FactoryException if the transform is not separable. */ public MathTransform createSubTransform(final MathTransform transform, final IntegerSequence inputDimensions, final IntegerSequence outputDimensions) throws FactoryException { final int dimSource = transform.getDimSource(); final int dimTarget = transform.getDimTarget(); final int dimInput = inputDimensions.getNumElements(); final int lower = JAIUtilities.getMinimum(inputDimensions); final int upper = JAIUtilities.getMaximum(inputDimensions)+1; if (lower<0 || lower>=upper) { throw new IllegalArgumentException(Errors.format( ErrorKeys.ILLEGAL_ARGUMENT_$2, "minimum(inputDimensions)", new Integer(lower))); } if (upper > dimSource) { throw new IllegalArgumentException(Errors.format( ErrorKeys.ILLEGAL_ARGUMENT_$2, "maximum(inputDimensions)", new Integer(upper-1))); } /* * Check for easiest cases: same transform, identity transform or concatenated transforms. */ if (dimInput == dimSource) { assert lower==0 && upper==dimSource; JAIUtilities.fill(outputDimensions, 0, dimTarget); return transform; } if (transform.isIdentity()) { JAIUtilities.add(outputDimensions, inputDimensions, 0); return createIdentityTransform(dimInput); } if (transform instanceof ConcatenatedTransform) { final ConcatenatedTransform ctr = (ConcatenatedTransform) transform; final IntegerSequence trans = new IntegerSequence(); final MathTransform step1 = createSubTransform(ctr.transform1, inputDimensions, trans); final MathTransform step2 = createSubTransform(ctr.transform2, trans, outputDimensions); return createConcatenatedTransform(step1, step2); } /* * Special case for the pass through transform: if at least one input dimension * belong to the passthrough's sub-transform, then delegates part of the work to * subTransform(passThrough.transform, ...) */ if (transform instanceof PassThroughTransform) { final PassThroughTransform passThrough = (PassThroughTransform) transform; final int dimPass = passThrough.transform.getDimSource(); final int dimDiff = passThrough.transform.getDimTarget() - dimPass; final int subLower = passThrough.firstAffectedOrdinate; final int subUpper = subLower + dimPass; final IntegerSequence subInputs = new IntegerSequence(); for (inputDimensions.startEnumeration(); inputDimensions.hasMoreElements();) { int n = inputDimensions.nextElement(); if (n>=subLower && n= subUpper) { n += dimDiff; } outputDimensions.insert(n); } } if (subInputs.getNumElements() == 0) { // No input dimension belong to the sub-transform. return createIdentityTransform(dimInput); } final IntegerSequence subOutputs; final MathTransform subTransform; subOutputs = (outputDimensions!=null) ? new IntegerSequence() : null; subTransform = createSubTransform(passThrough.transform, subInputs, subOutputs); JAIUtilities.add(outputDimensions, subOutputs, subLower); if (JAIUtilities.containsAll(inputDimensions, lower, subLower) && JAIUtilities.containsAll(inputDimensions, subUpper, upper)) { final int firstAffectedOrdinate = Math.max(0, subLower-lower); final int numTrailingOrdinates = Math.max(0, upper-subUpper); return createPassThroughTransform(firstAffectedOrdinate, subTransform, numTrailingOrdinates); } // TODO: handle more general case here... } /* * If the transformation is specified by a matrix, select all output dimensions which * do not depends on any of the discarted input dimensions. */ if (transform instanceof LinearTransform) { int nRows = 0; boolean hasLastRow = false; double[][] rows = ((LinearTransform)transform).getMatrix().getElements(); assert rows.length-1 == dimTarget; reduce: for (int j=0; jtransform has * (longitude, latitude, height) outputs, then a sub-transform * may be used to keep only the (longitude, latitude) part. In most cases, * the created sub-transform is non-invertible since it loose informations. *

* This transform may be see as a non-square matrix transform with less rows * than columns, concatenated with transform. However, invoking * createFilterTransfom(...) allows the optimization of some common cases. * * @param transform The transform to reduces. * @param outputDimensions The output dimension to keep. * This sequence can contains any integers in the range 0 inclusive to * transform.{@linkplain MathTransform#getDimTarget getDimTarget()} * exclusive. * @return The transform keeping only the specified output dimensions. */ public MathTransform createFilterTransform(MathTransform transform, final IntegerSequence outputDimensions) { final int dimSource = transform.getDimSource(); final int dimTarget = transform.getDimTarget(); final int dimOutput = outputDimensions.getNumElements(); final int lower = JAIUtilities.getMinimum(outputDimensions); final int upper = JAIUtilities.getMaximum(outputDimensions)+1; if (lower<0 || lower>=upper) { throw new IllegalArgumentException(Errors.format( ErrorKeys.ILLEGAL_ARGUMENT_$2, "minimum(outputDimensions)", new Integer(lower))); } if (upper > dimTarget) { throw new IllegalArgumentException(Errors.format( ErrorKeys.ILLEGAL_ARGUMENT_$2, "maximum(outputDimensions)", new Integer(upper))); } if (dimOutput == dimTarget) { assert lower==0 && upper==dimTarget; return transform; } /* * If the transform is an instance of "pass through" transform but no dimension from its * subtransform is requested, then ignore the subtransform (i.e. treat the whole transform * as identity, except for the number of output dimension which may be different from the * number of input dimension). */ int dimPass = 0; int dimDiff = 0; int dimStep = dimTarget; if (transform instanceof PassThroughTransform) { final PassThroughTransform passThrough = (PassThroughTransform) transform; final int subLower = passThrough.firstAffectedOrdinate; final int subUpper = subLower + passThrough.transform.getDimTarget(); if (!JAIUtilities.containsAny(outputDimensions, subLower, subUpper)) { transform = null; dimStep = dimSource; dimPass = subLower; dimDiff = (subLower + passThrough.transform.getDimSource()) - subUpper; } } /* * Create the matrix to be used as a filter, [x'] [1 0 0 0] [x] * and concatenate it to the transform. The [z'] = [0 0 1 0] [y] * matrix will contains only a 1 for the output [1 ] [0 0 0 1] [z] * dimension to keep, as in the following example: [1] */ final Matrix matrix = new Matrix(dimOutput+1, dimStep+1); matrix.setZero(); int j=0; for (outputDimensions.startEnumeration(); outputDimensions.hasMoreElements(); j++) { int i = outputDimensions.nextElement(); if (i >= dimPass) { i += dimDiff; } matrix.setElement(j, i, 1); } // Affine transform has one more row/column than dimension. matrix.setElement(dimOutput, dimStep, 1); MathTransform filtered = createAffineTransform(matrix); if (transform != null) { filtered = createConcatenatedTransform(transform, filtered); } return filtered; } /** * Creates a transform from a classification name and parameters. * The client must ensure that all the linear parameters are expressed * in meters, and all the angular parameters are expressed in degrees. * Also, they must supply "semi_major" and "semi_minor" parameters * for cartographic projection transforms. * * @param classification The classification name of the transform * (e.g. "Transverse_Mercator"). Leading and trailing spaces * are ignored, and comparaison is case-insensitive. * @param parameters The parameter values in standard units. * @return The parameterized transform. * @throws NoSuchClassificationException if there is no transform for the specified * classification. * @throws MissingParameterException if a parameter was required but not found. * @throws FactoryException if the math transform creation failed from some other reason. * * @see org.opengis.ct.CT_MathTransformFactory#createParameterizedTransform * @see #getAvailableTransforms */ public MathTransform createParameterizedTransform(String classification, final ParameterList parameters) throws MissingParameterException, FactoryException { final MathTransform transform; classification = classification.trim(); if (classification.equalsIgnoreCase("Affine")) { return createAffineTransform(MatrixParameters.getMatrix(parameters)); } transform = getMathTransformProvider(classification).create(parameters); return (MathTransform) pool.canonicalize(transform); } /** * Creates a transform from a projection. The client must ensure that all the linear * parameters are expressed in meters, and all the angular parameters are expressed * in degrees. Also, they must supply "semi_major" and "semi_minor" parameters for * cartographic projection transforms. * * @param projection The projection. * @return The parameterized transform. * @throws NoSuchClassificationException if there is no transform for the specified projection. * @throws MissingParameterException if a parameter was required but not found. * @throws FactoryException if the math transform creation failed from some other reason. */ public MathTransform createParameterizedTransform(final Projection projection) throws MissingParameterException, FactoryException { final MathTransform transform; transform = getMathTransformProvider(projection.getClassName()).create(projection); return (MathTransform) pool.canonicalize(transform); } /** * Returns the classification names of every available transforms. * The returned array may have a zero length, but will never be null. * * @see #createParameterizedTransform */ public String[] getAvailableTransforms() { final String[] names = new String[providers.length]; for (int i=0; igetMathTransformProvider("Transverse_Mercator").getParameterList()), * or the transform name in a given locale (e.g. * getMathTransformProvider("Transverse_Mercator").getName({@link Locale#FRENCH})) * * @param classification The classification name of the transform * (e.g. "Transverse_Mercator"). It should be one of the name * returned by {@link #getAvailableTransforms}. Leading and * trailing spaces are ignored. Comparisons are case-insensitive. * @return The provider for a math transform. * @throws NoSuchClassificationException if there is no provider registered * with the specified classification name. */ public MathTransformProvider getMathTransformProvider(String classification) throws NoSuchClassificationException { classification = classification.trim(); for (int i=0; i *
  • If the specified parameter is a linear measure, then this method returns * {@link Unit#METRE}. Other linear units are not authorized.
  • *
  • If the specified parameter is an angular measure, then this method returns * {@link Unit#DEGREE}. Other angular units are not authorized.
  • *
  • Otherwise, this method may returns {@link Unit#DIMENSIONLESS} or * null.
  • * * * @param parameter The parameter name (e.g. "false_easting" * or "central_meridian"). * @return The parameter unit, or null. */ public Unit getParameterUnit(final String parameter) { return DescriptorNaming.getParameterUnit(parameter); } /** * Creates a math transform object from a Well-Known Text (WKT) string. * WKT are part of Coordinate Transformation Services Specification. * * @param text The Well-Known Text. * @return The math transform (never null). * @throws FactoryException if the Well-Known Text can't be parsed, * or if the math transform creation failed from some other reason. */ public MathTransform createFromWKT(final String text) throws FactoryException { if (parser == null) { // Not a big deal if we are not synchronized. If this method is invoked in // same time by two different threads, we may have two WKTParser objects // for a short time. It doesn't hurt... parser = new WKTParser(Locale.US, this); } try { return parser.parseMathTransform(text); } catch (ParseException exception) { final Throwable cause = exception.getCause(); if (cause instanceof FactoryException) { throw (FactoryException) cause; } throw new FactoryException(exception.getLocalizedMessage(), exception); } } /** * Returns an OpenGIS interface for this info. This method first looks in the cache. * If no interface was previously cached, then this method creates a new adapter and * caches the result. * * Note: The returned type is a generic {@link Object} in order * to avoid premature class loading of OpenGIS interface. * * @param adapters The originating {@link Adapters}. * @return The OpenGIS interface for this info. * @throws RemoteException if the object can't be exported. */ final synchronized Object toOpenGIS(final Object adapters) throws RemoteException { if (proxy != null) { if (proxy instanceof Reference) { final Object ref = ((Reference) proxy).get(); if (ref != null) { return ref; } } else { return proxy; } } final Object opengis = new Export(adapters); proxy = new WeakReference(opengis); return opengis; } ///////////////////////////////////////////////////////////////////////// //////////////// //////////////// //////////////// OPENGIS ADAPTER //////////////// //////////////// //////////////// ///////////////////////////////////////////////////////////////////////// /** * Wrap a {@link MathTransformFactory} for use with OpenGIS. This wrapper is a good * place to check for non-implemented OpenGIS methods (just check for methods throwing * {@link UnsupportedOperationException}). This class is suitable for RMI use. * * @version $Id$ * @author Martin Desruisseaux */ private final class Export extends UnicastRemoteObject implements CT_MathTransformFactory { /** * The originating adapter. */ protected final Adapters adapters; /** * Construct a remote object. */ protected Export(final Object adapters) throws RemoteException { super(); // TODO: Fetch the port number from the adapter. this.adapters = (Adapters)adapters; } /** * Creates an affine transform from a matrix. */ public CT_MathTransform createAffineTransform(final PT_Matrix matrix) throws RemoteException { return adapters.export(MathTransformFactory.this.createAffineTransform( adapters.wrap(matrix))); } /** * Creates a transform by concatenating two existing transforms. */ public CT_MathTransform createConcatenatedTransform(final CT_MathTransform transform1, final CT_MathTransform transform2) throws RemoteException { return adapters.export(MathTransformFactory.this.createConcatenatedTransform( adapters.wrap(transform1), adapters.wrap(transform2))); } /** * Creates a transform which passes through a subset of ordinates to another transform. */ public CT_MathTransform createPassThroughTransform(final int firstAffectedOrdinate, final CT_MathTransform subTransform) throws RemoteException { return adapters.export(MathTransformFactory.this.createPassThroughTransform( firstAffectedOrdinate, adapters.wrap(subTransform), 0)); } /** * Creates a transform from a classification name and parameters. */ public CT_MathTransform createParameterizedTransform(final String classification, final CT_Parameter[] parameters) throws RemoteException { try { return adapters.export(MathTransformFactory.this.createParameterizedTransform( classification, adapters.wrap(parameters))); } catch (FactoryException exception) { throw Adapters.serverException(exception); } } /** * Creates a math transform from a Well-Known Text string. */ public CT_MathTransform createFromWKT(final String text) throws RemoteException { try { return adapters.export(MathTransformFactory.this.createFromWKT(text)); } catch (FactoryException exception) { throw Adapters.serverException(exception); } } /** * Creates a math transform from XML. */ public CT_MathTransform createFromXML(final String xml) throws RemoteException { throw new UnsupportedOperationException("XML parsing not yet implemented"); } /** * Tests whether parameter is angular. */ public boolean isParameterAngular(final String parameterName) throws RemoteException { final Unit unit = getParameterUnit(parameterName); return (unit!=null) && Unit.DEGREE.canConvert(unit); } /** * Tests whether parameter is linear. */ public boolean isParameterLinear(final String parameterName) throws RemoteException { final Unit unit = getParameterUnit(parameterName); return (unit!=null) && Unit.METRE.canConvert(unit); } } }