/* * 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; import java.text.ParseException; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.Set; import java.util.HashSet; import java.util.TreeSet; import javax.measure.unit.Unit; import javax.measure.quantity.Length; import javax.measure.converter.ConversionException; import org.opengis.metadata.citation.Citation; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.FactoryException; import org.opengis.referencing.NoSuchIdentifierException; import org.opengis.referencing.datum.Ellipsoid; import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.Conversion; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.Operation; import org.opengis.referencing.operation.OperationMethod; import org.opengis.referencing.operation.Projection; import org.geotools.metadata.iso.citation.Citations; import org.geotools.factory.Hints; import org.geotools.factory.FactoryRegistry; import org.geotools.parameter.Parameters; import org.geotools.parameter.ParameterWriter; import org.geotools.referencing.AbstractIdentifiedObject; import org.geotools.referencing.cs.AbstractCS; import org.geotools.referencing.factory.ReferencingFactory; import org.geotools.referencing.operation.matrix.MatrixFactory; import org.geotools.referencing.operation.transform.ConcatenatedTransform; import org.geotools.referencing.operation.transform.PassThroughTransform; import org.geotools.referencing.operation.transform.ProjectiveTransform; import org.geotools.referencing.wkt.MathTransformParser; import org.geotools.referencing.wkt.Symbols; import org.geotools.resources.Arguments; import org.geotools.resources.LazySet; import org.geotools.resources.CRSUtilities; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.util.CanonicalSet; /** * Low level factory for creating {@linkplain MathTransform math transforms}. * Many high level GIS applications will never need to use this factory directly; * they can use a {@linkplain DefaultCoordinateOperationFactory coordinate operation factory} * instead. However, the {@code MathTransformFactory} interface can be used directly * by applications that wish to transform other types of coordinates (e.g. color coordinates, * or image pixel coordinates). *
* A {@linkplain MathTransform 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 {@code MathTransformFactory} * significantly easier than it would be otherwise. * * For example 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 {@linkplain MathTransform 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 {@code MathTransformFactory}. * The low semantic content of {@linkplain MathTransform 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 {@linkplain MathTransform 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.
*
* @since 2.1
*
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @tutorial http://docs.codehaus.org/display/GEOTOOLS/Coordinate+Transformation+Parameters
*/
public class DefaultMathTransformFactory extends ReferencingFactory implements MathTransformFactory {
/**
* The hints to provide to math transform providers. Null for now, but may be non-null
* in some future version.
*/
private static final Hints HINTS = null;
/**
* The object to use for parsing Well-Known Text (WKT) strings.
* Will be created only when first needed.
*/
private transient MathTransformParser parser;
/**
* The last value returned by {@link #getProvider}. Stored as an
* optimization since the same provider is often asked many times.
*/
private transient MathTransformProvider lastProvider;
/**
* The operation method for the last transform created.
*/
private static final ThreadLocal This method creates new parameter instances at every call. It is intented to be modified
* by the user before to be passed to
* The {@linkplain OperationMethod operation method} used can be obtained by a call to
* {@link #getLastUsedMethod}.
*
* @param baseCRS The source coordinate reference system.
* @param parameters The parameter values for the transform.
* @param derivedCS the target coordinate system.
* @return The parameterized transform.
* @throws NoSuchIdentifierException if there is no transform registered for the method.
* @throws FactoryException if the object creation failed. This exception is thrown
* if some required parameter has not been supplied, or has illegal value.
*/
public MathTransform createBaseToDerived(final CoordinateReferenceSystem baseCRS,
final ParameterValueGroup parameters,
final CoordinateSystem derivedCS)
throws NoSuchIdentifierException, FactoryException
{
/*
* If the user's parameter do not contains semi-major and semi-minor axis length, infers
* them from the ellipsoid. This is a convenience service since the user often omit those
* parameters (because they duplicate datum information).
*/
final Ellipsoid ellipsoid = CRSUtilities.getHeadGeoEllipsoid(baseCRS);
if (ellipsoid != null) {
final Unit where options are: and <method> is the optional name of an operation method
* (e.g. Note for Windows users: If the output contains strange
* symbols, try to supply an "{@code -encoding}" argument. Example: The codepage number (850 in the previous example) can be obtained from the DOS
* commande line using the "{@code chcp}" command with no arguments.{@linkplain Operation}.class
for fetching all operation methods,
* or {@linkplain Projection}.class
for fetching only map projection
* methods.
* @return All {@linkplain MathTransform math transform} methods available in this factory.
*
* @see #getDefaultParameters
* @see #createParameterizedTransform
*/
public SetgetProvider("Transverse_Mercator").getParameters()
),
* or any of the alias in a given locale.
*
* @param method The case insensitive
* {@linkplain org.opengis.metadata.Identifier#getCode identifier code}
* of the operation method to search for (e.g. {@code "Transverse_Mercator"}).
* @return The math transform provider.
* @throws NoSuchIdentifierException if there is no provider registered for the specified
* method.
*/
private MathTransformProvider getProvider(final String method) throws NoSuchIdentifierException {
/*
* Copies the 'lastProvider' reference in order to avoid synchronization. This is safe
* because copy of object references are atomic operations. Note that this is not the
* deprecated "double check" idiom since we are not creating new objects, but checking
* for existing ones.
*/
MathTransformProvider provider = lastProvider;
if (provider!=null && provider.nameMatches(method)) {
return provider;
}
final Iterator"Transverse_Mercator"
).
*
* {@linkplain #createParameterizedTransform
* createParameterizedTransform}(parameters)
.
*
* @param parameters The parameter values.
* @return The parameterized transform.
* @throws NoSuchIdentifierException if there is no transform registered for the method.
* @throws FactoryException if the object creation failed. This exception is thrown
* if some required parameter has not been supplied, or has illegal value.
*
* @see #getDefaultParameters
* @see #getAvailableMethods
* @see #getLastUsedMethod
*/
public MathTransform createParameterizedTransform(ParameterValueGroup parameters)
throws NoSuchIdentifierException, FactoryException
{
MathTransform transform;
OperationMethod method = null;
try {
final String classification = parameters.getDescriptor().getName().getCode();
final MathTransformProvider provider = getProvider(classification);
method = provider;
try {
parameters = provider.ensureValidValues(parameters);
transform = provider.createMathTransform(parameters);
} catch (IllegalArgumentException exception) {
/*
* Catch only exceptions which may be the result of improper parameter
* usage (e.g. a value out of range). Do not catch exception caused by
* programming errors (e.g. null pointer exception).
*/
throw new FactoryException(exception);
}
if (transform instanceof MathTransformProvider.Delegate) {
final MathTransformProvider.Delegate delegate = (MathTransformProvider.Delegate) transform;
method = delegate.method;
transform = delegate.transform;
}
transform = pool.unique(transform);
} finally {
lastMethod.set(method); // May be null in case of failure, which is intented.
}
return transform;
}
/**
* Creates an affine transform from a matrix.
* If the transform's input dimension is {@code M}, and output dimension
* is {@code N}, then the matrix will have size {@code [N+1][M+1]}.
* The +1 in the matrix dimensions allows the matrix to do a shift, as well as
* a rotation. The {@code [M][j]} element of the matrix will be the j'th
* ordinate of the moved origin. The {@code [i][N]} element of the matrix
* will be 0 for i less than {@code M}, and 1 for i
* equals {@code M}.
*
* @param matrix The matrix used to define the affine transform.
* @return The affine transform.
* @throws FactoryException if the object creation failed.
*/
public MathTransform createAffineTransform(final Matrix matrix)
throws FactoryException
{
lastMethod.remove(); // To be strict, we should set ProjectiveTransform.Provider
return pool.unique(ProjectiveTransform.create(matrix));
}
/**
* 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 transform1 The first transform to apply to points.
* @param transform2 The second transform to apply to points.
* @return The concatenated transform.
* @throws FactoryException if the object creation failed.
*/
public MathTransform createConcatenatedTransform(final MathTransform transform1,
final MathTransform transform2)
throws FactoryException
{
MathTransform tr;
try {
tr = ConcatenatedTransform.create(transform1, transform2);
} catch (IllegalArgumentException exception) {
throw new FactoryException(exception);
}
tr = pool.unique(tr);
return tr;
}
/**
* 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
* (Lat,Lon,Height) coordinates, then you may wish to
* convert the height values from meters to feet without affecting the
* (Lat,Lon) values.
*
* @param firstAffectedOrdinate The lowest index of the affected ordinates.
* @param subTransform Transform to use for affected ordinates.
* @param numTrailingOrdinates Number of trailing ordinates to pass through.
* Affected ordinates will range from {@code firstAffectedOrdinate}
* inclusive to {@code dimTarget-numTrailingOrdinates} exclusive.
* @return A pass through transform with the following dimensions:
* ParameterValueGroup p = factory.getDefaultParameters("Transverse_Mercator");
* p.parameter("semi_major").setValue(6378137.000);
* p.parameter("semi_minor").setValue(6356752.314);
* MathTransform mt = factory.createParameterizedTransform(p);
*
*
* Source: firstAffectedOrdinate + subTransform.getSourceDimensions() + numTrailingOrdinates
* Target: firstAffectedOrdinate + subTransform.getTargetDimensions() + numTrailingOrdinates
* @throws FactoryException if the object creation failed.
*/
public MathTransform createPassThroughTransform(final int firstAffectedOrdinate,
final MathTransform subTransform,
final int numTrailingOrdinates)
throws FactoryException
{
MathTransform tr;
try {
tr = PassThroughTransform.create(firstAffectedOrdinate,
subTransform,
numTrailingOrdinates);
} catch (IllegalArgumentException exception) {
throw new FactoryException(exception);
}
tr = pool.unique(tr);
return tr;
}
/**
* Creates a math transform object from a XML string. The default implementation
* always throws an exception, since this method is not yet implemented.
*
* @param xml Math transform encoded in XML format.
* @throws FactoryException if the object creation failed.
*/
public MathTransform createFromXML(String xml) throws FactoryException {
throw new FactoryException("Not yet implemented.");
}
/**
* Creates a math transform object from a
* Well
* Known Text (WKT).
*
* @param text Math transform encoded in Well-Known Text format.
* @return The math transform (never {@code null}).
* @throws FactoryException if the Well-Known Text can't be parsed,
* or if the math transform creation failed from some other reason.
*/
public synchronized MathTransform createFromWKT(final String text) throws FactoryException {
// Note: while this factory is thread safe, the WKT parser is not.
// Since we share a single instance of this parser, we must
// synchronize.
if (parser == null) {
parser = new MathTransformParser(Symbols.DEFAULT, 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);
}
}
/**
* Scans for factory plug-ins on the application class path. This method is
* needed because the application class path can theoretically change, or
* additional plug-ins may become available. Rather than re-scanning the
* classpath on every invocation of the API, the class path is scanned
* automatically only on the first invocation. Clients can call this
* method to prompt a re-scan. Thus this method need only be invoked by
* sophisticated applications which dynamically make new plug-ins
* available at runtime.
*/
public void scanForPlugins() {
registry.scanForPlugins();
}
/**
* Dump to the standard output stream a list of available operation methods.
* This method can be invoked from the command line. It provides a mean to
* verify which transforms were found in the classpath. The syntax is:
*
*
*
*
* java org.geotools.referencing.operation.DefaultMathTransformFactory
* <options> <method>
*
*
*
*
*
* -projections
List only projections
*
* -conversions
List only conversions
*
* -all
List the parameters for all transforms
*
* -encoding
<code> Set the character encoding
*
* -locale
<language> Set the language for the output (e.g. "fr" for French) "Affine"
, "EPSG:9624"
or just
* "9624"
for the affine transform method).
*
*
* java org.geotools.referencing.operation.DefaultMathTransformFactory -encoding Cp850
*