001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2001, 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: * This package contains documentation from OpenGIS specifications.
018: * OpenGIS consortium's work is fully acknowledged here.
019: */
020: package org.geotools.referencing.operation;
021:
022: // J2SE dependencies and extensions
023: import java.util.Map;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.LinkedList;
027: import java.util.Collections;
028: import javax.units.ConversionException;
029:
030: // OpenGIS dependencies
031: import org.opengis.metadata.quality.PositionalAccuracy;
032: import org.opengis.parameter.ParameterDescriptorGroup;
033: import org.opengis.parameter.ParameterValueGroup;
034: import org.opengis.referencing.FactoryException;
035: import org.opengis.referencing.IdentifiedObject;
036: import org.opengis.referencing.ReferenceIdentifier;
037: import org.opengis.referencing.crs.CoordinateReferenceSystem;
038: import org.opengis.referencing.cs.CoordinateSystem;
039: import org.opengis.referencing.operation.Conversion;
040: import org.opengis.referencing.operation.CoordinateOperation;
041: import org.opengis.referencing.operation.CoordinateOperationFactory;
042: import org.opengis.referencing.operation.ConcatenatedOperation;
043: import org.opengis.referencing.operation.MathTransform;
044: import org.opengis.referencing.operation.MathTransformFactory;
045: import org.opengis.referencing.operation.Matrix;
046: import org.opengis.referencing.operation.Operation;
047: import org.opengis.referencing.operation.OperationMethod;
048: import org.opengis.referencing.operation.OperationNotFoundException;
049: import org.opengis.referencing.operation.NoninvertibleTransformException;
050: import org.opengis.referencing.operation.Transformation;
051:
052: // Geotools dependencies
053: import org.geotools.factory.Hints;
054: import org.geotools.metadata.iso.citation.Citations;
055: import org.geotools.metadata.iso.quality.PositionalAccuracyImpl;
056: import org.geotools.referencing.CRS;
057: import org.geotools.referencing.AbstractIdentifiedObject;
058: import org.geotools.referencing.NamedIdentifier;
059: import org.geotools.referencing.factory.FactoryGroup;
060: import org.geotools.referencing.factory.ReferencingFactory;
061: import org.geotools.referencing.cs.AbstractCS;
062: import org.geotools.referencing.operation.transform.ProjectiveTransform;
063: import org.geotools.resources.Utilities;
064: import org.geotools.resources.i18n.Errors;
065: import org.geotools.resources.i18n.ErrorKeys;
066: import org.geotools.resources.i18n.Vocabulary;
067: import org.geotools.resources.i18n.VocabularyKeys;
068: import org.geotools.util.CanonicalSet;
069:
070: /**
071: * Base class for coordinate operation factories. This class provides helper methods for the
072: * construction of building blocks. It doesn't figure out any operation path by itself. This
073: * more "intelligent" job is left to subclasses.
074: *
075: * @since 2.1
076: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/AbstractCoordinateOperationFactory.java $
077: * @version $Id: AbstractCoordinateOperationFactory.java 28264 2007-12-05 21:53:08Z desruisseaux $
078: * @author Martin Desruisseaux
079: */
080: public abstract class AbstractCoordinateOperationFactory extends
081: ReferencingFactory implements CoordinateOperationFactory {
082: /**
083: * The identifier for an identity operation.
084: */
085: protected static final ReferenceIdentifier IDENTITY = new NamedIdentifier(
086: Citations.GEOTOOLS, Vocabulary
087: .formatInternational(VocabularyKeys.IDENTITY));
088:
089: /**
090: * The identifier for conversion using an affine transform for axis swapping and/or
091: * unit conversions.
092: */
093: protected static final ReferenceIdentifier AXIS_CHANGES = new NamedIdentifier(
094: Citations.GEOTOOLS, Vocabulary
095: .formatInternational(VocabularyKeys.AXIS_CHANGES));
096:
097: /**
098: * The identifier for a transformation which is a datum shift.
099: *
100: * @see PositionalAccuracyImpl#DATUM_SHIFT_APPLIED
101: */
102: protected static final ReferenceIdentifier DATUM_SHIFT = new NamedIdentifier(
103: Citations.GEOTOOLS, Vocabulary
104: .formatInternational(VocabularyKeys.DATUM_SHIFT));
105:
106: /**
107: * The identifier for a transformation which is a datum shift without
108: * {@linkplain org.geotools.referencing.datum.BursaWolfParameters Bursa Wolf parameters}.
109: * Only the changes in ellipsoid axis-length are taken in account. Such ellipsoid shifts
110: * are approximative and may have 1 kilometer error. This transformation is allowed
111: * only if the factory was created with {@link Hints#LENIENT_DATUM_SHIFT} set to
112: * {@link Boolean#TRUE}.
113: *
114: * @see PositionalAccuracyImpl#DATUM_SHIFT_OMITTED
115: */
116: protected static final ReferenceIdentifier ELLIPSOID_SHIFT = new NamedIdentifier(
117: Citations.GEOTOOLS,
118: Vocabulary
119: .formatInternational(VocabularyKeys.ELLIPSOID_SHIFT));
120:
121: /**
122: * The identifier for a geocentric conversion.
123: */
124: protected static final ReferenceIdentifier GEOCENTRIC_CONVERSION = new NamedIdentifier(
125: Citations.GEOTOOLS,
126: Vocabulary
127: .formatInternational(VocabularyKeys.GEOCENTRIC_TRANSFORM));
128:
129: /**
130: * The identifier for an inverse operation.
131: */
132: protected static final ReferenceIdentifier INVERSE_OPERATION = new NamedIdentifier(
133: Citations.GEOTOOLS,
134: Vocabulary
135: .formatInternational(VocabularyKeys.INVERSE_OPERATION));
136:
137: /**
138: * Shortcut to identified object constants.
139: *
140: * @todo Replace by a static import when we will be allowed to compile with J2SE 1.5.
141: */
142: private static final String NAME_KEY = IdentifiedObject.NAME_KEY;
143:
144: /**
145: * The set of helper methods on factories.
146: *
147: * @see #getFactoryGroup
148: */
149: private final FactoryGroup factories;
150:
151: /**
152: * The underlying math transform factory. This factory is used
153: * for constructing {@link MathTransform} objects for all
154: * {@linkplain CoordinateOperation coordinate operations}.
155: *
156: * @see #getMathTransformFactory
157: */
158: private final MathTransformFactory mtFactory;
159:
160: /**
161: * A pool of coordinate operation. This pool is used in order
162: * to returns instance of existing operations when possible.
163: */
164: private final CanonicalSet pool = new CanonicalSet();
165:
166: /**
167: * Tells if {@link FactoryGroup#hints} has been invoked. It must be invoked exactly once,
168: * but can't be invoked in the constructor because it causes a {@link StackOverflowError}
169: * in some situations.
170: */
171: private boolean hintsInitialized;
172:
173: /**
174: * Constructs a coordinate operation factory using the specified hints.
175: * This constructor recognizes the {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS},
176: * {@link Hints#DATUM_FACTORY DATUM} and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM}
177: * {@code FACTORY} hints.
178: *
179: * @param userHints The hints, or {@code null} if none.
180: */
181: public AbstractCoordinateOperationFactory(final Hints userHints) {
182: this (userHints, NORMAL_PRIORITY);
183: }
184:
185: /**
186: * Constructs a coordinate operation factory using the specified hints and priority.
187: * This constructor recognizes the {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS},
188: * {@link Hints#DATUM_FACTORY DATUM} and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM}
189: * {@code FACTORY} hints.
190: *
191: * @param userHints The hints, or {@code null} if none.
192: * @param priority The priority for this factory, as a number between
193: * {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and
194: * {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive.
195: *
196: * @since 2.2
197: */
198: public AbstractCoordinateOperationFactory(final Hints userHints,
199: final int priority) {
200: super (priority);
201: factories = FactoryGroup.createInstance(userHints);
202: mtFactory = factories.getMathTransformFactory();
203: }
204:
205: /**
206: * If the specified factory is an instance of {@code AbstractCoordinateOperationFactory},
207: * fetch the {@link FactoryGroup} from this instance instead of from the hints. This
208: * constructor is strictly reserved for factory subclasses that are wrapper around an
209: * other factory, like {@link BufferedCoordinateOperationFactory}.
210: */
211: AbstractCoordinateOperationFactory(
212: final CoordinateOperationFactory factory,
213: final Hints hints, final int priority) {
214: super (priority);
215: if (factory instanceof AbstractCoordinateOperationFactory) {
216: factories = ((AbstractCoordinateOperationFactory) factory)
217: .getFactoryGroup();
218: } else {
219: factories = FactoryGroup.createInstance(hints);
220: }
221: mtFactory = factories.getMathTransformFactory();
222: }
223:
224: /**
225: * Returns the implementation hints for this factory. The returned map contains values for
226: * {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS}, {@link Hints#DATUM_FACTORY DATUM}
227: * and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM} {@code FACTORY} hints. Other values
228: * may be provided as well, at implementation choice.
229: */
230: public Map getImplementationHints() {
231: synchronized (hints) { // Note: avoid lock on public object.
232: if (!hintsInitialized) {
233: initializeHints();
234: hintsInitialized = true; // Set only after success.
235: }
236: }
237: return super .getImplementationHints();
238: }
239:
240: /**
241: * Invoked when the {@link #hints} map should be initialized. This method may
242: * be overridden by subclasses like {@link BufferedCoordinateOperationFactory}.
243: */
244: void initializeHints() {
245: assert Thread.holdsLock(hints);
246: final FactoryGroup factories = getFactoryGroup();
247: final Map factoryGroupHints = factories
248: .getImplementationHints();
249: hints.putAll(factoryGroupHints);
250: }
251:
252: /**
253: * Returns the underlying math transform factory. This factory
254: * is used for constructing {@link MathTransform} objects for
255: * all {@linkplain CoordinateOperation coordinate operations}.
256: */
257: public final MathTransformFactory getMathTransformFactory() {
258: return mtFactory;
259: }
260:
261: /**
262: * Returns the set of helper methods on factories.
263: */
264: final FactoryGroup getFactoryGroup() {
265: return factories;
266: }
267:
268: /**
269: * Returns an affine transform between two coordinate systems. Only units and
270: * axis order (e.g. transforming from (NORTH,WEST) to (EAST,NORTH)) are taken
271: * in account.
272: * <p>
273: * Example: If coordinates in {@code sourceCS} are (x,y) pairs in metres and
274: * coordinates in {@code targetCS} are (-y,x) pairs in centimetres, then the
275: * transformation can be performed as below:
276: *
277: * <pre><blockquote>
278: * [-y(cm)] [ 0 -100 0 ] [x(m)]
279: * [ x(cm)] = [ 100 0 0 ] [y(m)]
280: * [ 1 ] [ 0 0 1 ] [1 ]
281: * </blockquote></pre>
282: *
283: * @param sourceCS The source coordinate system.
284: * @param targetCS The target coordinate system.
285: * @return The transformation from {@code sourceCS} to {@code targetCS} as
286: * an affine transform. Only axis orientation and units are taken in account.
287: * @throws OperationNotFoundException If the affine transform can't be constructed.
288: *
289: * @see AbstractCS#swapAndScaleAxis
290: */
291: protected Matrix swapAndScaleAxis(final CoordinateSystem sourceCS,
292: final CoordinateSystem targetCS)
293: throws OperationNotFoundException {
294: try {
295: return AbstractCS.swapAndScaleAxis(sourceCS, targetCS);
296: } catch (IllegalArgumentException exception) {
297: throw new OperationNotFoundException(getErrorMessage(
298: sourceCS, targetCS), exception);
299: } catch (ConversionException exception) {
300: throw new OperationNotFoundException(getErrorMessage(
301: sourceCS, targetCS), exception);
302: }
303: // No attempt to catch ClassCastException since such
304: // exception would indicates a programming error.
305: }
306:
307: /**
308: * Returns the specified identifier in a map to be given to coordinate operation constructors.
309: * In the special case where the {@code name} identifier is {@link #DATUM_SHIFT} or
310: * {@link #ELLIPSOID_SHIFT}, the map will contains extra informations like positional
311: * accuracy.
312: *
313: * @todo In the datum shift case, an operation version is mandatory but unknow at this time.
314: * However, we noticed that the EPSG database do not always defines a version neither.
315: * Consequently, the Geotools implementation relax the rule requirying an operation
316: * version and we do not try to provide this information here for now.
317: */
318: private static Map getProperties(final ReferenceIdentifier name) {
319: final Map properties;
320: if (name == DATUM_SHIFT || name == ELLIPSOID_SHIFT) {
321: properties = new HashMap(4);
322: properties.put(NAME_KEY, name);
323: properties
324: .put(
325: CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY,
326: new org.opengis.metadata.quality.PositionalAccuracy[] { name == DATUM_SHIFT ? PositionalAccuracyImpl.DATUM_SHIFT_APPLIED
327: : PositionalAccuracyImpl.DATUM_SHIFT_OMITTED });
328: } else {
329: properties = Collections.singletonMap(NAME_KEY, name);
330: }
331: return properties;
332: }
333:
334: /**
335: * Creates a coordinate operation from a matrix, which usually describes an affine tranform.
336: * A default {@link OperationMethod} object is given to this transform. In the special case
337: * where the {@code name} identifier is {@link #DATUM_SHIFT} or {@link #ELLIPSOID_SHIFT},
338: * the operation will be an instance of {@link Transformation} instead of the usual
339: * {@link Conversion}.
340: *
341: * @param name The identifier for the operation to be created.
342: * @param sourceCRS The source coordinate reference system.
343: * @param targetCRS The target coordinate reference system.
344: * @param matrix The matrix which describe an affine transform operation.
345: * @return The conversion or transformation.
346: * @throws FactoryException if the operation can't be created.
347: */
348: protected CoordinateOperation createFromAffineTransform(
349: final ReferenceIdentifier name,
350: final CoordinateReferenceSystem sourceCRS,
351: final CoordinateReferenceSystem targetCRS,
352: final Matrix matrix) throws FactoryException {
353: final MathTransform transform = mtFactory
354: .createAffineTransform(matrix);
355: final Map properties = getProperties(name);
356: final Class type = properties
357: .containsKey(CoordinateOperation.POSITIONAL_ACCURACY_KEY) ? Transformation.class
358: : Conversion.class;
359: return createFromMathTransform(properties, sourceCRS,
360: targetCRS, transform,
361: ProjectiveTransform.ProviderAffine.getProvider(
362: transform.getSourceDimensions(), transform
363: .getTargetDimensions()), type);
364: }
365:
366: /**
367: * Creates a coordinate operation from a set of parameters.
368: * The {@linkplain OperationMethod operation method} is inferred automatically,
369: * if possible.
370: *
371: * @param name The identifier for the operation to be created.
372: * @param sourceCRS The source coordinate reference system.
373: * @param targetCRS The target coordinate reference system.
374: * @param parameters The parameters.
375: * @return The conversion or transformation.
376: * @throws FactoryException if the operation can't be created.
377: */
378: protected CoordinateOperation createFromParameters(
379: final ReferenceIdentifier name,
380: final CoordinateReferenceSystem sourceCRS,
381: final CoordinateReferenceSystem targetCRS,
382: final ParameterValueGroup parameters)
383: throws FactoryException {
384: final Map properties = getProperties(name);
385: final FactoryGroup factories = getFactoryGroup();
386: final MathTransform transform = factories
387: .createParameterizedTransform(parameters);
388: final OperationMethod method = factories.getLastUsedMethod();
389: return createFromMathTransform(properties, sourceCRS,
390: targetCRS, transform, method, Operation.class);
391: }
392:
393: /**
394: * Creates a coordinate operation from a math transform.
395: *
396: * @param name The identifier for the operation to be created.
397: * @param sourceCRS The source coordinate reference system.
398: * @param targetCRS The destination coordinate reference system.
399: * @param transform The math transform.
400: * @return A coordinate operation using the specified math transform.
401: * @throws FactoryException if the operation can't be constructed.
402: */
403: protected CoordinateOperation createFromMathTransform(
404: final ReferenceIdentifier name,
405: final CoordinateReferenceSystem sourceCRS,
406: final CoordinateReferenceSystem targetCRS,
407: final MathTransform transform) throws FactoryException {
408: return createFromMathTransform(Collections.singletonMap(
409: NAME_KEY, name), sourceCRS, targetCRS, transform, null,
410: CoordinateOperation.class);
411: }
412:
413: /**
414: * Creates a coordinate operation from a math transform.
415: * If the specified math transform is already a coordinate operation, and if source
416: * and target CRS match, then {@code transform} is returned with no change.
417: * Otherwise, a new coordinate operation is created.
418: *
419: * @param properties The properties to give to the operation.
420: * @param sourceCRS The source coordinate reference system.
421: * @param targetCRS The destination coordinate reference system.
422: * @param transform The math transform.
423: * @param method The operation method, or {@code null}.
424: * @param type The required super-class (e.g. <code>{@linkplain Transformation}.class</code>).
425: * @return A coordinate operation using the specified math transform.
426: * @throws FactoryException if the operation can't be constructed.
427: */
428: protected CoordinateOperation createFromMathTransform(
429: final Map properties,
430: final CoordinateReferenceSystem sourceCRS,
431: final CoordinateReferenceSystem targetCRS,
432: final MathTransform transform,
433: final OperationMethod method, final Class type)
434: throws FactoryException {
435: CoordinateOperation operation;
436: if (transform instanceof CoordinateOperation) {
437: operation = (CoordinateOperation) transform;
438: if (Utilities.equals(operation.getSourceCRS(), sourceCRS)
439: && Utilities.equals(operation.getTargetCRS(),
440: targetCRS)
441: && Utilities.equals(operation.getMathTransform(),
442: transform)) {
443: if (operation instanceof Operation) {
444: if (Utilities.equals(((Operation) operation)
445: .getMethod(), method)) {
446: return operation;
447: }
448: } else {
449: return operation;
450: }
451: }
452: }
453: operation = DefaultOperation.create(properties, sourceCRS,
454: targetCRS, transform, method, type);
455: operation = (CoordinateOperation) pool.unique(operation);
456: return operation;
457: }
458:
459: /**
460: * Constructs a defining conversion from a set of properties.
461: *
462: * @param properties Set of properties. Should contains at least {@code "name"}.
463: * @param method The operation method.
464: * @param parameters The parameter values.
465: * @return The defining conversion.
466: * @throws FactoryException if the object creation failed.
467: *
468: * @see DefiningConversion
469: *
470: * @since 2.4
471: */
472: public Conversion createDefiningConversion(final Map properties,
473: final OperationMethod method,
474: final ParameterValueGroup parameters)
475: throws FactoryException {
476: Conversion conversion = new DefiningConversion(properties,
477: method, parameters);
478: conversion = (Conversion) pool.unique(conversion);
479: return conversion;
480: }
481:
482: /**
483: * Creates a concatenated operation from a sequence of operations.
484: *
485: * @param properties Set of properties. Should contains at least {@code "name"}.
486: * @param operations The sequence of operations.
487: * @return The concatenated operation.
488: * @throws FactoryException if the object creation failed.
489: */
490: public CoordinateOperation createConcatenatedOperation(
491: Map properties, CoordinateOperation[] operations)
492: throws FactoryException {
493: CoordinateOperation operation;
494: operation = new DefaultConcatenatedOperation(properties,
495: operations, mtFactory);
496: operation = (CoordinateOperation) pool.unique(operation);
497: return operation;
498: }
499:
500: /**
501: * Concatenate two operation steps. If an operation is an {@link #AXIS_CHANGES},
502: * it will be included as part of the second operation instead of creating an
503: * {@link ConcatenatedOperation}. If a concatenated operation is created, it
504: * will get an automatically generated name.
505: *
506: * @param step1 The first step, or {@code null} for the identity operation.
507: * @param step2 The second step, or {@code null} for the identity operation.
508: * @return A concatenated operation, or {@code null} if all arguments was nul.
509: * @throws FactoryException if the operation can't be constructed.
510: */
511: protected CoordinateOperation concatenate(
512: final CoordinateOperation step1,
513: final CoordinateOperation step2) throws FactoryException {
514: if (step1 == null)
515: return step2;
516: if (step2 == null)
517: return step1;
518: if (false) {
519: // Note: we sometime get this assertion failure if the user provided CRS with two
520: // different ellipsoids but an identical TOWGS84 conversion infos (which is
521: // usually wrong, but still happen).
522: assert equalsIgnoreMetadata(step1.getTargetCRS(), step2
523: .getSourceCRS()) : "CRS 1 =" + step1.getTargetCRS()
524: + '\n' + "CRS 2 =" + step2.getSourceCRS();
525: }
526: if (isIdentity(step1))
527: return step2;
528: if (isIdentity(step2))
529: return step1;
530: final MathTransform mt1 = step1.getMathTransform();
531: final MathTransform mt2 = step2.getMathTransform();
532: final CoordinateReferenceSystem sourceCRS = step1
533: .getSourceCRS();
534: final CoordinateReferenceSystem targetCRS = step2
535: .getTargetCRS();
536: CoordinateOperation step = null;
537: if (step1.getName() == AXIS_CHANGES
538: && mt1.getSourceDimensions() == mt1
539: .getTargetDimensions())
540: step = step2;
541: if (step2.getName() == AXIS_CHANGES
542: && mt2.getSourceDimensions() == mt2
543: .getTargetDimensions())
544: step = step1;
545: if (step instanceof Operation) {
546: /*
547: * Applies only on operation in order to avoid merging with PassThroughOperation.
548: * Also applies only if the transform to hide has identical source and target
549: * dimensions in order to avoid mismatch with the method's dimensions.
550: */
551: return createFromMathTransform(AbstractIdentifiedObject
552: .getProperties(step), sourceCRS, targetCRS,
553: mtFactory.createConcatenatedTransform(mt1, mt2),
554: ((Operation) step).getMethod(),
555: CoordinateOperation.class);
556: }
557: return createConcatenatedOperation(getTemporaryName(sourceCRS,
558: targetCRS), new CoordinateOperation[] { step1, step2 });
559: }
560:
561: /**
562: * Concatenate three transformation steps. If the first and/or the last operation is an
563: * {@link #AXIS_CHANGES}, it will be included as part of the second operation instead of
564: * creating an {@link ConcatenatedOperation}. If a concatenated operation is created, it
565: * will get an automatically generated name.
566: *
567: * @param step1 The first step, or {@code null} for the identity operation.
568: * @param step2 The second step, or {@code null} for the identity operation.
569: * @param step3 The third step, or {@code null} for the identity operation.
570: * @return A concatenated operation, or {@code null} if all arguments were null.
571: * @throws FactoryException if the operation can't be constructed.
572: */
573: protected CoordinateOperation concatenate(
574: final CoordinateOperation step1,
575: final CoordinateOperation step2,
576: final CoordinateOperation step3) throws FactoryException {
577: if (step1 == null)
578: return concatenate(step2, step3);
579: if (step2 == null)
580: return concatenate(step1, step3);
581: if (step3 == null)
582: return concatenate(step1, step2);
583: assert equalsIgnoreMetadata(step1.getTargetCRS(), step2
584: .getSourceCRS()) : step1;
585: assert equalsIgnoreMetadata(step2.getTargetCRS(), step3
586: .getSourceCRS()) : step3;
587:
588: if (isIdentity(step1))
589: return concatenate(step2, step3);
590: if (isIdentity(step2))
591: return concatenate(step1, step3);
592: if (isIdentity(step3))
593: return concatenate(step1, step2);
594: if (step1.getName() == AXIS_CHANGES)
595: return concatenate(concatenate(step1, step2), step3);
596: if (step3.getName() == AXIS_CHANGES)
597: return concatenate(step1, concatenate(step2, step3));
598: final CoordinateReferenceSystem sourceCRS = step1
599: .getSourceCRS();
600: final CoordinateReferenceSystem targetCRS = step3
601: .getTargetCRS();
602: return createConcatenatedOperation(getTemporaryName(sourceCRS,
603: targetCRS), new CoordinateOperation[] { step1, step2,
604: step3 });
605: }
606:
607: /**
608: * Returns {@code true} if the specified operation is an identity conversion.
609: * This method always returns {@code false} for transformations even if their
610: * associated math transform is an identity one, because such transformations
611: * are usually datum shift and must be visible.
612: */
613: private static boolean isIdentity(
614: final CoordinateOperation operation) {
615: return (operation instanceof Conversion)
616: && operation.getMathTransform().isIdentity();
617: }
618:
619: /**
620: * Returns the inverse of the specified operation.
621: *
622: * @param operation The operation to invert.
623: * @return The inverse of {@code operation}.
624: * @throws NoninvertibleTransformException if the operation is not invertible.
625: * @throws FactoryException if the operation creation failed for an other reason.
626: *
627: * @since 2.3
628: */
629: protected CoordinateOperation inverse(
630: final CoordinateOperation operation)
631: throws NoninvertibleTransformException, FactoryException {
632: final CoordinateReferenceSystem sourceCRS = operation
633: .getSourceCRS();
634: final CoordinateReferenceSystem targetCRS = operation
635: .getTargetCRS();
636: final Map properties = AbstractIdentifiedObject.getProperties(
637: operation, null);
638: properties.putAll(getTemporaryName(targetCRS, sourceCRS));
639: if (operation instanceof ConcatenatedOperation) {
640: final LinkedList inverted = new LinkedList/*<CoordinateOperation>*/();
641: for (final Iterator it = ((ConcatenatedOperation) operation)
642: .getOperations().iterator(); it.hasNext();) {
643: inverted.addFirst(inverse((CoordinateOperation) it
644: .next()));
645: }
646: return createConcatenatedOperation(properties,
647: (CoordinateOperation[]) inverted
648: .toArray(new CoordinateOperation[inverted
649: .size()]));
650: }
651: final MathTransform transform = operation.getMathTransform()
652: .inverse();
653: final Class type = AbstractCoordinateOperation
654: .getType(operation);
655: final OperationMethod method = (operation instanceof Operation) ? ((Operation) operation)
656: .getMethod()
657: : null;
658: return createFromMathTransform(properties, targetCRS,
659: sourceCRS, transform, method, type);
660: }
661:
662: /////////////////////////////////////////////////////////////////////////////////
663: /////////////////////////////////////////////////////////////////////////////////
664: //////////// ////////////
665: //////////// M I S C E L L A N E O U S ////////////
666: //////////// ////////////
667: /////////////////////////////////////////////////////////////////////////////////
668: /////////////////////////////////////////////////////////////////////////////////
669:
670: /**
671: * Returns the dimension of the specified coordinate system,
672: * or {@code 0} if the coordinate system is null.
673: */
674: static int getDimension(final CoordinateReferenceSystem crs) {
675: return (crs != null) ? crs.getCoordinateSystem().getDimension()
676: : 0;
677: }
678:
679: /**
680: * An identifier for temporary objects. This identifier manage a count of temporary
681: * identifier. The count is appended to the identifier name (e.g. "WGS84 (step 1)").
682: */
683: private static final class TemporaryIdentifier extends
684: NamedIdentifier {
685: /** The parent identifier. */
686: private final ReferenceIdentifier parent;
687:
688: /** The temporary object count. */
689: private final int count;
690:
691: /** Constructs an identifier derived from the specified one. */
692: public TemporaryIdentifier(final ReferenceIdentifier parent) {
693: this (
694: parent,
695: ((parent instanceof TemporaryIdentifier) ? ((TemporaryIdentifier) parent).count
696: : 0) + 1);
697: }
698:
699: /** Work around for RFE #4093999 in Sun's bug database */
700: private TemporaryIdentifier(final ReferenceIdentifier parent,
701: final int count) {
702: super (Citations.GEOTOOLS, unwrap(parent).getCode()
703: + " (step " + count + ')');
704: this .parent = parent;
705: this .count = count;
706: }
707:
708: /** Returns the parent identifier for the specified identifier, if any. */
709: public static ReferenceIdentifier unwrap(
710: ReferenceIdentifier identifier) {
711: while (identifier instanceof TemporaryIdentifier) {
712: identifier = ((TemporaryIdentifier) identifier).parent;
713: }
714: return identifier;
715: }
716: }
717:
718: /**
719: * Returns the name of the GeoAPI interface implemented by the specified object.
720: * In addition, the name may be added between brackets.
721: */
722: private static String getClassName(final IdentifiedObject object) {
723: if (object != null) {
724: Class type = object.getClass();
725: final Class[] interfaces = type.getInterfaces();
726: for (int i = 0; i < interfaces.length; i++) {
727: final Class candidate = interfaces[i];
728: if (candidate.getName().startsWith(
729: "org.opengis.referencing.")) {
730: type = candidate;
731: break;
732: }
733: }
734: String name = Utilities.getShortName(type);
735: final ReferenceIdentifier id = object.getName();
736: if (id != null) {
737: name = name + '[' + id.getCode() + ']';
738: }
739: return name;
740: }
741: return null;
742: }
743:
744: /**
745: * Returns a temporary name for object derived from the specified one.
746: *
747: * @param source The CRS to base name on, or {@code null} if none.
748: */
749: static Map getTemporaryName(final IdentifiedObject source) {
750: final Map properties = new HashMap(4);
751: properties.put(NAME_KEY, new TemporaryIdentifier(source
752: .getName()));
753: properties.put(IdentifiedObject.REMARKS_KEY, Vocabulary
754: .formatInternational(VocabularyKeys.DERIVED_FROM_$1,
755: getClassName(source)));
756: return properties;
757: }
758:
759: /**
760: * Returns a temporary name for object derived from a concatenation.
761: *
762: * @param source The CRS to base name on, or {@code null} if none.
763: */
764: static Map getTemporaryName(final CoordinateReferenceSystem source,
765: final CoordinateReferenceSystem target) {
766: final String name = getClassName(source) + " \u21E8 "
767: + getClassName(target);
768: return Collections.singletonMap(NAME_KEY, name);
769: }
770:
771: /**
772: * Compares the specified objects for equality. If both objects are Geotools
773: * implementations of {@linkplain AbstractIdentifiedObject},
774: * then this method will ignore the metadata during the comparaison.
775: *
776: * @param object1 The first object to compare (may be null).
777: * @param object2 The second object to compare (may be null).
778: * @return {@code true} if both objects are equals.
779: *
780: * @todo This method may be insuffisient, since it will returns {@code false} for
781: * two different implementations, even if they encapsulate the same data values.
782: */
783: static boolean equalsIgnoreMetadata(final IdentifiedObject object1,
784: final IdentifiedObject object2) {
785: return CRS.equalsIgnoreMetadata(object1, object2);
786: }
787:
788: /**
789: * Returns an error message for "No path found from sourceCRS to targetCRS".
790: * This is used for the construction of {@link OperationNotFoundException}.
791: *
792: * @param source The source CRS.
793: * @param target The target CRS.
794: * @return A default error message.
795: */
796: protected static String getErrorMessage(
797: final IdentifiedObject source, final IdentifiedObject target) {
798: return Errors.format(ErrorKeys.NO_TRANSFORMATION_PATH_$2,
799: getClassName(source), getClassName(target));
800: }
801:
802: /**
803: * Makes sure an argument is non-null.
804: *
805: * @param name Argument name.
806: * @param object User argument.
807: * @throws IllegalArgumentException if {@code object} is null.
808: */
809: protected static void ensureNonNull(final String name,
810: final Object object) throws IllegalArgumentException {
811: if (object == null) {
812: throw new IllegalArgumentException(Errors.format(
813: ErrorKeys.NULL_ARGUMENT_$1, name));
814: }
815: }
816: }
|