001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2005-2006, Geotools Project Managment Committee (PMC)
006: * (C) 2005, Institut de Recherche pour le Développement
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: */
018: package org.geotools.referencing.operation.transform;
019:
020: // J2SE dependencies and extensions
021: import java.util.Collections;
022: import javax.units.NonSI;
023: import javax.units.SI;
024:
025: // OpenGIS dependencies
026: import org.opengis.parameter.ParameterDescriptor;
027: import org.opengis.parameter.ParameterDescriptorGroup;
028: import org.opengis.parameter.ParameterNotFoundException;
029: import org.opengis.parameter.ParameterValueGroup;
030: import org.opengis.referencing.operation.Matrix;
031: import org.opengis.referencing.operation.MathTransform;
032: import org.opengis.referencing.operation.Transformation;
033:
034: // Geotools dependencies
035: import org.geotools.measure.Units;
036: import org.geotools.metadata.iso.citation.Citations;
037: import org.geotools.parameter.DefaultParameterDescriptor;
038: import org.geotools.parameter.FloatParameter;
039: import org.geotools.parameter.ParameterGroup;
040: import org.geotools.referencing.NamedIdentifier;
041: import org.geotools.referencing.datum.BursaWolfParameters;
042: import org.geotools.referencing.operation.MathTransformProvider;
043: import org.geotools.resources.Utilities;
044: import org.geotools.resources.i18n.Errors;
045: import org.geotools.resources.i18n.ErrorKeys;
046:
047: /**
048: * An affine transform applied on {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric}
049: * coordinates. While "geocentric translation" is a little bit more restrictive name, it describes
050: * the part which is common to all instances of this class. A rotation may also be performed in
051: * addition of the translation, but the rotation sign is operation-dependent (EPSG 9606 and 9607
052: * have opposite sign). This transform is used for the following operations:
053: * <p>
054: * <table border="1">
055: * <tr><th>EPSG name</th> <th>EPSG code</th></tr>
056: * <tr><td>Geocentric translations</td> <td>9603</td></tr>
057: * <tr><td>Position Vector 7-param. transformation</td> <td>9606</td></tr>
058: * <tr><td>Coordinate Frame rotation</td> <td>9607</td></tr>
059: * </table>
060: * <p>
061: * The conversion between geographic and geocentric coordinates is usually <strong>not</strong>
062: * part of this transform. However, the Geotools implementation of the
063: * {@linkplain GeocentricTranslation.Provider provider} accepts the following extensions:
064: * <p>
065: * <ul>
066: * <li>If {@code "src_semi_major"} and {@code "src_semi_minor"} parameters are provided, then
067: * a {@code "Ellipsoid_To_Geocentric"} transform is concatenated before this transform.</li>
068: * <li>If {@code "tgt_semi_major"} and {@code "tgt_semi_minor"} parameters are provided, then
069: * a {@code "Geocentric_To_Ellipsoid"} transform is concatenated after this transform.</li>
070: * </ul>
071: *
072: * @since 2.2
073: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/GeocentricTranslation.java $
074: * @version $Id: GeocentricTranslation.java 24384 2007-02-14 00:23:05Z desruisseaux $
075: * @author Martin Desruisseaux
076: */
077: public class GeocentricTranslation extends ProjectiveTransform {
078: /**
079: * Serial number for interoperability with different versions.
080: */
081: private static final long serialVersionUID = -168669443433018655L;
082:
083: /**
084: * The parameter descriptor group.
085: */
086: private final ParameterDescriptorGroup descriptor;
087:
088: /**
089: * Creates a new geocentric affine transform. If the parameters don't contain rotation terms,
090: * then this transform will be of kind "<cite>Geocentric translations</cite>". Otherwise, it
091: * will be of kind "<cite>Position Vector 7-param. transformation</cite>".
092: */
093: public GeocentricTranslation(final BursaWolfParameters parameters) {
094: this (parameters,
095: parameters.isTranslation() ? Provider.PARAMETERS
096: : ProviderSevenParam.PARAMETERS);
097: }
098:
099: /**
100: * Creates a new geocentric affine transform using the specified parameter descriptor.
101: */
102: GeocentricTranslation(final BursaWolfParameters parameters,
103: final ParameterDescriptorGroup descriptor) {
104: super (parameters.getAffineTransform());
105: this .descriptor = descriptor;
106: }
107:
108: /**
109: * Creates a new geocentric affine transform using the specified parameter descriptor.
110: */
111: private GeocentricTranslation(final Matrix matrix,
112: final ParameterDescriptorGroup descriptor) {
113: super (matrix);
114: this .descriptor = descriptor;
115: }
116:
117: /**
118: * Returns the parameter descriptors for this math transform.
119: */
120: public ParameterDescriptorGroup getParameterDescriptors() {
121: return descriptor;
122: }
123:
124: /**
125: * Returns the parameters for this math transform.
126: */
127: public ParameterValueGroup getParameterValues() {
128: final BursaWolfParameters parameters = new BursaWolfParameters(
129: null);
130: parameters.setAffineTransform(getMatrix(),
131: Double.POSITIVE_INFINITY);
132: if (ProviderFrameRotation.PARAMETERS.equals(descriptor)) {
133: parameters.ex = -parameters.ex;
134: parameters.ey = -parameters.ey;
135: parameters.ez = -parameters.ez;
136: }
137: final boolean isTranslation = Provider.PARAMETERS
138: .equals(descriptor);
139: final FloatParameter[] param = new FloatParameter[isTranslation ? 3
140: : 7];
141: param[0] = new FloatParameter(Provider.DX, parameters.dx);
142: param[1] = new FloatParameter(Provider.DY, parameters.dy);
143: param[2] = new FloatParameter(Provider.DZ, parameters.dz);
144: if (!isTranslation) {
145: param[3] = new FloatParameter(ProviderSevenParam.EX,
146: parameters.ex);
147: param[4] = new FloatParameter(ProviderSevenParam.EY,
148: parameters.ey);
149: param[5] = new FloatParameter(ProviderSevenParam.EZ,
150: parameters.ez);
151: param[6] = new FloatParameter(ProviderSevenParam.PPM,
152: parameters.ppm);
153: }
154: return new ParameterGroup(getParameterDescriptors(), param);
155: }
156:
157: /**
158: * Creates an inverse transform using the specified matrix.
159: */
160: MathTransform createInverse(final Matrix matrix) {
161: return new GeocentricTranslation(matrix, descriptor);
162: }
163:
164: /**
165: * Returns a hash value for this transform.
166: * This value need not remain consistent between
167: * different implementations of the same class.
168: */
169: public int hashCode() {
170: return super .hashCode() ^ descriptor.hashCode();
171: }
172:
173: /**
174: * Compares the specified object with this math transform for equality.
175: */
176: public boolean equals(final Object object) {
177: if (super .equals(object)) {
178: final GeocentricTranslation that = (GeocentricTranslation) object;
179: return Utilities.equals(this .descriptor, that.descriptor);
180: }
181: return false;
182: }
183:
184: /**
185: * Base class for {@linkplain GeocentricTranslation geocentric affine transform} providers.
186: * This base class is the provider for the "<cite>Geocentric translations</cite>" operation
187: * (EPSG code 9603). The translation terms are the same for the 2 derived operations,
188: * "<cite>Position Vector 7-param. transformation</cite>" and
189: * "<cite>Coordinate Frame rotation</cite>".
190: *
191: * @version $Id: GeocentricTranslation.java 24384 2007-02-14 00:23:05Z desruisseaux $
192: * @author Martin Desruisseaux
193: *
194: * @since 2.2
195: */
196: public static class Provider extends MathTransformProvider {
197: /**
198: * Serial number for interoperability with different versions.
199: */
200: private static final long serialVersionUID = -7160250630666911608L;
201:
202: /**
203: * The default value for geographic source and target dimensions, which is 2.
204: * NOTE: If this default value is modified, then the handling of the 3D cases
205: * in {@link MolodenskiTransform} must be adjusted.
206: */
207: static final int DEFAULT_DIMENSION = 2;
208:
209: /**
210: * The number of source geographic dimension (2 or 3).
211: * This is a Geotools-specific argument. If presents, an {@code "Ellipsoid_To_Geocentric"}
212: * transform will be concatenated before the geocentric translation.
213: */
214: public static final ParameterDescriptor SRC_DIM = new DefaultParameterDescriptor(
215: Collections.singletonMap(NAME_KEY, new NamedIdentifier(
216: Citations.GEOTOOLS, "src_dim")),
217: DEFAULT_DIMENSION, 2, 3, false);
218:
219: /**
220: * The number of target geographic dimension (2 or 3).
221: * This is a Geotools-specific argument. If presents, a {@code "Geocentric_To_Ellipsoid"}
222: * transform will be concatenated after the geocentric translation.
223: */
224: public static final ParameterDescriptor TGT_DIM = new DefaultParameterDescriptor(
225: Collections.singletonMap(NAME_KEY, new NamedIdentifier(
226: Citations.GEOTOOLS, "tgt_dim")),
227: DEFAULT_DIMENSION, 2, 3, false);
228:
229: /**
230: * The operation parameter descriptor for the "src_semi_major" optional parameter value.
231: * This is a Geotools-specific argument. If presents, an {@code "Ellipsoid_To_Geocentric"}
232: * transform will be concatenated before the geocentric translation. Valid values range
233: * from 0 to infinity.
234: */
235: public static final ParameterDescriptor SRC_SEMI_MAJOR = createOptionalDescriptor(
236: new NamedIdentifier[] { new NamedIdentifier(
237: Citations.OGC, "src_semi_major") }, 0.0,
238: Double.POSITIVE_INFINITY, SI.METER);
239:
240: /**
241: * The operation parameter descriptor for the "src_semi_minor" optional parameter value.
242: * This is a Geotools-specific argument. If presents, an {@code "Ellipsoid_To_Geocentric"}
243: * transform will be concatenated before the geocentric translation. Valid values range
244: * from 0 to infinity.
245: */
246: public static final ParameterDescriptor SRC_SEMI_MINOR = createOptionalDescriptor(
247: new NamedIdentifier[] { new NamedIdentifier(
248: Citations.OGC, "src_semi_minor"), }, 0.0,
249: Double.POSITIVE_INFINITY, SI.METER);
250:
251: /**
252: * The operation parameter descriptor for the "tgt_semi_major" optional parameter value.
253: * This is a Geotools-specific argument. If presents, a {@code "Geocentric_To_Ellipsoid"}
254: * transform will be concatenated after the geocentric translation. Valid values range
255: * from 0 to infinity.
256: */
257: public static final ParameterDescriptor TGT_SEMI_MAJOR = createOptionalDescriptor(
258: new NamedIdentifier[] { new NamedIdentifier(
259: Citations.OGC, "tgt_semi_major") }, 0.0,
260: Double.POSITIVE_INFINITY, SI.METER);
261:
262: /**
263: * The operation parameter descriptor for the "tgt_semi_minor" optional parameter value.
264: * This is a Geotools-specific argument. If presents, a {@code "Geocentric_To_Ellipsoid"}
265: * transform will be concatenated after the geocentric translation. Valid values range
266: * from 0 to infinity.
267: */
268: public static final ParameterDescriptor TGT_SEMI_MINOR = createOptionalDescriptor(
269: new NamedIdentifier[] { new NamedIdentifier(
270: Citations.OGC, "tgt_semi_minor") }, 0.0,
271: Double.POSITIVE_INFINITY, SI.METER);
272:
273: /**
274: * The operation parameter descriptor for the <cite>X-axis translation</cite> ("dx")
275: * parameter value. Valid values range from -infinity to infinity. Units are meters.
276: */
277: public static final ParameterDescriptor DX = createDescriptor(
278: new NamedIdentifier[] {
279: new NamedIdentifier(Citations.OGC, "dx"),
280: new NamedIdentifier(Citations.EPSG,
281: "X-axis translation") }, 0.0,
282: Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
283: SI.METER);
284:
285: /**
286: * The operation parameter descriptor for the <cite>Y-axis translation</cite> ("dy")
287: * parameter value. Valid values range from -infinity to infinity. Units are meters.
288: */
289: public static final ParameterDescriptor DY = createDescriptor(
290: new NamedIdentifier[] {
291: new NamedIdentifier(Citations.OGC, "dy"),
292: new NamedIdentifier(Citations.EPSG,
293: "Y-axis translation") }, 0.0,
294: Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
295: SI.METER);
296:
297: /**
298: * The operation parameter descriptor for the <cite>Z-axis translation</cite> ("dz")
299: * parameter value. Valid values range from -infinity to infinity. Units are meters.
300: */
301: public static final ParameterDescriptor DZ = createDescriptor(
302: new NamedIdentifier[] {
303: new NamedIdentifier(Citations.OGC, "dz"),
304: new NamedIdentifier(Citations.EPSG,
305: "Z-axis translation") }, 0.0,
306: Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
307: SI.METER);
308:
309: /**
310: * The parameters group.
311: */
312: static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
313: new NamedIdentifier[] {
314: new NamedIdentifier(Citations.EPSG,
315: "Geocentric translations"),
316: new NamedIdentifier(Citations.EPSG, "9603") },
317: new ParameterDescriptor[] { DX, DY, DZ, SRC_SEMI_MAJOR,
318: SRC_SEMI_MINOR, TGT_SEMI_MAJOR, TGT_SEMI_MINOR,
319: SRC_DIM, TGT_DIM });
320:
321: /**
322: * Constructs a default provider.
323: */
324: public Provider() {
325: this (PARAMETERS);
326: }
327:
328: /**
329: * Constructs a provider with the specified parameters.
330: */
331: Provider(final ParameterDescriptorGroup parameters) {
332: super (3, 3, parameters);
333: }
334:
335: /**
336: * Returns the operation type.
337: */
338: public Class getOperationType() {
339: return Transformation.class;
340: }
341:
342: /**
343: * Creates a math transform from the specified group of parameter values.
344: *
345: * @param values The group of parameter values.
346: * @return The created math transform.
347: * @throws ParameterNotFoundException if a required parameter was not found.
348: */
349: protected MathTransform createMathTransform(
350: final ParameterValueGroup values)
351: throws ParameterNotFoundException {
352: final BursaWolfParameters parameters = new BursaWolfParameters(
353: null);
354: fill(parameters, values);
355: return concatenate(concatenate(new GeocentricTranslation(
356: parameters, getParameters()), values,
357: SRC_SEMI_MAJOR, SRC_SEMI_MINOR, SRC_DIM), values,
358: TGT_SEMI_MAJOR, TGT_SEMI_MINOR, TGT_DIM);
359: }
360:
361: /**
362: * Fill the specified Bursa-Wolf parameters according the specified values.
363: * This method is invoked automatically by {@link #createMathTransform}.
364: */
365: protected void fill(final BursaWolfParameters parameters,
366: final ParameterValueGroup values) {
367: parameters.dx = doubleValue(DX, values);
368: parameters.dy = doubleValue(DY, values);
369: parameters.dz = doubleValue(DZ, values);
370: }
371:
372: /**
373: * Concatenates the supplied transform with an "ellipsoid to geocentric" or a
374: * "geocentric to ellipsod" step, if needed.
375: */
376: private static MathTransform concatenate(
377: final MathTransform transform,
378: final ParameterValueGroup values,
379: final ParameterDescriptor major,
380: final ParameterDescriptor minor,
381: final ParameterDescriptor dim) {
382: double semiMajor = doubleValue(major, values);
383: double semiMinor = doubleValue(minor, values);
384: int dimension = intValue(dim, values);
385: switch (dimension) {
386: case 0:
387: if (Double.isNaN(semiMajor) && Double.isNaN(semiMinor))
388: return transform;
389: case 2: // Fall through for 0 and 2 cases.
390: case 3:
391: break; // The dimension is a valid value.
392: default:
393: throw new IllegalArgumentException(Errors.format(
394: ErrorKeys.ILLEGAL_ARGUMENT_$2, dim.getName()
395: .getCode(), new Integer(dimension)));
396: }
397: ensureValid(major, semiMajor);
398: ensureValid(minor, semiMinor);
399: final GeocentricTransform step;
400: step = new GeocentricTransform(semiMajor, semiMinor,
401: SI.METER, dimension == 3);
402: // Note: dimension may be 0 if not user-provided, which is treated as 2.
403: if (dim == SRC_DIM) {
404: return ConcatenatedTransform.create(step, transform);
405: } else {
406: return ConcatenatedTransform.create(transform, step
407: .inverse());
408: }
409: }
410:
411: /**
412: * Ensures the the specified parameter is valid.
413: */
414: private static void ensureValid(
415: final ParameterDescriptor param, double value) {
416: if (!(value > 0)) {
417: throw new IllegalStateException(Errors.format(
418: ErrorKeys.MISSING_PARAMETER_$1, param.getName()
419: .getCode()));
420: }
421: }
422: }
423:
424: /**
425: * Base class for {@linkplain GeocentricTranslation geocentric affine transform} providers
426: * with rotation terms. This base class is the provider for the "<cite>Position Vector 7-param.
427: * transformation</cite>".
428: *
429: * @version $Id: GeocentricTranslation.java 24384 2007-02-14 00:23:05Z desruisseaux $
430: * @author Martin Desruisseaux
431: *
432: * @since 2.2
433: */
434: public static class ProviderSevenParam extends Provider {
435: /**
436: * Serial number for interoperability with different versions.
437: */
438: private static final long serialVersionUID = -6398226638364450229L;
439:
440: /**
441: * The maximal value for a rotation, in arc-second.
442: */
443: private static final double MAX_ROTATION = 180 * 60 * 60;
444:
445: /**
446: * The operation parameter descriptor for the <cite>X-axis rotation</cite> ("ex")
447: * parameter value. Units are arc-seconds.
448: */
449: public static final ParameterDescriptor EX = createDescriptor(
450: new NamedIdentifier[] {
451: new NamedIdentifier(Citations.OGC, "ex"),
452: new NamedIdentifier(Citations.EPSG,
453: "X-axis rotation") }, 0.0,
454: -MAX_ROTATION, MAX_ROTATION, NonSI.SECOND_ANGLE);
455:
456: /**
457: * The operation parameter descriptor for the <cite>Y-axis rotation</cite> ("ey")
458: * parameter value. Units are arc-seconds.
459: */
460: public static final ParameterDescriptor EY = createDescriptor(
461: new NamedIdentifier[] {
462: new NamedIdentifier(Citations.OGC, "ey"),
463: new NamedIdentifier(Citations.EPSG,
464: "Y-axis rotation") }, 0.0,
465: -MAX_ROTATION, MAX_ROTATION, NonSI.SECOND_ANGLE);
466:
467: /**
468: * The operation parameter descriptor for the <cite>Z-axis rotation</cite> ("ez")
469: * parameter value. Units are arc-seconds.
470: */
471: public static final ParameterDescriptor EZ = createDescriptor(
472: new NamedIdentifier[] {
473: new NamedIdentifier(Citations.OGC, "ez"),
474: new NamedIdentifier(Citations.EPSG,
475: "Z-axis rotation") }, 0.0,
476: -MAX_ROTATION, MAX_ROTATION, NonSI.SECOND_ANGLE);
477:
478: /**
479: * The operation parameter descriptor for the <cite>Scale difference</cite> ("ppm")
480: * parameter value. Valid values range from -infinity to infinity. Units are parts
481: * per million.
482: */
483: public static final ParameterDescriptor PPM = createDescriptor(
484: new NamedIdentifier[] {
485: new NamedIdentifier(Citations.OGC, "ppm"),
486: new NamedIdentifier(Citations.EPSG,
487: "Scale difference") }, 0.0,
488: Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
489: Units.PPM);
490:
491: /**
492: * The parameters group.
493: */
494: static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
495: "Position Vector 7-param. transformation", "9606");
496:
497: /**
498: * Creates a parameters group.
499: */
500: static ParameterDescriptorGroup createDescriptorGroup(
501: final String name, final String code) {
502: return createDescriptorGroup(new NamedIdentifier[] {
503: new NamedIdentifier(Citations.EPSG, name),
504: new NamedIdentifier(Citations.EPSG, code) },
505: new ParameterDescriptor[] { DX, DY, DZ, EX, EY, EZ,
506: PPM, SRC_SEMI_MAJOR, SRC_SEMI_MINOR,
507: TGT_SEMI_MAJOR, TGT_SEMI_MINOR, SRC_DIM,
508: TGT_DIM });
509: }
510:
511: /**
512: * Constructs a default provider.
513: */
514: public ProviderSevenParam() {
515: super (PARAMETERS);
516: }
517:
518: /**
519: * Constructs a provider with the specified parameters.
520: */
521: ProviderSevenParam(final ParameterDescriptorGroup parameters) {
522: super (parameters);
523: }
524:
525: /**
526: * Fill the specified Bursa-Wolf parameters according the specified values.
527: */
528: protected void fill(final BursaWolfParameters parameters,
529: final ParameterValueGroup values) {
530: super .fill(parameters, values);
531: parameters.ppm = doubleValue(PPM, values);
532: parameters.ex = doubleValue(EX, values);
533: parameters.ey = doubleValue(EY, values);
534: parameters.ez = doubleValue(EZ, values);
535: }
536: }
537:
538: /**
539: * {@linkplain GeocentricTranslation Geocentric affine transform} provider for
540: * "<cite>Coordinate Frame rotation</cite>".
541: *
542: * @version $Id: GeocentricTranslation.java 24384 2007-02-14 00:23:05Z desruisseaux $
543: * @author Martin Desruisseaux
544: *
545: * @since 2.2
546: */
547: public static class ProviderFrameRotation extends
548: ProviderSevenParam {
549: /**
550: * Serial number for interoperability with different versions.
551: */
552: private static final long serialVersionUID = 5513675854809530038L;
553:
554: /**
555: * The parameters group.
556: */
557: static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
558: "Coordinate Frame rotation", "9607");
559:
560: /**
561: * Constructs a default provider.
562: */
563: public ProviderFrameRotation() {
564: super (PARAMETERS);
565: }
566:
567: /**
568: * Fill the specified Bursa-Wolf parameters according the specified values.
569: */
570: protected void fill(final BursaWolfParameters parameters,
571: final ParameterValueGroup values) {
572: super.fill(parameters, values);
573: parameters.ex = -parameters.ex;
574: parameters.ey = -parameters.ey;
575: parameters.ez = -parameters.ez;
576: }
577: }
578: }
|