1 | /*
|
---|
2 | * GeoTools - The Open Source Java GIS Toolkit
|
---|
3 | * http://geotools.org
|
---|
4 | *
|
---|
5 | * (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
|
---|
6 | *
|
---|
7 | * This library is free software; you can redistribute it and/or
|
---|
8 | * modify it under the terms of the GNU Lesser General Public
|
---|
9 | * License as published by the Free Software Foundation;
|
---|
10 | * version 2.1 of the License.
|
---|
11 | *
|
---|
12 | * This library is distributed in the hope that it will be useful,
|
---|
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
15 | * Lesser General Public License for more details.
|
---|
16 | *
|
---|
17 | * This package contains documentation from OpenGIS specifications.
|
---|
18 | * OpenGIS consortium's work is fully acknowledged here.
|
---|
19 | */
|
---|
20 | package org.geotools.referencing.operation;
|
---|
21 |
|
---|
22 | import java.util.Collection;
|
---|
23 | import java.util.Collections;
|
---|
24 | import java.util.HashMap;
|
---|
25 | import java.util.Map;
|
---|
26 |
|
---|
27 | import org.geotools.referencing.AbstractIdentifiedObject;
|
---|
28 | import org.geotools.referencing.crs.AbstractDerivedCRS;
|
---|
29 | import org.geotools.resources.i18n.ErrorKeys;
|
---|
30 | import org.geotools.resources.i18n.Errors;
|
---|
31 | import org.geotools.util.Utilities;
|
---|
32 | import org.opengis.metadata.extent.Extent;
|
---|
33 | import org.opengis.metadata.quality.PositionalAccuracy;
|
---|
34 | import org.opengis.referencing.crs.CoordinateReferenceSystem;
|
---|
35 | import org.opengis.referencing.operation.ConicProjection;
|
---|
36 | import org.opengis.referencing.operation.Conversion;
|
---|
37 | import org.opengis.referencing.operation.CoordinateOperation;
|
---|
38 | import org.opengis.referencing.operation.CylindricalProjection;
|
---|
39 | import org.opengis.referencing.operation.MathTransform;
|
---|
40 | import org.opengis.referencing.operation.Operation;
|
---|
41 | import org.opengis.referencing.operation.PlanarProjection;
|
---|
42 | import org.opengis.referencing.operation.Projection;
|
---|
43 | import org.opengis.referencing.operation.Transformation;
|
---|
44 | import org.opengis.util.InternationalString;
|
---|
45 |
|
---|
46 |
|
---|
47 | /**
|
---|
48 | * Establishes an association between a source and a target coordinate reference system,
|
---|
49 | * and provides a {@linkplain MathTransform transform} for transforming coordinates in
|
---|
50 | * the source CRS to coordinates in the target CRS. Many but not all coordinate operations (from
|
---|
51 | * {@linkplain CoordinateReferenceSystem coordinate reference system} <VAR>A</VAR> to
|
---|
52 | * {@linkplain CoordinateReferenceSystem coordinate reference system} <VAR>B</VAR>)
|
---|
53 | * also uniquely define the inverse operation (from
|
---|
54 | * {@linkplain CoordinateReferenceSystem coordinate reference system} <VAR>B</VAR> to
|
---|
55 | * {@linkplain CoordinateReferenceSystem coordinate reference system} <VAR>A</VAR>).
|
---|
56 | * In some cases, the operation method algorithm for the inverse operation is the same
|
---|
57 | * as for the forward algorithm, but the signs of some operation parameter values must
|
---|
58 | * be reversed. In other cases, different algorithms are required for the forward and
|
---|
59 | * inverse operations, but the same operation parameter values are used. If (some)
|
---|
60 | * entirely different parameter values are needed, a different coordinate operation
|
---|
61 | * shall be defined.
|
---|
62 | * <p>
|
---|
63 | * This class is conceptually <cite>abstract</cite>, even if it is technically possible to
|
---|
64 | * instantiate it. Typical applications should create instances of the most specific subclass with
|
---|
65 | * {@code Default} prefix instead. An exception to this rule may occurs when it is not possible to
|
---|
66 | * identify the exact type.
|
---|
67 | *
|
---|
68 | * @since 2.1
|
---|
69 | *
|
---|
70 | * @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/modules/library/referencing/src/main/java/org/geotools/referencing/operation/AbstractCoordinateOperation.java $
|
---|
71 | * @version $Id: AbstractCoordinateOperation.java 37299 2011-05-25 05:21:24Z mbedward $
|
---|
72 | * @author Martin Desruisseaux (IRD)
|
---|
73 | */
|
---|
74 | public class AbstractCoordinateOperation extends AbstractIdentifiedObject
|
---|
75 | implements CoordinateOperation
|
---|
76 | {
|
---|
77 | /**
|
---|
78 | * Serial number for interoperability with different versions.
|
---|
79 | */
|
---|
80 | private static final long serialVersionUID = 1237358357729193885L;
|
---|
81 |
|
---|
82 | /**
|
---|
83 | * An empty array of positional accuracy. This is usefull for fetching accuracies as an array,
|
---|
84 | * using the following idiom:
|
---|
85 | * <blockquote><pre>
|
---|
86 | * {@linkplain #getPositionalAccuracy()}.toArray(EMPTY_ACCURACY_ARRAY);
|
---|
87 | * </pre></blockquote>
|
---|
88 | */
|
---|
89 | public static final PositionalAccuracy[] EMPTY_ACCURACY_ARRAY = new PositionalAccuracy[0];
|
---|
90 |
|
---|
91 | /**
|
---|
92 | * List of localizable properties. To be given to {@link AbstractIdentifiedObject} constructor.
|
---|
93 | */
|
---|
94 | private static final String[] LOCALIZABLES = {SCOPE_KEY};
|
---|
95 |
|
---|
96 | /**
|
---|
97 | * The source CRS, or {@code null} if not available.
|
---|
98 | */
|
---|
99 | protected final CoordinateReferenceSystem sourceCRS;
|
---|
100 |
|
---|
101 | /**
|
---|
102 | * The target CRS, or {@code null} if not available.
|
---|
103 | */
|
---|
104 | protected final CoordinateReferenceSystem targetCRS;
|
---|
105 |
|
---|
106 | /**
|
---|
107 | * Version of the coordinate transformation
|
---|
108 | * (i.e., instantiation due to the stochastic nature of the parameters).
|
---|
109 | */
|
---|
110 | final String operationVersion;
|
---|
111 |
|
---|
112 | /**
|
---|
113 | * Estimate(s) of the impact of this operation on point accuracy, or {@code null}
|
---|
114 | * if none.
|
---|
115 | */
|
---|
116 | private final Collection<PositionalAccuracy> coordinateOperationAccuracy;
|
---|
117 |
|
---|
118 | /**
|
---|
119 | * Area in which this operation is valid, or {@code null} if not available.
|
---|
120 | */
|
---|
121 | protected final Extent domainOfValidity;
|
---|
122 |
|
---|
123 | /**
|
---|
124 | * Description of domain of usage, or limitations of usage, for which this operation is valid.
|
---|
125 | */
|
---|
126 | private final InternationalString scope;
|
---|
127 |
|
---|
128 | /**
|
---|
129 | * Transform from positions in the {@linkplain #getSourceCRS source coordinate reference system}
|
---|
130 | * to positions in the {@linkplain #getTargetCRS target coordinate reference system}.
|
---|
131 | */
|
---|
132 | protected final MathTransform transform;
|
---|
133 |
|
---|
134 | /**
|
---|
135 | * Constructs a new coordinate operation with the same values than the specified
|
---|
136 | * defining conversion, together with the specified source and target CRS. This
|
---|
137 | * constructor is used by {@link DefaultConversion} only.
|
---|
138 | */
|
---|
139 | AbstractCoordinateOperation(final Conversion definition,
|
---|
140 | final CoordinateReferenceSystem sourceCRS,
|
---|
141 | final CoordinateReferenceSystem targetCRS,
|
---|
142 | final MathTransform transform)
|
---|
143 | {
|
---|
144 | super(definition);
|
---|
145 | this.sourceCRS = sourceCRS;
|
---|
146 | this.targetCRS = targetCRS;
|
---|
147 | this.operationVersion = definition.getOperationVersion();
|
---|
148 | this.coordinateOperationAccuracy = definition.getCoordinateOperationAccuracy();
|
---|
149 | this.domainOfValidity = definition.getDomainOfValidity();
|
---|
150 | this.scope = definition.getScope();
|
---|
151 | this.transform = transform;
|
---|
152 | }
|
---|
153 |
|
---|
154 | /**
|
---|
155 | * Constructs a coordinate operation from a set of properties.
|
---|
156 | * The properties given in argument follow the same rules than for the
|
---|
157 | * {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}.
|
---|
158 | * Additionally, the following properties are understood by this construtor:
|
---|
159 | * <p>
|
---|
160 | * <table border='1'>
|
---|
161 | * <tr bgcolor="#CCCCFF" class="TableHeadingColor">
|
---|
162 | * <th nowrap>Property name</th>
|
---|
163 | * <th nowrap>Value type</th>
|
---|
164 | * <th nowrap>Value given to</th>
|
---|
165 | * </tr>
|
---|
166 | * <tr>
|
---|
167 | * <td nowrap> {@value org.opengis.referencing.operation.CoordinateOperation#OPERATION_VERSION_KEY} </td>
|
---|
168 | * <td nowrap> {@link String} </td>
|
---|
169 | * <td nowrap> {@link #getOperationVersion}</td>
|
---|
170 | * </tr>
|
---|
171 | * <tr>
|
---|
172 | * <td nowrap> {@value org.opengis.referencing.operation.CoordinateOperation#COORDINATE_OPERATION_ACCURACY_KEY} </td>
|
---|
173 | * <td nowrap> <code>{@linkplain PositionalAccuracy}[]</code> </td>
|
---|
174 | * <td nowrap> {@link #getCoordinateOperationAccuracy}</td>
|
---|
175 | * </tr>
|
---|
176 | * <tr>
|
---|
177 | * <td nowrap> {@value org.opengis.referencing.operation.CoordinateOperation#DOMAIN_OF_VALIDITY_KEY} </td>
|
---|
178 | * <td nowrap> {@link Extent} </td>
|
---|
179 | * <td nowrap> {@link #getDomainOfValidity}</td>
|
---|
180 | * </tr>
|
---|
181 | * <tr>
|
---|
182 | * <td nowrap> {@value org.opengis.referencing.operation.CoordinateOperation#SCOPE_KEY} </td>
|
---|
183 | * <td nowrap> {@link String} or {@link InternationalString} </td>
|
---|
184 | * <td nowrap> {@link #getScope}</td>
|
---|
185 | * </tr>
|
---|
186 | * </table>
|
---|
187 | *
|
---|
188 | * @param properties Set of properties. Should contains at least {@code "name"}.
|
---|
189 | * @param sourceCRS The source CRS.
|
---|
190 | * @param targetCRS The target CRS.
|
---|
191 | * @param transform Transform from positions in the {@linkplain #getSourceCRS source CRS}
|
---|
192 | * to positions in the {@linkplain #getTargetCRS target CRS}.
|
---|
193 | */
|
---|
194 | public AbstractCoordinateOperation(final Map<String,?> properties,
|
---|
195 | final CoordinateReferenceSystem sourceCRS,
|
---|
196 | final CoordinateReferenceSystem targetCRS,
|
---|
197 | final MathTransform transform)
|
---|
198 | {
|
---|
199 | this(properties, new HashMap<String,Object>(), sourceCRS, targetCRS, transform);
|
---|
200 | }
|
---|
201 |
|
---|
202 | /**
|
---|
203 | * Work around for RFE #4093999 in Sun's bug database
|
---|
204 | * ("Relax constraint on placement of this()/super() call in constructors").
|
---|
205 | */
|
---|
206 | private AbstractCoordinateOperation(final Map<String,?> properties,
|
---|
207 | final Map<String,Object> subProperties,
|
---|
208 | final CoordinateReferenceSystem sourceCRS,
|
---|
209 | final CoordinateReferenceSystem targetCRS,
|
---|
210 | final MathTransform transform)
|
---|
211 | {
|
---|
212 | super(properties, subProperties, LOCALIZABLES);
|
---|
213 | PositionalAccuracy[] positionalAccuracy;
|
---|
214 | domainOfValidity = (Extent) subProperties.get(DOMAIN_OF_VALIDITY_KEY);
|
---|
215 | scope = (InternationalString) subProperties.get(SCOPE_KEY);
|
---|
216 | operationVersion = (String) subProperties.get(OPERATION_VERSION_KEY);
|
---|
217 | positionalAccuracy = (PositionalAccuracy[]) subProperties.get(COORDINATE_OPERATION_ACCURACY_KEY);
|
---|
218 | if (positionalAccuracy==null || positionalAccuracy.length==0) {
|
---|
219 | positionalAccuracy = null;
|
---|
220 | } else {
|
---|
221 | positionalAccuracy = positionalAccuracy.clone();
|
---|
222 | for (int i=0; i<positionalAccuracy.length; i++) {
|
---|
223 | ensureNonNull(COORDINATE_OPERATION_ACCURACY_KEY, positionalAccuracy, i);
|
---|
224 | }
|
---|
225 | }
|
---|
226 | this.coordinateOperationAccuracy = asSet(positionalAccuracy);
|
---|
227 | this.sourceCRS = sourceCRS;
|
---|
228 | this.targetCRS = targetCRS;
|
---|
229 | this.transform = transform;
|
---|
230 | validate();
|
---|
231 | }
|
---|
232 |
|
---|
233 | /**
|
---|
234 | * Checks the validity of this operation. This method is invoked by the constructor after
|
---|
235 | * every fields have been assigned. It can be overriden by subclasses if different rules
|
---|
236 | * should be applied.
|
---|
237 | * <p>
|
---|
238 | * {@link DefaultConversion} overrides this method in order to allow null values, providing
|
---|
239 | * that all of {@code transform}, {@code sourceCRS} and {@code targetCRS} are null together.
|
---|
240 | * Note that null values are not allowed for transformations, so {@link DefaultTransformation}
|
---|
241 | * does not override this method.
|
---|
242 | *
|
---|
243 | * @throws IllegalArgumentException if at least one of {@code transform}, {@code sourceCRS}
|
---|
244 | * or {@code targetCRS} is invalid. We throw this kind of exception rather than
|
---|
245 | * {@link IllegalStateException} because this method is invoked by the constructor
|
---|
246 | * for checking argument validity.
|
---|
247 | */
|
---|
248 | void validate() throws IllegalArgumentException {
|
---|
249 | ensureNonNull ("sourceCRS", transform);
|
---|
250 | ensureNonNull ("targetCRS", transform);
|
---|
251 | ensureNonNull ("transform", transform);
|
---|
252 | checkDimension("sourceCRS", sourceCRS, transform.getSourceDimensions());
|
---|
253 | checkDimension("targetCRS", targetCRS, transform.getTargetDimensions());
|
---|
254 | }
|
---|
255 |
|
---|
256 | /**
|
---|
257 | * Checks if a reference coordinate system has the expected number of dimensions.
|
---|
258 | *
|
---|
259 | * @param name The argument name.
|
---|
260 | * @param crs The coordinate reference system to check.
|
---|
261 | * @param expected The expected number of dimensions.
|
---|
262 | */
|
---|
263 | private static void checkDimension(final String name,
|
---|
264 | final CoordinateReferenceSystem crs,
|
---|
265 | final int expected)
|
---|
266 | {
|
---|
267 | final int actual = crs.getCoordinateSystem().getDimension();
|
---|
268 | if (actual != expected) {
|
---|
269 | throw new IllegalArgumentException(Errors.format(
|
---|
270 | ErrorKeys.MISMATCHED_DIMENSION_$3, name, actual, expected));
|
---|
271 | }
|
---|
272 | }
|
---|
273 |
|
---|
274 | /**
|
---|
275 | * Returns the source CRS.
|
---|
276 | */
|
---|
277 | public CoordinateReferenceSystem getSourceCRS() {
|
---|
278 | return sourceCRS;
|
---|
279 | }
|
---|
280 |
|
---|
281 | /**
|
---|
282 | * Returns the target CRS.
|
---|
283 | */
|
---|
284 | public CoordinateReferenceSystem getTargetCRS() {
|
---|
285 | return targetCRS;
|
---|
286 | }
|
---|
287 |
|
---|
288 | /**
|
---|
289 | * Version of the coordinate transformation (i.e., instantiation due to the stochastic
|
---|
290 | * nature of the parameters). Mandatory when describing a transformation, and should not
|
---|
291 | * be supplied for a conversion.
|
---|
292 | *
|
---|
293 | * @return The coordinate operation version, or {@code null} in none.
|
---|
294 | */
|
---|
295 | public String getOperationVersion() {
|
---|
296 | return operationVersion;
|
---|
297 | }
|
---|
298 |
|
---|
299 | /**
|
---|
300 | * Estimate(s) of the impact of this operation on point accuracy. Gives
|
---|
301 | * position error estimates for target coordinates of this coordinate
|
---|
302 | * operation, assuming no errors in source coordinates.
|
---|
303 | *
|
---|
304 | * @return The position error estimates, or an empty collection if not available.
|
---|
305 | *
|
---|
306 | * @see #getAccuracy()
|
---|
307 | *
|
---|
308 | * @since 2.4
|
---|
309 | */
|
---|
310 | public Collection<PositionalAccuracy> getCoordinateOperationAccuracy() {
|
---|
311 | if (coordinateOperationAccuracy == null) {
|
---|
312 | return Collections.emptySet();
|
---|
313 | }
|
---|
314 | return coordinateOperationAccuracy;
|
---|
315 | }
|
---|
316 |
|
---|
317 | /**
|
---|
318 | * Area or region or timeframe in which this coordinate operation is valid.
|
---|
319 | * Returns {@code null} if not available.
|
---|
320 | *
|
---|
321 | * @since 2.4
|
---|
322 | */
|
---|
323 | public Extent getDomainOfValidity() {
|
---|
324 | return domainOfValidity;
|
---|
325 | }
|
---|
326 |
|
---|
327 | /**
|
---|
328 | * Description of domain of usage, or limitations of usage, for which this operation is valid.
|
---|
329 | */
|
---|
330 | public InternationalString getScope() {
|
---|
331 | return scope;
|
---|
332 | }
|
---|
333 |
|
---|
334 | /**
|
---|
335 | * Gets the math transform. The math transform will transform positions in the
|
---|
336 | * {@linkplain #getSourceCRS source coordinate reference system} into positions
|
---|
337 | * in the {@linkplain #getTargetCRS target coordinate reference system}.
|
---|
338 | */
|
---|
339 | public MathTransform getMathTransform() {
|
---|
340 | return transform;
|
---|
341 | }
|
---|
342 |
|
---|
343 | /**
|
---|
344 | * Returns the most specific GeoAPI interface implemented by the specified operation.
|
---|
345 | *
|
---|
346 | * @param object A coordinate operation.
|
---|
347 | * @return The most specific GeoAPI interface
|
---|
348 | * (e.g. <code>{@linkplain Transformation}.class</code>).
|
---|
349 | */
|
---|
350 | public static Class<? extends CoordinateOperation> getType(final CoordinateOperation object) {
|
---|
351 | if (object instanceof Transformation) return Transformation.class;
|
---|
352 | if (object instanceof ConicProjection) return ConicProjection.class;
|
---|
353 | if (object instanceof CylindricalProjection) return CylindricalProjection.class;
|
---|
354 | if (object instanceof PlanarProjection) return PlanarProjection.class;
|
---|
355 | if (object instanceof Projection) return Projection.class;
|
---|
356 | if (object instanceof Conversion) return Conversion.class;
|
---|
357 | if (object instanceof Operation) return Operation.class;
|
---|
358 | return CoordinateOperation.class;
|
---|
359 | }
|
---|
360 |
|
---|
361 | /**
|
---|
362 | * Compares this coordinate operation with the specified object for equality.
|
---|
363 | * If {@code compareMetadata} is {@code true}, then all available properties are
|
---|
364 | * compared including {@linkplain #getDomainOfValidity domain of validity} and
|
---|
365 | * {@linkplain #getScope scope}.
|
---|
366 | *
|
---|
367 | * @param object The object to compare to {@code this}.
|
---|
368 | * @param compareMetadata {@code true} for performing a strict comparaison, or
|
---|
369 | * {@code false} for comparing only properties relevant to transformations.
|
---|
370 | * @return {@code true} if both objects are equal.
|
---|
371 | */
|
---|
372 | @Override
|
---|
373 | public boolean equals(final AbstractIdentifiedObject object, final boolean compareMetadata) {
|
---|
374 | if (object == this) {
|
---|
375 | return true; // Slight optimization.
|
---|
376 | }
|
---|
377 | if (super.equals(object, compareMetadata)) {
|
---|
378 | final AbstractCoordinateOperation that = (AbstractCoordinateOperation) object;
|
---|
379 | if (equals(this.sourceCRS, that.sourceCRS, compareMetadata) &&
|
---|
380 | Utilities.equals(this.transform, that.transform))
|
---|
381 | // See comment in DefaultOperation.equals(...) about why we compare MathTransform.
|
---|
382 | {
|
---|
383 | if (compareMetadata) {
|
---|
384 | if (!Utilities.equals(this.domainOfValidity, that.domainOfValidity) ||
|
---|
385 | !Utilities.equals(this.scope, that.scope) ||
|
---|
386 | !Utilities.equals(this.coordinateOperationAccuracy, that.coordinateOperationAccuracy))
|
---|
387 | {
|
---|
388 | return false;
|
---|
389 | }
|
---|
390 | }
|
---|
391 | /*
|
---|
392 | * Avoid never-ending recursivity: AbstractDerivedCRS has a 'conversionFromBase'
|
---|
393 | * field that is set to this AbstractCoordinateOperation.
|
---|
394 | */
|
---|
395 | final Boolean comparing = AbstractDerivedCRS._COMPARING.get();
|
---|
396 | if (comparing!=null && comparing.booleanValue()) {
|
---|
397 | return true;
|
---|
398 | }
|
---|
399 | try {
|
---|
400 | AbstractDerivedCRS._COMPARING.set(Boolean.TRUE);
|
---|
401 | return equals(this.targetCRS, that.targetCRS, compareMetadata);
|
---|
402 | } finally {
|
---|
403 | AbstractDerivedCRS._COMPARING.set(Boolean.FALSE);
|
---|
404 | }
|
---|
405 | }
|
---|
406 | }
|
---|
407 | return false;
|
---|
408 | }
|
---|
409 |
|
---|
410 | /**
|
---|
411 | * Returns a hash code value for this coordinate operation.
|
---|
412 | */
|
---|
413 | @Override
|
---|
414 | public int hashCode() {
|
---|
415 | int code = (int)serialVersionUID;
|
---|
416 | if (sourceCRS != null) code ^= sourceCRS.hashCode();
|
---|
417 | if (targetCRS != null) code ^= targetCRS.hashCode();
|
---|
418 | if (transform != null) code ^= transform.hashCode();
|
---|
419 | return code;
|
---|
420 | }
|
---|
421 | }
|
---|