001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2005, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.geometry;
018:
019: // OpenGIS dependencies
020: import org.opengis.referencing.FactoryException;
021: import org.opengis.referencing.crs.CoordinateReferenceSystem;
022: import org.opengis.referencing.operation.CoordinateOperation;
023: import org.opengis.referencing.operation.CoordinateOperationFactory;
024: import org.opengis.referencing.operation.MathTransform;
025: import org.opengis.referencing.operation.TransformException;
026: import org.opengis.geometry.DirectPosition;
027: import org.opengis.geometry.MismatchedDimensionException;
028:
029: // Geotools dependencies
030: import org.geotools.factory.Hints;
031: import org.geotools.factory.FactoryRegistryException;
032: import org.geotools.referencing.CRS;
033: import org.geotools.referencing.ReferencingFactoryFinder;
034: import org.geotools.referencing.crs.DefaultGeographicCRS;
035: import org.geotools.resources.i18n.ErrorKeys;
036: import org.geotools.resources.i18n.Errors;
037:
038: /**
039: * A direct position capable to {@linkplain #transform transform} a point between an arbitrary CRS
040: * and {@linkplain #getCoordinateReferenceSystem its own CRS}. This class caches the last transform
041: * used in order to improve the performances when the {@linkplain CoordinateOperation#getSourceCRS
042: * source} and {@linkplain CoordinateOperation#getTargetCRS target} CRS don't change often. Using
043: * this class is faster than invoking <code>{@linkplain CoordinateOperationFactory#createOperation
044: * CoordinateOperationFactory.createOperation}(sourceCRS, targetCRS)</code> for every points.
045: *
046: * <ul>
047: * <li><p><strong>Note 1:</strong>
048: * This class is advantageous on a performance point of view only if the same instance of
049: * {@code TransformedDirectPosition} is used for transforming many points between arbitrary
050: * CRS and this {@linkplain #getCoordinateReferenceSystem position CRS}.</p></li>
051: *
052: * <li><p><strong>Note 2:</strong>
053: * This convenience class is useful when the source and target CRS are <em>not likely</em> to
054: * change often. If you are <em>sure</em> that the source and target CRS will not change at all
055: * for a given set of positions, then using {@link CoordinateOperation} directly gives better
056: * performances. This is because {@code TransformedDirectPosition} checks if the CRS changed
057: * before every transformations, which may be costly.</p></li>
058: *
059: * <li><p><strong>Note 3:</strong>
060: * This class is called <cite>Transformed</cite> Direct Position because it is more commonly
061: * used for transforming many points from arbitrary CRS to a common CRS (using the
062: * {@link #transform(DirectPosition)} method) than the other way around.</li></p>
063: * </ul>
064: *
065: * This class usually don't appears in a public API. It is more typicaly used as a helper private
066: * field in some more complex class. For example suppose that {@code MyClass} needs to perform its
067: * internal working in some particular CRS, but we want robust API that adjusts itself to whatever
068: * CRS the client happen to use. {@code MyClass} could be written as below:
069: *
070: * <blockquote><pre>
071: * public class MyClass {
072: * private static final CoordinateReferenceSystem PUBLIC_CRS = ...
073: * private static final CoordinateReferenceSystem INTERNAL_CRS = ...
074: *
075: * private final TransformedDirectPosition myPosition =
076: * new TransformedDirectPosition(PUBLIC_CRS, INTERNAL_CRS, null);
077: *
078: * public void setPosition(DirectPosition position) throws TransformException {
079: * // The position CRS is usually PUBLIC_CRS, but code below will work even if it is not.
080: * myPosition.transform(position);
081: * }
082: *
083: * public DirectPosition getPosition() throws TransformException {
084: * return myPosition.inverseTransform(PUBLIC_CRS);
085: * }
086: * }
087: * </pre></blockquote>
088: *
089: * @since 2.2
090: * @author Martin Desruisseaux
091: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/geometry/TransformedDirectPosition.java $
092: * @version $Id: TransformedDirectPosition.java 25050 2007-04-06 00:41:49Z jgarnett $
093: */
094: public class TransformedDirectPosition extends GeneralDirectPosition {
095: /**
096: * Serial number for interoperability with different versions.
097: */
098: private static final long serialVersionUID = -3988283183934950437L;
099:
100: /**
101: * The factory to use for creating new coordinate operation.
102: */
103: private final CoordinateOperationFactory factory;
104:
105: /**
106: * The default source CRS. To be used only when the user invoked {@link #transform} with
107: * a position without associated {@link CoordinateReferenceSystem}. May be {@code null}
108: * if the default CRS is assumed equals to {@linkplain #getCoordinateReferenceSystem this
109: * position CRS}.
110: */
111: private final CoordinateReferenceSystem defaultCRS;
112:
113: /**
114: * The last source CRS used, or {@code null}. The {@code targetCRS} is the
115: * {@linkplain #getCoordinateReferenceSystem CRS associated with this position}.
116: */
117: private transient CoordinateReferenceSystem sourceCRS;
118:
119: /**
120: * The forward and inverse transforms. Will be created only when first needed.
121: */
122: private transient MathTransform forward, inverse;
123:
124: /**
125: * Creates a new direct position initialized with the
126: * {@linkplain DefaultGeographicCRS#WGS84 WGS84} CRS.
127: *
128: * @since 2.3
129: */
130: public TransformedDirectPosition() {
131: this (null, DefaultGeographicCRS.WGS84, null);
132: }
133:
134: /**
135: * Creates a new position which will contains the result of coordinate transformations from
136: * {@code sourceCRS} to {@code targetCRS}. The {@linkplain #getCoordinateReferenceSystem CRS
137: * associated with this position} will be initially set to {@code targetCRS}.
138: *
139: * @param sourceCRS The <strong>default</strong> CRS to be used by the
140: * <code>{@link #transform transform}(position)</code> method <strong>only</strong>
141: * when the user-supplied {@code position} has a null
142: * {@linkplain DirectPosition#getCoordinateReferenceSystem associated CRS}.
143: * This {@code sourceCRS} argument may be {@code null}, in which case it is assumed
144: * the same than {@code targetCRS}.
145: *
146: * @param targetCRS The {@linkplain #getCoordinateReferenceSystem CRS associated with this
147: * position}. Used for every {@linkplain #transform coordinate transformations} until
148: * the next call to {@link #setCoordinateReferenceSystem setCoordinateReferenceSystem}
149: * or {@link #setLocation(DirectPosition) setLocation}. This argument can not be null.
150: *
151: * @param hints The set of hints to use for fetching a {@link CoordinateOperationFactory},
152: * or {@code null} if none.
153: *
154: * @throws IllegalArgumentException if {@code targetCRS} was {@code null}.
155: * @throws FactoryRegistryException if no {@linkplain CoordinateOperationFactory coordinate
156: * operation factory} can be found for the specified hints.
157: *
158: * @since 2.3
159: */
160: public TransformedDirectPosition(
161: final CoordinateReferenceSystem sourceCRS,
162: final CoordinateReferenceSystem targetCRS, final Hints hints)
163: throws FactoryRegistryException {
164: super (targetCRS);
165: ensureNonNull("targetCRS", targetCRS);
166: defaultCRS = CRS.equalsIgnoreMetadata(sourceCRS, targetCRS) ? null
167: : sourceCRS;
168: factory = ReferencingFactoryFinder
169: .getCoordinateOperationFactory(hints);
170: }
171:
172: /**
173: * Sets the coordinate reference system in which the coordinate is given.
174: * The given CRS will be used as:
175: * <p>
176: * <ul>
177: * <li>the {@linkplain CoordinateOperation#getTargetCRS target CRS} for every call to
178: * {@link #transform(DirectPosition)}</li>
179: * <li>the {@linkplain CoordinateOperation#getSourceCRS source CRS} for every call to
180: * {@link #inverseTransform(CoordinateReferenceSystem)}</li>
181: * </ul>
182: *
183: * @param crs The new CRS for this direct position.
184: * @throws MismatchedDimensionException if the specified CRS doesn't have the expected
185: * number of dimensions.
186: */
187: public void setCoordinateReferenceSystem(
188: final CoordinateReferenceSystem crs)
189: throws MismatchedDimensionException {
190: ensureNonNull("crs", crs);
191: super .setCoordinateReferenceSystem(crs);
192: forward = null;
193: inverse = null;
194: }
195:
196: /**
197: * Sets the {@link #sourceCRS} field and create the associated {@link #forward} transform.
198: * This method do not create yet the {@link #inverse} transform, since it may not be needed.
199: */
200: private void setSourceCRS(final CoordinateReferenceSystem crs)
201: throws TransformException {
202: final CoordinateReferenceSystem targetCRS = getCoordinateReferenceSystem();
203: final CoordinateOperation operation;
204: try {
205: operation = factory.createOperation(crs, targetCRS);
206: } catch (FactoryException exception) {
207: throw new TransformException(exception
208: .getLocalizedMessage(), exception);
209: }
210: /*
211: * Note: 'sourceCRS' must be set last, when we are sure that all other fields
212: * are set to their correct value. This is in order to keep this instance in
213: * a consistent state in case an exception is thrown.
214: */
215: forward = operation.getMathTransform();
216: inverse = null;
217: sourceCRS = crs;
218: }
219:
220: /**
221: * Transforms a given position and stores the result in this object.
222: *
223: * <ul>
224: * <li><p>The {@linkplain CoordinateOperation#getSourceCRS source CRS} is the
225: * {@linkplain DirectPosition#getCoordinateReferenceSystem CRS associated with the given
226: * position}, or the {@code sourceCRS} argument given at
227: * {@linkplain #TransformedDirectPosition(CoordinateReferenceSystem,
228: * CoordinateReferenceSystem, Hints) construction time} <strong>if and only if</strong>
229: * the CRS associated with {@code position} is null.</p></li>
230: *
231: * <li><p>The {@linkplain CoordinateOperation#getTargetCRS target CRS} is the {@linkplain
232: * #getCoordinateReferenceSystem CRS associated with this position}. This is always the
233: * {@code targetCRS} argument given at {@linkplain
234: * #TransformedDirectPosition(CoordinateReferenceSystem, CoordinateReferenceSystem,
235: * Hints) construction time} or by the last call to {@link #setCoordinateReferenceSystem
236: * setCoordinateReferenceSystem}.</p></li>
237: * </ul>
238: *
239: * @param position A position using an arbitrary CRS. This object will not be modified.
240: * @throws TransformException if a coordinate transformation was required and failed.
241: */
242: public void transform(final DirectPosition position)
243: throws TransformException {
244: CoordinateReferenceSystem userCRS = position
245: .getCoordinateReferenceSystem();
246: if (userCRS == null) {
247: userCRS = defaultCRS;
248: if (userCRS == null) {
249: setLocation(position);
250: return;
251: }
252: }
253: /*
254: * A projection may be required. Checks if it is the same one than the one used
255: * last time this method has been invoked. If the specified position uses a new
256: * CRS, then gets the transformation and saves it in case the next call to this
257: * method would uses again the same transformation.
258: */
259: if (forward == null
260: || !CRS.equalsIgnoreMetadata(sourceCRS, userCRS)) {
261: setSourceCRS(userCRS);
262: }
263: if (forward.transform(position, this ) != this ) {
264: throw new AssertionError(forward); // Should never occurs.
265: }
266: }
267:
268: /**
269: * Returns a new point with the same coordinates than this one, but transformed in the given
270: * CRS. This method never returns {@code this}, so the returned point usually doesn't need to
271: * be cloned.
272: *
273: * @param crs The CRS for the position to be returned.
274: * @return The same position than {@code this}, but transformed in the specified CRS.
275: * @throws TransformException if a coordinate transformation was required and failed.
276: *
277: * @since 2.3
278: */
279: public DirectPosition inverseTransform(
280: final CoordinateReferenceSystem crs)
281: throws TransformException {
282: if (inverse == null
283: || !CRS.equalsIgnoreMetadata(sourceCRS, crs)) {
284: ensureNonNull("crs", crs);
285: setSourceCRS(crs);
286: inverse = forward.inverse();
287: }
288: return inverse.transform(this , null);
289: }
290:
291: /**
292: * Returns a new point with the same coordinates than this one, but transformed in the
293: * {@code sourceCRS} given at {@linkplain #TransformedDirectPosition(CoordinateReferenceSystem,
294: * CoordinateReferenceSystem, Hints) construction time}. This method never returns {@code this},
295: * so the returned point usually doesn't need to be cloned.
296: *
297: * @return The same position than {@code this}, but transformed in the source CRS.
298: * @throws TransformException if a coordinate transformation was required and failed.
299: *
300: * @since 2.3
301: */
302: public DirectPosition inverseTransform() throws TransformException {
303: if (defaultCRS != null) {
304: return inverseTransform(defaultCRS);
305: } else {
306: return new GeneralDirectPosition(this );
307: }
308: }
309:
310: /**
311: * Makes sure an argument is non-null.
312: *
313: * @param name Argument name.
314: * @param object User argument.
315: * @throws InvalidParameterValueException if {@code object} is null.
316: */
317: private static void ensureNonNull(final String name,
318: final Object object) throws IllegalArgumentException {
319: if (object == null) {
320: throw new IllegalArgumentException(Errors.format(
321: ErrorKeys.NULL_ARGUMENT_$1, name));
322: }
323: }
324: }
|