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.Collections;
|
---|
23 | import java.util.HashMap;
|
---|
24 | import java.util.Map;
|
---|
25 |
|
---|
26 | import org.geotools.parameter.Parameters;
|
---|
27 | import org.geotools.referencing.AbstractIdentifiedObject;
|
---|
28 | import org.geotools.referencing.operation.transform.AbstractMathTransform;
|
---|
29 | import org.geotools.referencing.operation.transform.ConcatenatedTransform;
|
---|
30 | import org.geotools.referencing.operation.transform.PassThroughTransform;
|
---|
31 | import org.geotools.resources.i18n.ErrorKeys;
|
---|
32 | import org.geotools.resources.i18n.Errors;
|
---|
33 | import org.geotools.resources.i18n.Vocabulary;
|
---|
34 | import org.geotools.resources.i18n.VocabularyKeys;
|
---|
35 | import org.geotools.util.Utilities;
|
---|
36 | import org.opengis.geometry.MismatchedDimensionException;
|
---|
37 | import org.opengis.parameter.ParameterDescriptorGroup;
|
---|
38 | import org.opengis.referencing.operation.MathTransform;
|
---|
39 | import org.opengis.referencing.operation.Matrix;
|
---|
40 | import org.opengis.referencing.operation.Operation;
|
---|
41 | import org.opengis.referencing.operation.OperationMethod;
|
---|
42 | import org.opengis.referencing.operation.Projection;
|
---|
43 | import org.opengis.util.InternationalString;
|
---|
44 |
|
---|
45 |
|
---|
46 | /**
|
---|
47 | * Definition of an algorithm used to perform a coordinate operation. Most operation
|
---|
48 | * methods use a number of operation parameters, although some coordinate conversions
|
---|
49 | * use none. Each coordinate operation using the method assigns values to these parameters.
|
---|
50 | *
|
---|
51 | * @since 2.1
|
---|
52 | *
|
---|
53 | * @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/modules/library/referencing/src/main/java/org/geotools/referencing/operation/DefaultOperationMethod.java $
|
---|
54 | * @version $Id: DefaultOperationMethod.java 37299 2011-05-25 05:21:24Z mbedward $
|
---|
55 | * @author Martin Desruisseaux (IRD)
|
---|
56 | *
|
---|
57 | * @see DefaultOperation
|
---|
58 | */
|
---|
59 | public class DefaultOperationMethod extends AbstractIdentifiedObject implements OperationMethod {
|
---|
60 | /**
|
---|
61 | * Serial number for interoperability with different versions.
|
---|
62 | */
|
---|
63 | private static final long serialVersionUID = -98032729598205972L;
|
---|
64 |
|
---|
65 | /**
|
---|
66 | * List of localizable properties. To be given to {@link AbstractIdentifiedObject} constructor.
|
---|
67 | */
|
---|
68 | private static final String[] LOCALIZABLES = {FORMULA_KEY};
|
---|
69 |
|
---|
70 | /**
|
---|
71 | * Formula(s) or procedure used by this operation method. This may be a reference to a
|
---|
72 | * publication. Note that the operation method may not be analytic, in which case this
|
---|
73 | * attribute references or contains the procedure, not an analytic formula.
|
---|
74 | */
|
---|
75 | private final InternationalString formula;
|
---|
76 |
|
---|
77 | /**
|
---|
78 | * Number of dimensions in the source CRS of this operation method.
|
---|
79 | */
|
---|
80 | protected final int sourceDimensions;
|
---|
81 |
|
---|
82 | /**
|
---|
83 | * Number of dimensions in the target CRS of this operation method.
|
---|
84 | */
|
---|
85 | protected final int targetDimensions;
|
---|
86 |
|
---|
87 | /**
|
---|
88 | * The set of parameters, or {@code null} if none.
|
---|
89 | */
|
---|
90 | private final ParameterDescriptorGroup parameters;
|
---|
91 |
|
---|
92 | /**
|
---|
93 | * Convenience constructor that creates an operation method from a math transform.
|
---|
94 | * The information provided in the newly created object are approximative, and
|
---|
95 | * usually acceptable only as a fallback when no other information are available.
|
---|
96 | *
|
---|
97 | * @param transform The math transform to describe.
|
---|
98 | */
|
---|
99 | public DefaultOperationMethod(final MathTransform transform) {
|
---|
100 | this(getProperties(transform),
|
---|
101 | transform.getSourceDimensions(),
|
---|
102 | transform.getTargetDimensions(),
|
---|
103 | getDescriptor(transform));
|
---|
104 | }
|
---|
105 |
|
---|
106 | /**
|
---|
107 | * Work around for RFE #4093999 in Sun's bug database
|
---|
108 | * ("Relax constraint on placement of this()/super() call in constructors").
|
---|
109 | */
|
---|
110 | private static Map<String,?> getProperties(final MathTransform transform) {
|
---|
111 | ensureNonNull("transform", transform);
|
---|
112 | final Map<String,?> properties;
|
---|
113 | if (transform instanceof AbstractMathTransform) {
|
---|
114 | final AbstractMathTransform mt = (AbstractMathTransform) transform;
|
---|
115 | properties = getProperties(mt.getParameterDescriptors(), null);
|
---|
116 | } else {
|
---|
117 | properties = Collections.singletonMap(NAME_KEY, Vocabulary.format(VocabularyKeys.UNKNOW));
|
---|
118 | }
|
---|
119 | return properties;
|
---|
120 | }
|
---|
121 |
|
---|
122 | /**
|
---|
123 | * Work around for RFE #4093999 in Sun's bug database
|
---|
124 | * ("Relax constraint on placement of this()/super() call in constructors").
|
---|
125 | * This code should have been merged with {@code getProperties} above.
|
---|
126 | */
|
---|
127 | private static ParameterDescriptorGroup getDescriptor(final MathTransform transform) {
|
---|
128 | ParameterDescriptorGroup descriptor = null;
|
---|
129 | if (transform instanceof AbstractMathTransform) {
|
---|
130 | descriptor = ((AbstractMathTransform) transform).getParameterDescriptors();
|
---|
131 | }
|
---|
132 | return descriptor;
|
---|
133 | }
|
---|
134 |
|
---|
135 | /**
|
---|
136 | * Constructs a new operation method with the same values than the specified one
|
---|
137 | * except the dimensions.
|
---|
138 | *
|
---|
139 | * @param method The operation method to copy.
|
---|
140 | * @param sourceDimensions Number of dimensions in the source CRS of this operation method.
|
---|
141 | * @param targetDimensions Number of dimensions in the target CRS of this operation method.
|
---|
142 | */
|
---|
143 | public DefaultOperationMethod(final OperationMethod method,
|
---|
144 | final int sourceDimensions,
|
---|
145 | final int targetDimensions)
|
---|
146 | {
|
---|
147 | super(method);
|
---|
148 | this.formula = method.getFormula();
|
---|
149 | this.parameters = method.getParameters();
|
---|
150 | this.sourceDimensions = sourceDimensions;
|
---|
151 | this.targetDimensions = targetDimensions;
|
---|
152 | ensurePositive("sourceDimensions", sourceDimensions);
|
---|
153 | ensurePositive("targetDimensions", targetDimensions);
|
---|
154 | }
|
---|
155 |
|
---|
156 | /**
|
---|
157 | * Constructs an operation method from a set of properties and a descriptor group.
|
---|
158 | * The properties given in argument follow the same rules than for the
|
---|
159 | * {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}.
|
---|
160 | * Additionally, the following properties are understood by this construtor:
|
---|
161 | * <br><br>
|
---|
162 | * <table border='1'>
|
---|
163 | * <tr bgcolor="#CCCCFF" class="TableHeadingColor">
|
---|
164 | * <th nowrap>Property name</th>
|
---|
165 | * <th nowrap>Value type</th>
|
---|
166 | * <th nowrap>Value given to</th>
|
---|
167 | * </tr>
|
---|
168 | * <tr>
|
---|
169 | * <td nowrap> {@link #FORMULA_KEY "formula"} </td>
|
---|
170 | * <td nowrap> {@link String} or {@link InternationalString} </td>
|
---|
171 | * <td nowrap> {@link #getFormula}</td>
|
---|
172 | * </tr>
|
---|
173 | * </table>
|
---|
174 | *
|
---|
175 | * @param properties Set of properties. Should contains at least {@code "name"}.
|
---|
176 | * @param sourceDimensions Number of dimensions in the source CRS of this operation method.
|
---|
177 | * @param targetDimensions Number of dimensions in the target CRS of this operation method.
|
---|
178 | * @param parameters The set of parameters, or {@code null} if none.
|
---|
179 | */
|
---|
180 | public DefaultOperationMethod(final Map<String,?> properties,
|
---|
181 | final int sourceDimensions,
|
---|
182 | final int targetDimensions,
|
---|
183 | final ParameterDescriptorGroup parameters)
|
---|
184 | {
|
---|
185 | this(properties, new HashMap<String,Object>(), sourceDimensions, targetDimensions, parameters);
|
---|
186 | }
|
---|
187 |
|
---|
188 | /**
|
---|
189 | * Work around for RFE #4093999 in Sun's bug database
|
---|
190 | * ("Relax constraint on placement of this()/super() call in constructors").
|
---|
191 | */
|
---|
192 | private DefaultOperationMethod(final Map<String,?> properties,
|
---|
193 | final Map<String,Object> subProperties,
|
---|
194 | final int sourceDimensions,
|
---|
195 | final int targetDimensions,
|
---|
196 | ParameterDescriptorGroup parameters)
|
---|
197 | {
|
---|
198 | super(properties, subProperties, LOCALIZABLES);
|
---|
199 | formula = (InternationalString) subProperties.get(FORMULA_KEY);
|
---|
200 | // 'parameters' may be null, which is okay. A null value will
|
---|
201 | // make serialization smaller and faster than an empty object.
|
---|
202 | this.parameters = parameters;
|
---|
203 | this.sourceDimensions = sourceDimensions;
|
---|
204 | this.targetDimensions = targetDimensions;
|
---|
205 | ensurePositive("sourceDimensions", sourceDimensions);
|
---|
206 | ensurePositive("targetDimensions", targetDimensions);
|
---|
207 | }
|
---|
208 |
|
---|
209 | /**
|
---|
210 | * Ensure that the specified value is positive.
|
---|
211 | * An {@link IllegalArgumentException} is throws if it is not.
|
---|
212 | *
|
---|
213 | * @param name The parameter name.
|
---|
214 | * @param value The parameter value.
|
---|
215 | * @throws IllegalArgumentException if the specified value is not positive.
|
---|
216 | */
|
---|
217 | private static void ensurePositive(final String name, final int value)
|
---|
218 | throws IllegalArgumentException
|
---|
219 | {
|
---|
220 | if (value < 0) {
|
---|
221 | throw new IllegalArgumentException(Errors.format(
|
---|
222 | ErrorKeys.ILLEGAL_ARGUMENT_$2, name, value));
|
---|
223 | }
|
---|
224 | }
|
---|
225 |
|
---|
226 | /**
|
---|
227 | * Formula(s) or procedure used by this operation method. This may be a reference to a
|
---|
228 | * publication. Note that the operation method may not be analytic, in which case this
|
---|
229 | * attribute references or contains the procedure, not an analytic formula.
|
---|
230 | */
|
---|
231 | public InternationalString getFormula() {
|
---|
232 | return formula;
|
---|
233 | }
|
---|
234 |
|
---|
235 | /**
|
---|
236 | * Number of dimensions in the source CRS of this operation method.
|
---|
237 | *
|
---|
238 | * @return The dimension of source CRS.
|
---|
239 | */
|
---|
240 | public int getSourceDimensions() {
|
---|
241 | return sourceDimensions;
|
---|
242 | }
|
---|
243 |
|
---|
244 | /**
|
---|
245 | * Number of dimensions in the target CRS of this operation method.
|
---|
246 | */
|
---|
247 | public int getTargetDimensions() {
|
---|
248 | return targetDimensions;
|
---|
249 | }
|
---|
250 |
|
---|
251 | /**
|
---|
252 | * Returns the set of parameters.
|
---|
253 | */
|
---|
254 | public ParameterDescriptorGroup getParameters() {
|
---|
255 | return (parameters!=null) ? parameters : Parameters.EMPTY_GROUP;
|
---|
256 | }
|
---|
257 |
|
---|
258 | /**
|
---|
259 | * Returns the operation type. Current implementation returns {@code Projection.class} for
|
---|
260 | * proper WKT formatting using an unknow implementation. But the {@link MathTransformProvider}
|
---|
261 | * subclass (with protected access) will overrides this method with a more conservative default
|
---|
262 | * value.
|
---|
263 | *
|
---|
264 | * @return The GeoAPI interface implemented by this operation.
|
---|
265 | */
|
---|
266 | Class<? extends Operation> getOperationType() {
|
---|
267 | return Projection.class;
|
---|
268 | }
|
---|
269 |
|
---|
270 | /**
|
---|
271 | * Compare this operation method with the specified object for equality.
|
---|
272 | * If {@code compareMetadata} is {@code true}, then all available
|
---|
273 | * properties are compared including {@linkplain #getFormula formula}.
|
---|
274 | *
|
---|
275 | * @param object The object to compare to {@code this}.
|
---|
276 | * @param compareMetadata {@code true} for performing a strict comparaison, or
|
---|
277 | * {@code false} for comparing only properties relevant to transformations.
|
---|
278 | * @return {@code true} if both objects are equal.
|
---|
279 | */
|
---|
280 | @Override
|
---|
281 | public boolean equals(final AbstractIdentifiedObject object, final boolean compareMetadata) {
|
---|
282 | if (object == this) {
|
---|
283 | return true; // Slight optimization.
|
---|
284 | }
|
---|
285 | if (super.equals(object, compareMetadata)) {
|
---|
286 | final DefaultOperationMethod that = (DefaultOperationMethod) object;
|
---|
287 | if (this.sourceDimensions == that.sourceDimensions &&
|
---|
288 | this.targetDimensions == that.targetDimensions &&
|
---|
289 | equals(this.parameters, that.parameters, compareMetadata))
|
---|
290 | {
|
---|
291 | return !compareMetadata || Utilities.equals(this.formula, that.formula);
|
---|
292 | }
|
---|
293 | }
|
---|
294 | return false;
|
---|
295 | }
|
---|
296 |
|
---|
297 | /**
|
---|
298 | * Returns a hash code value for this operation method.
|
---|
299 | */
|
---|
300 | @Override
|
---|
301 | public int hashCode() {
|
---|
302 | int code = (int)serialVersionUID + sourceDimensions + 37*targetDimensions;
|
---|
303 | if (parameters != null) {
|
---|
304 | code = code * 37 + parameters.hashCode();
|
---|
305 | }
|
---|
306 | return code;
|
---|
307 | }
|
---|
308 |
|
---|
309 | /**
|
---|
310 | * Returns {@code true} if the specified transform is likely to exists only for axis switch
|
---|
311 | * and/or unit conversions. The heuristic rule checks if the transform is backed by a square
|
---|
312 | * matrix with exactly one non-null value in each row and each column. This method is used
|
---|
313 | * for implementation of the {@link #checkDimensions} method only.
|
---|
314 | */
|
---|
315 | private static boolean isTrivial(final MathTransform transform) {
|
---|
316 | if (transform instanceof LinearTransform) {
|
---|
317 | final Matrix matrix = ((LinearTransform) transform).getMatrix();
|
---|
318 | final int size = matrix.getNumRow();
|
---|
319 | if (matrix.getNumCol() == size) {
|
---|
320 | for (int j=0; j<size; j++) {
|
---|
321 | int n1=0, n2=0;
|
---|
322 | for (int i=0; i<size; i++) {
|
---|
323 | if (matrix.getElement(j,i) != 0) n1++;
|
---|
324 | if (matrix.getElement(i,j) != 0) n2++;
|
---|
325 | }
|
---|
326 | if (n1 != 1 || n2 != 1) {
|
---|
327 | return false;
|
---|
328 | }
|
---|
329 | }
|
---|
330 | return true;
|
---|
331 | }
|
---|
332 | }
|
---|
333 | return false;
|
---|
334 | }
|
---|
335 |
|
---|
336 | /**
|
---|
337 | * Checks if an operation method and a math transform have a compatible number of source
|
---|
338 | * and target dimensions. In the particular case of a {@linkplain PassThroughTransform pass
|
---|
339 | * through transform} with more dimension than the expected number, the check will rather be
|
---|
340 | * performed against the {@linkplain PassThroughTransform#getSubTransform sub transform}.
|
---|
341 | * <p>
|
---|
342 | * This convenience method is provided for argument checking.
|
---|
343 | *
|
---|
344 | * @param method The operation method to compare to the math transform, or {@code null}.
|
---|
345 | * @param transform The math transform to compare to the operation method, or {@code null}.
|
---|
346 | * @throws MismatchedDimensionException if the number of dimensions are incompatibles.
|
---|
347 | *
|
---|
348 | * @todo The check for {@link ConcatenatedTransform} and {@link PassThroughTransform} works
|
---|
349 | * only for Geotools implementation.
|
---|
350 | */
|
---|
351 | public static void checkDimensions(final OperationMethod method, MathTransform transform)
|
---|
352 | throws MismatchedDimensionException
|
---|
353 | {
|
---|
354 | if (method!=null && transform!=null) {
|
---|
355 | int actual, expected=method.getSourceDimensions();
|
---|
356 | while ((actual=transform.getSourceDimensions()) > expected) {
|
---|
357 | if (transform instanceof ConcatenatedTransform) {
|
---|
358 | // Ignore axis switch and unit conversions.
|
---|
359 | final ConcatenatedTransform c = (ConcatenatedTransform) transform;
|
---|
360 | if (isTrivial(c.transform1)) {
|
---|
361 | transform = c.transform2;
|
---|
362 | } else if (isTrivial(c.transform2)) {
|
---|
363 | transform = c.transform1;
|
---|
364 | } else {
|
---|
365 | // The transform is something more complex than an axis switch.
|
---|
366 | // Stop the loop with the current illegal transform and let the
|
---|
367 | // exception be thrown after the loop.
|
---|
368 | break;
|
---|
369 | }
|
---|
370 | } else if (transform instanceof PassThroughTransform) {
|
---|
371 | transform = ((PassThroughTransform) transform).getSubTransform();
|
---|
372 | } else {
|
---|
373 | break;
|
---|
374 | }
|
---|
375 | }
|
---|
376 | final String name;
|
---|
377 | if (actual != expected) {
|
---|
378 | name = "sourceDimensions";
|
---|
379 | } else {
|
---|
380 | actual = transform.getTargetDimensions();
|
---|
381 | expected = method.getTargetDimensions();
|
---|
382 | if (actual != expected) {
|
---|
383 | name = "targetDimensions";
|
---|
384 | } else {
|
---|
385 | return;
|
---|
386 | }
|
---|
387 | }
|
---|
388 | throw new IllegalArgumentException(Errors.format(
|
---|
389 | ErrorKeys.MISMATCHED_DIMENSION_$3, name, actual, expected));
|
---|
390 | }
|
---|
391 | }
|
---|
392 | }
|
---|