001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2004, 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
023: import java.util.ArrayList;
024: import java.util.Collection;
025: import java.util.Collections;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import java.util.LinkedHashSet;
029: import java.util.List;
030: import java.util.Map;
031: import java.util.Set;
032:
033: // OpenGIS dependencies
034: import org.opengis.metadata.quality.PositionalAccuracy;
035: import org.opengis.referencing.FactoryException;
036: import org.opengis.referencing.crs.CoordinateReferenceSystem;
037: import org.opengis.referencing.operation.SingleOperation;
038: import org.opengis.referencing.operation.CoordinateOperation;
039: import org.opengis.referencing.operation.ConcatenatedOperation;
040: import org.opengis.referencing.operation.Transformation;
041: import org.opengis.referencing.operation.MathTransform;
042: import org.opengis.referencing.operation.MathTransformFactory;
043:
044: // Geotools dependencies
045: import org.geotools.referencing.wkt.Formatter;
046: import org.geotools.referencing.AbstractIdentifiedObject;
047: import org.geotools.referencing.operation.transform.ConcatenatedTransform;
048: import org.geotools.resources.Utilities;
049: import org.geotools.resources.i18n.Errors;
050: import org.geotools.resources.i18n.ErrorKeys;
051:
052: /**
053: * An ordered sequence of two or more single coordinate operations. The sequence of operations is
054: * constrained by the requirement that the source coordinate reference system of step
055: * (<var>n</var>+1) must be the same as the target coordinate reference system of step
056: * (<var>n</var>). The source coordinate reference system of the first step and the target
057: * coordinate reference system of the last step are the source and target coordinate reference
058: * system associated with the concatenated operation.
059: *
060: * @since 2.1
061: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/DefaultConcatenatedOperation.java $
062: * @version $Id: DefaultConcatenatedOperation.java 28264 2007-12-05 21:53:08Z desruisseaux $
063: * @author Martin Desruisseaux
064: */
065: public class DefaultConcatenatedOperation extends
066: AbstractCoordinateOperation implements ConcatenatedOperation {
067: /**
068: * Serial number for interoperability with different versions.
069: */
070: private static final long serialVersionUID = 4199619838029045700L;
071:
072: /**
073: * The sequence of operations.
074: */
075: private final List/*<SingleOperation>*/operations;
076:
077: /**
078: * Constructs a concatenated operation from the specified name.
079: *
080: * @param name The operation name.
081: * @param operations The sequence of operations.
082: */
083: public DefaultConcatenatedOperation(final String name,
084: final CoordinateOperation[] operations) {
085: this (Collections.singletonMap(NAME_KEY, name), operations);
086: }
087:
088: /**
089: * Constructs a concatenated operation from a set of properties.
090: * The properties given in argument follow the same rules than for the
091: * {@link AbstractCoordinateOperation} constructor.
092: *
093: * @param properties Set of properties. Should contains at least <code>"name"</code>.
094: * @param operations The sequence of operations.
095: */
096: public DefaultConcatenatedOperation(final Map properties,
097: final CoordinateOperation[] operations) {
098: this (properties, new ArrayList(
099: operations != null ? operations.length : 4), operations);
100: }
101:
102: /**
103: * Constructs a concatenated operation from a set of properties and a
104: * {@linkplain MathTransformFactory math transform factory}.
105: * The properties given in argument follow the same rules than for the
106: * {@link AbstractCoordinateOperation} constructor.
107: *
108: * @param properties Set of properties. Should contains at least <code>"name"</code>.
109: * @param operations The sequence of operations.
110: * @param factory The math transform factory to use for math transforms concatenation.
111: * @throws FactoryException if the factory can't concatenate the math transforms.
112: */
113: public DefaultConcatenatedOperation(final Map properties,
114: final CoordinateOperation[] operations,
115: final MathTransformFactory factory) throws FactoryException {
116: this (properties, new ArrayList(
117: operations != null ? operations.length : 4),
118: operations, factory);
119: }
120:
121: /**
122: * Work around for RFE #4093999 in Sun's bug database
123: * ("Relax constraint on placement of this()/super() call in constructors").
124: */
125: private DefaultConcatenatedOperation(final Map properties,
126: final ArrayList list, final CoordinateOperation[] operations) {
127: this (properties, expand(operations, list), list);
128: }
129:
130: /**
131: * Work around for RFE #4093999 in Sun's bug database
132: * ("Relax constraint on placement of this()/super() call in constructors").
133: */
134: private DefaultConcatenatedOperation(final Map properties,
135: final ArrayList list,
136: final CoordinateOperation[] operations,
137: final MathTransformFactory factory) throws FactoryException {
138: this (properties, expand(operations, list, factory, true), list);
139: }
140:
141: /**
142: * Work around for RFE #4093999 in Sun's bug database
143: * ("Relax constraint on placement of this()/super() call in constructors").
144: */
145: private DefaultConcatenatedOperation(final Map properties,
146: final MathTransform transform,
147: final ArrayList/*<SingleOperation>*/operations) {
148: super (mergeAccuracy(properties, operations),
149: ((SingleOperation) operations.get(0)).getSourceCRS(),
150: ((SingleOperation) operations
151: .get(operations.size() - 1)).getTargetCRS(),
152: transform);
153: operations.trimToSize();
154: this .operations = Collections.unmodifiableList(operations); // No need to clone.
155: }
156:
157: /**
158: * Work around for RFE #4093999 in Sun's bug database
159: * ("Relax constraint on placement of this()/super() call in constructors").
160: */
161: private static MathTransform expand(
162: final CoordinateOperation[] operations, final List list) {
163: try {
164: return expand(operations, list, null, true);
165: } catch (FactoryException exception) {
166: // Should not happen, since we didn't used any MathTransformFactory.
167: throw new AssertionError(exception);
168: }
169: }
170:
171: /**
172: * Transforms the list of operations into a list of single operations. This method
173: * also check against null value and make sure that all CRS dimension matches.
174: *
175: * @param operations The array of operations to expand.
176: * @param list The list in which to add {@code SingleOperation}.
177: * @param factory The math transform factory to use, or {@code null}
178: * @param wantTransform {@code true} if the concatenated math transform should be computed.
179: * @return The concatenated math transform.
180: * @throws FactoryException if the factory can't concatenate the math transforms.
181: */
182: private static MathTransform expand(
183: final CoordinateOperation[] operations, final List list,
184: final MathTransformFactory factory,
185: final boolean wantTransform) throws FactoryException {
186: MathTransform transform = null;
187: ensureNonNull("operations", operations);
188: for (int i = 0; i < operations.length; i++) {
189: ensureNonNull("operations", operations, i);
190: final CoordinateOperation op = operations[i];
191: if (op instanceof SingleOperation) {
192: list.add(op);
193: } else if (op instanceof ConcatenatedOperation) {
194: final ConcatenatedOperation cop = (ConcatenatedOperation) op;
195: final List cops = cop.getOperations();
196: expand((CoordinateOperation[]) cops
197: .toArray(new CoordinateOperation[cops.size()]),
198: list, factory, false);
199: } else {
200: throw new IllegalArgumentException(Errors.format(
201: ErrorKeys.ILLEGAL_CLASS_$2, Utilities
202: .getShortClassName(op), Utilities
203: .getShortName(SingleOperation.class)));
204: }
205: /*
206: * Check the CRS dimensions.
207: */
208: if (i != 0) {
209: final CoordinateReferenceSystem previous = operations[i - 1]
210: .getTargetCRS();
211: final CoordinateReferenceSystem next = op
212: .getSourceCRS();
213: if (previous != null && next != null) {
214: final int dim1 = previous.getCoordinateSystem()
215: .getDimension();
216: final int dim2 = next.getCoordinateSystem()
217: .getDimension();
218: if (dim1 != dim2) {
219: throw new IllegalArgumentException(
220: Errors
221: .format(
222: ErrorKeys.MISMATCHED_DIMENSION_$2,
223: new Integer(dim1),
224: new Integer(dim2)));
225: }
226: }
227: }
228: /*
229: * Concatenates the math transform.
230: */
231: if (wantTransform) {
232: final MathTransform step = op.getMathTransform();
233: if (transform == null) {
234: transform = step;
235: } else if (factory != null) {
236: transform = factory.createConcatenatedTransform(
237: transform, step);
238: } else {
239: transform = ConcatenatedTransform.create(transform,
240: step);
241: }
242: }
243: }
244: if (wantTransform) {
245: final int size = list.size();
246: if (size <= 1) {
247: throw new IllegalArgumentException(Errors.format(
248: ErrorKeys.MISSING_PARAMETER_$1, "operations["
249: + size + ']'));
250: }
251: }
252: return transform;
253: }
254:
255: /**
256: * If no accuracy were specified in the given properties map, add all accuracies found in the
257: * operation to concatenate. This method considers only {@link Transformation} components and
258: * ignores all conversions. According ISO 19111, the accuracy attribute is allowed only for
259: * transformations. However, this restriction is not enforced everywhere. The EPSG database
260: * declares an accuracy of 0 meters for conversions, which is conceptually exact. Ourself we
261: * are departing from the specification, since we are adding accuracy informations to a
262: * concatenated operation. This departure should be considered as a convenience feature
263: * only; accuracies are really relevant in transformations only.
264: * <p>
265: * There is also a technical reasons for ignoring conversions. If a concatenated operation
266: * contains a datum shift (i.e. a transformation) with unknow accuracy, and a projection
267: * (i.e. a conversion) with a declared 0 meter error, we don't want to declare this 0 meter
268: * error as the concatenated operation's accuracy; it would be a false information.
269: * <p>
270: * Note that a concatenated operation typically contains an arbitrary amount of conversions,
271: * but only one transformation. So considering transformation only usually means to pickup
272: * only one operation in the given {@code operations} list.
273: *
274: * @todo We should use a Map and merge only one accuracy for each specification.
275: */
276: private static Map mergeAccuracy(final Map properties,
277: final List/*<CoordinateOperation>*/operations) {
278: if (!properties.containsKey(COORDINATE_OPERATION_ACCURACY_KEY)) {
279: Set accuracy = null;
280: for (final Iterator it = operations.iterator(); it
281: .hasNext();) {
282: final CoordinateOperation op = (CoordinateOperation) it
283: .next();
284: if (op instanceof Transformation) {
285: // See javadoc for a rational why we take only transformations in account.
286: final Collection/*<PositionalAccuracy>*/candidates = op
287: .getPositionalAccuracy();
288: if (candidates != null && !candidates.isEmpty()) {
289: if (accuracy == null) {
290: accuracy = new LinkedHashSet();
291: }
292: accuracy.addAll(candidates);
293: }
294: }
295: }
296: if (accuracy != null) {
297: final Map merged = new HashMap(properties);
298: merged
299: .put(
300: COORDINATE_OPERATION_ACCURACY_KEY,
301: accuracy
302: .toArray(new PositionalAccuracy[accuracy
303: .size()]));
304: return merged;
305: }
306: }
307: return properties;
308: }
309:
310: /**
311: * Returns the sequence of operations.
312: */
313: public List/*<SingleOperation>*/getOperations() {
314: return operations;
315: }
316:
317: /**
318: * Compare this concatenated operation with the specified object for equality.
319: * If {@code compareMetadata} is {@code true}, then all available properties are
320: * compared including {@linkplain #getValidArea valid area} and {@linkplain #getScope scope}.
321: *
322: * @param object The object to compare to {@code this}.
323: * @param compareMetadata {@code true} for performing a strict comparaison, or
324: * {@code false} for comparing only properties relevant to transformations.
325: * @return {@code true} if both objects are equal.
326: */
327: public boolean equals(final AbstractIdentifiedObject object,
328: final boolean compareMetadata) {
329: if (object == this ) {
330: return true; // Slight optimization.
331: }
332: if (super .equals(object, compareMetadata)) {
333: final DefaultConcatenatedOperation that = (DefaultConcatenatedOperation) object;
334: return equals(this .operations, that.operations,
335: compareMetadata);
336: }
337: return false;
338: }
339:
340: /**
341: * Returns a hash code value for this concatenated operation.
342: */
343: public int hashCode() {
344: return operations.hashCode() ^ (int) serialVersionUID;
345: }
346:
347: /**
348: * {@inheritDoc}
349: */
350: protected String formatWKT(final Formatter formatter) {
351: final String label = super .formatWKT(formatter);
352: for (final Iterator it = operations.iterator(); it.hasNext();) {
353: formatter.append((CoordinateOperation) it.next());
354: }
355: return label;
356: }
357: }
|