001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
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; either
010: * version 2.1 of the License, or (at your option) any later version.
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: package org.geotools.referencing.operation.transform;
018:
019: // J2SE dependencies and extensions
020: import java.io.Serializable;
021: import java.util.Collection;
022: import java.util.Collections;
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.ParameterValue;
030: import org.opengis.parameter.ParameterValueGroup;
031: import org.opengis.referencing.operation.MathTransform;
032: import org.opengis.referencing.operation.MathTransform2D;
033: import org.opengis.referencing.operation.OperationMethod;
034: import org.opengis.referencing.operation.Transformation;
035:
036: // Geotools dependencies
037: import org.geotools.metadata.iso.citation.Citations;
038: import org.geotools.parameter.DefaultParameterDescriptor;
039: import org.geotools.parameter.FloatParameter;
040: import org.geotools.parameter.Parameter;
041: import org.geotools.parameter.ParameterGroup;
042: import org.geotools.referencing.NamedIdentifier;
043: import org.geotools.referencing.operation.MathTransformProvider;
044: import org.geotools.resources.i18n.VocabularyKeys;
045: import org.geotools.resources.i18n.Vocabulary;
046: import org.geotools.resources.i18n.ErrorKeys;
047: import org.geotools.resources.i18n.Errors;
048:
049: /**
050: * Two- or three-dimensional datum shift using the (potentially abridged) Molodensky transformation.
051: * The Molodensky transformation (EPSG code 9604) and the abridged Molodensky transformation (EPSG
052: * code 9605) transform two or three dimensional geographic points from one geographic coordinate
053: * reference system to another (a datum shift), using three shift parameters (delta X, delta Y,
054: * delta Z) and the difference between the semi-major axis and flattenings of the two ellipsoids.
055: * <p>
056: *
057: * Unlike the Bursa-Wolf 3 parameter method (which acts on geocentric coordinates),
058: * this transformation can be performed directly on geographic coordinates.
059: * <p>
060: *
061: * <strong>References:</strong><ul>
062: * <li> Defense Mapping Agency (DMA), Datums, Ellipsoids, Grids and Grid Reference Systems,
063: * Technical Manual 8358.1.
064: * Available from <a href="http://earth-info.nga.mil/GandG/pubs.html">http://earth-info.nga.mil/GandG/pubs.html</a></li>
065: * <li> Defense Mapping Agency (DMA), The Universal Grids: Universal Transverse
066: * Mercator (UTM) and Universal Polar Stereographic (UPS), Fairfax VA, Technical Manual 8358.2.
067: * Available from <a href="http://earth-info.nga.mil/GandG/pubs.html">http://earth-info.nga.mil/GandG/pubs.html</a></li>
068: * <li> National Imagery and Mapping Agency (NIMA), Department of Defense World
069: * Geodetic System 1984, Technical Report 8350.2.
070: * Available from <a href="http://earth-info.nga.mil/GandG/pubs.html">http://earth-info.nga.mil/GandG/pubs.html</a></li>
071: * <li> "Coordinate Conversions and Transformations including Formulas",
072: * EPSG Guidence Note Number 7, Version 19.</li>
073: * </ul>
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/transform/MolodenskiTransform.java $
077: * @version $Id: MolodenskiTransform.java 24384 2007-02-14 00:23:05Z desruisseaux $
078: * @author Rueben Schulz
079: * @author Martin Desruisseaux
080: */
081: public class MolodenskiTransform extends AbstractMathTransform
082: implements Serializable {
083: /**
084: * Serial number for interoperability with different versions.
085: */
086: private static final long serialVersionUID = 7536566033885338422L;
087:
088: /**
089: * The tolerance error for assertions, in decimal degrees.
090: */
091: private static final float EPS = 1E-5f;
092:
093: /**
094: * {@code true} for the abridged formula, or {@code false} for the complete version.
095: */
096: private final boolean abridged;
097:
098: /**
099: * {@code true} for a 3D transformation, or
100: * {@code false} for a 2D transformation.
101: */
102: private final boolean source3D, target3D;
103:
104: /**
105: * X,Y,Z shift in meters.
106: */
107: private final double dx, dy, dz;
108:
109: /**
110: * Semi-major (<var>a</var>) semi-minor (<var>b/<var>) radius in meters.
111: */
112: private final double a, b;
113:
114: /**
115: * Difference in the semi-major ({@code da = target a - source a}) and semi-minor
116: * ({@code db = target b - source b}) axes of the target and source ellipsoids.
117: */
118: private final double da, db;
119:
120: /**
121: * Difference between the flattenings ({@code df = target f - source f})
122: * of the target and source ellipsoids.
123: */
124: private final double df;
125:
126: /**
127: * Ratio of the Semi-major (<var>a</var>) semi-minor (<var>b/<var>) axis
128: * values ({@code a_b = a/b} and {@code b_a = b/a}).
129: */
130: private final double b_a, a_b;
131:
132: /**
133: * Some more constants (<code>daa = da*a</code> and {@code da_a = da/a}).
134: */
135: private final double daa, da_a;
136:
137: /**
138: * The square of excentricity of the ellipsoid: eČ = (aČ-bČ)/aČ where
139: * <var>a</var> is the semi-major axis length and
140: * <var>b</var> is the semi-minor axis length.
141: */
142: private final double e2;
143:
144: /**
145: * Defined as <code>(a*df) + (f*da)</code>.
146: */
147: private final double adf;
148:
149: /**
150: * The inverse of this transform. Will be created only when first needed.
151: */
152: private transient MolodenskiTransform inverse;
153:
154: /**
155: * Constructs a Molodenski transform from the specified parameters.
156: *
157: * @param abridged {@code true} for the abridged formula, or {@code false} for the complete one.
158: * @param a The source semi-major axis length in meters.
159: * @param b The source semi-minor axis length in meters.
160: * @param source3D {@code true} if the source has a height.
161: * @param ta The target semi-major axis length in meters.
162: * @param tb The target semi-minor axis length in meters.
163: * @param target3D {@code true} if the target has a height.
164: * @param dx The <var>x</var> translation in meters.
165: * @param dy The <var>y</var> translation in meters.
166: * @param dz The <var>z</var> translation in meters.
167: */
168: public MolodenskiTransform(final boolean abridged, final double a,
169: final double b, final boolean source3D, final double ta,
170: final double tb, final boolean target3D, final double dx,
171: final double dy, final double dz) {
172: this .abridged = abridged;
173: this .source3D = source3D;
174: this .target3D = target3D;
175: this .dx = dx;
176: this .dy = dy;
177: this .dz = dz;
178: this .a = a;
179: this .b = b;
180:
181: da = ta - a;
182: db = tb - b;
183: a_b = a / b;
184: b_a = b / a;
185: daa = da * a;
186: da_a = da / a;
187: df = (ta - tb) / ta - (a - b) / a;
188: e2 = 1 - (b * b) / (a * a);
189: adf = (a * df) + (a - b) * da / a;
190: }
191:
192: /**
193: * Returns the parameter descriptors for this math transform.
194: */
195: public ParameterDescriptorGroup getParameterDescriptors() {
196: return abridged ? ProviderAbridged.PARAMETERS
197: : Provider.PARAMETERS;
198: }
199:
200: /**
201: * Returns the parameters for this math transform.
202: *
203: * @return The parameters for this math transform.
204: */
205: public ParameterValueGroup getParameterValues() {
206: final ParameterValue dim = new Parameter(Provider.DIM);
207: dim.setValue(getSourceDimensions());
208: return new ParameterGroup(getParameterDescriptors(),
209: new ParameterValue[] {
210: dim,
211: new FloatParameter(Provider.DX, dx),
212: new FloatParameter(Provider.DY, dy),
213: new FloatParameter(Provider.DZ, dz),
214: new FloatParameter(Provider.SRC_SEMI_MAJOR, a),
215: new FloatParameter(Provider.SRC_SEMI_MINOR, b),
216: new FloatParameter(Provider.TGT_SEMI_MAJOR, a
217: + da),
218: new FloatParameter(Provider.TGT_SEMI_MINOR, b
219: + db) });
220: }
221:
222: /**
223: * Gets the dimension of input points.
224: */
225: public int getSourceDimensions() {
226: return source3D ? 3 : 2;
227: }
228:
229: /**
230: * Gets the dimension of output points.
231: */
232: public final int getTargetDimensions() {
233: return target3D ? 3 : 2;
234: }
235:
236: /**
237: * Transforms a list of coordinate point ordinal values.
238: * This method is provided for efficiently transforming many points.
239: * The supplied array of ordinal values will contain packed ordinal
240: * values. For example, if the source dimension is 3, then the ordinals
241: * will be packed in this order:
242: *
243: * (<var>x<sub>0</sub></var>,<var>y<sub>0</sub></var>,<var>z<sub>0</sub></var>,
244: * <var>x<sub>1</sub></var>,<var>y<sub>1</sub></var>,<var>z<sub>1</sub></var> ...).
245: *
246: * @param srcPts the array containing the source point coordinates.
247: * @param srcOff the offset to the first point to be transformed
248: * in the source array.
249: * @param dstPts the array into which the transformed point
250: * coordinates are returned. May be the same
251: * than {@code srcPts}.
252: * @param dstOff the offset to the location of the first
253: * transformed point that is stored in the
254: * destination array.
255: * @param numPts the number of point objects to be transformed.
256: */
257: public void transform(double[] srcPts, int srcOff, double[] dstPts,
258: int dstOff, int numPts) {
259: transform(null, srcPts, srcOff, null, dstPts, dstOff, numPts);
260: /*
261: * Assertions: computes the inverse transform in the 3D-case only
262: * (otherwise the transform is too approximative).
263: *
264: * NOTE: The somewhat complicated expression below executes 'maxError' *only* if
265: * 1) assertions are enabled and 2) the conditions before 'maxError' are meet. Do
266: * not factor the call to 'maxError' outside the 'assert' statement, otherwise it
267: * would be executed everytime and would hurt performance for normal operations
268: * (instead of slowing down during debugging only).
269: */
270: assert !(target3D && srcPts != dstPts && (maxError(null,
271: srcPts, srcOff, null, dstPts, dstOff, numPts)) > EPS);
272: }
273:
274: /**
275: * Transforms a list of coordinate point ordinal values.
276: * This method is provided for efficiently transforming many points.
277: * The supplied array of ordinal values will contain packed ordinal
278: * values. For example, if the source dimension is 3, then the ordinals
279: * will be packed in this order:
280: *
281: * (<var>x<sub>0</sub></var>,<var>y<sub>0</sub></var>,<var>z<sub>0</sub></var>,
282: * <var>x<sub>1</sub></var>,<var>y<sub>1</sub></var>,<var>z<sub>1</sub></var> ...).
283: *
284: * @param srcPts the array containing the source point coordinates.
285: * @param srcOff the offset to the first point to be transformed
286: * in the source array.
287: * @param dstPts the array into which the transformed point
288: * coordinates are returned. May be the same
289: * than {@code srcPts}.
290: * @param dstOff the offset to the location of the first
291: * transformed point that is stored in the
292: * destination array.
293: * @param numPts the number of point objects to be transformed.
294: */
295: public void transform(final float[] srcPts, int srcOff,
296: final float[] dstPts, int dstOff, int numPts) {
297: transform(srcPts, null, srcOff, dstPts, null, dstOff, numPts);
298: /*
299: * Assertions: computes the inverse transform in the 3D-case only
300: * (otherwise the transform is too approximative).
301: *
302: * NOTE: The somewhat complicated expression below executes 'maxError' *only* if
303: * 1) assertions are enabled and 2) the conditions before 'maxError' are meet. Do
304: * not factor the call to 'maxError' outside the 'assert' statement, otherwise it
305: * would be executed everytime and would hurt performance for normal operations
306: * (instead of slowing down during debugging only).
307: */
308: assert !(target3D && srcPts != dstPts && (maxError(srcPts,
309: null, srcOff, dstPts, null, dstOff, numPts)) > EPS);
310: }
311:
312: /**
313: * Implementation of the transformation methods for all cases.
314: */
315: private void transform(final float[] srcPts1,
316: final double[] srcPts2, int srcOff, final float[] dstPts1,
317: final double[] dstPts2, int dstOff, int numPts) {
318: int step = 0;
319: if ((srcPts2 != null ? srcPts2 == dstPts2 : srcPts1 == dstPts1)
320: && srcOff < dstOff
321: && srcOff + numPts * getSourceDimensions() > dstOff) {
322: if (source3D != target3D) {
323: // TODO: we need to figure out a general way to handle this case
324: // (overwritting the source array while source and target
325: // dimensions are not the same). This case occurs enough
326: // in the CTS implementation...
327: throw new UnsupportedOperationException(
328: "Not yet implemented.");
329: }
330: step = -getSourceDimensions();
331: srcOff -= (numPts - 1) * step;
332: dstOff -= (numPts - 1) * step;
333: }
334: while (--numPts >= 0) {
335: double x, y, z;
336: if (srcPts2 != null) {
337: x = srcPts2[srcOff++];
338: y = srcPts2[srcOff++];
339: z = (source3D) ? srcPts2[srcOff++] : 0.0;
340: } else {
341: x = srcPts1[srcOff++];
342: y = srcPts1[srcOff++];
343: z = (source3D) ? srcPts1[srcOff++] : 0.0;
344: }
345: x = Math.toRadians(x);
346: y = Math.toRadians(y);
347: final double sinX = Math.sin(x);
348: final double cosX = Math.cos(x);
349: final double sinY = Math.sin(y);
350: final double cosY = Math.cos(y);
351: final double sin2Y = sinY * sinY;
352: final double Rn = a / Math.sqrt(1 - e2 * sin2Y);
353: final double Rm = Rn * (1 - e2) / (1 - e2 * sin2Y);
354:
355: // Note: Computation of 'x' and 'y' ommit the division by sin(1"), because
356: // 1/sin(1") / (60*60*180/PI) = 1.0000000000039174050898603898692...
357: // (60*60 is for converting the final result from seconds to degrees,
358: // and 180/PI is for converting degrees to radians). This is an error
359: // of about 8E-7 arc seconds, probably close to rounding errors anyway.
360: if (abridged) {
361: y += (dz * cosY - sinY * (dy * sinX + dx * cosX) + adf
362: * Math.sin(2 * y))
363: / Rm;
364: x += (dy * cosX - dx * sinX) / (Rn * cosY);
365: } else {
366: y += (dz * cosY - sinY * (dy * sinX + dx * cosX) + da_a
367: * (Rn * e2 * sinY * cosY) + df
368: * (Rm * (a_b) + Rn * (b_a)) * sinY * cosY)
369: / (Rm + z);
370: x += (dy * cosX - dx * sinX) / ((Rn + z) * cosY);
371: }
372: //stay within latitude +-90 deg. and longitude +-180 deg.
373: if (Math.abs(y) > Math.PI / 2.0) {
374: if (dstPts2 != null) {
375: dstPts2[dstOff++] = 0.0;
376: dstPts2[dstOff++] = (y > 0.0) ? 90.0 : -90.0;
377: } else {
378: dstPts1[dstOff++] = 0.0f;
379: dstPts1[dstOff++] = (y > 0.0f) ? 90.0f : -90.0f;
380: }
381: } else {
382: x = Math.toDegrees(rollLongitude(x));
383: y = Math.toDegrees(y);
384: if (dstPts2 != null) {
385: dstPts2[dstOff++] = x;
386: dstPts2[dstOff++] = y;
387: } else {
388: dstPts1[dstOff++] = (float) x;
389: dstPts1[dstOff++] = (float) y;
390: }
391: }
392: if (target3D) {
393: if (abridged) {
394: z += dx * cosY * cosX + dy * cosY * sinX + dz
395: * sinY + adf * sin2Y - da;
396: } else {
397: z += dx * cosY * cosX + dy * cosY * sinX + dz
398: * sinY + df * (b_a) * Rn * sin2Y - daa / Rn;
399: }
400: if (dstPts2 != null) {
401: dstPts2[dstOff++] = z;
402: } else {
403: dstPts1[dstOff++] = (float) z;
404: }
405: }
406: srcOff += step;
407: dstOff += step;
408: }
409: }
410:
411: /**
412: * After a call to {@code transform}, applies the <em>inverse</em> transform on {@code dstPts}
413: * and compares the result with {@code srcPts}. The maximal difference (in absolute value) is
414: * returned. This method is used for assertions.
415: */
416: private float maxError(final float[] srcPts1,
417: final double[] srcPts2, int srcOff, final float[] dstPts1,
418: final double[] dstPts2, int dstOff, int numPts) {
419: float max = 0f;
420: if (inverse == null) {
421: inverse();
422: if (inverse == null) {
423: return max; // Custom user's subclass; can't do the test.
424: }
425: }
426: final int sourceDim = getSourceDimensions();
427: final float[] tmp = new float[numPts * sourceDim];
428: inverse.transform(dstPts1, dstPts2, dstOff, tmp, null, 0,
429: numPts);
430: for (int i = 0; i < tmp.length; i++, srcOff++) {
431: final float expected = (srcPts2 != null) ? (float) srcPts2[srcOff]
432: : srcPts1[srcOff];
433: float error = Math.abs(tmp[i] - expected);
434: switch (i % sourceDim) {
435: case 0:
436: error -= 360 * Math.floor(error / 360);
437: break; // Rool Longitude
438: case 2:
439: continue; // Ignore height because inacurate.
440: }
441: if (error > max) {
442: max = error;
443: }
444: }
445: return max;
446: }
447:
448: /**
449: * Creates the inverse transform of this object.
450: */
451: public MathTransform inverse() {
452: if (inverse == null) {
453: inverse = new MolodenskiTransform(abridged, a + da, b + db,
454: target3D, a, b, source3D, -dx, -dy, -dz);
455: inverse.inverse = this ;
456: }
457: return inverse;
458: }
459:
460: /**
461: * Returns a hash value for this transform.
462: */
463: public final int hashCode() {
464: final long code = Double.doubleToLongBits(dx)
465: + 37
466: * (Double.doubleToLongBits(dy) + 37 * (Double
467: .doubleToLongBits(dz) + 37 * (Double
468: .doubleToLongBits(a) + 37 * (Double
469: .doubleToLongBits(b) + 37 * (Double
470: .doubleToLongBits(da) + 37 * (Double
471: .doubleToLongBits(db)))))));
472: int c = (int) code ^ (int) (code >>> 32)
473: ^ (int) serialVersionUID;
474: if (abridged)
475: c = ~c;
476: return c;
477: }
478:
479: /**
480: * Compares the specified object with this math transform for equality.
481: */
482: public final boolean equals(final Object object) {
483: if (object == this ) {
484: // Slight optimization
485: return true;
486: }
487: if (super .equals(object)) {
488: final MolodenskiTransform that = (MolodenskiTransform) object;
489: return this .abridged == that.abridged
490: && this .source3D == that.source3D
491: && this .target3D == that.target3D
492: && Double.doubleToLongBits(this .dx) == Double
493: .doubleToLongBits(that.dx)
494: && Double.doubleToLongBits(this .dy) == Double
495: .doubleToLongBits(that.dy)
496: && Double.doubleToLongBits(this .dz) == Double
497: .doubleToLongBits(that.dz)
498: && Double.doubleToLongBits(this .a) == Double
499: .doubleToLongBits(that.a)
500: && Double.doubleToLongBits(this .b) == Double
501: .doubleToLongBits(that.b)
502: && Double.doubleToLongBits(this .da) == Double
503: .doubleToLongBits(that.da)
504: && Double.doubleToLongBits(this .db) == Double
505: .doubleToLongBits(that.db);
506: }
507: return false;
508: }
509:
510: /**
511: * A Molodenski transforms in 2D. This implementation is identical to
512: * {@link MolodenksiTransform} except that it implements {@link MathTransform2D}.
513: */
514: private static final class As2D extends MolodenskiTransform
515: implements MathTransform2D {
516: /** Serial number for compatibility with different versions. */
517: private static final long serialVersionUID = 8098439371246167474L;
518:
519: /** Constructs a 2D transform using Molodenski formulas. */
520: public As2D(final boolean abridged, final double a,
521: final double b, final double ta, final double tb,
522: final double dx, final double dy, final double dz) {
523: super (abridged, a, b, false, ta, tb, false, dx, dy, dz);
524: }
525:
526: /** Creates the inverse transform of this object. */
527: public MathTransform inverse() {
528: if (super .inverse == null) {
529: super .inverse = new As2D(super .abridged, super .a
530: + super .da, super .b + super .db, super .a,
531: super .b, -super .dx, -super .dy, -super .dz);
532: super .inverse.inverse = this ;
533: }
534: return super .inverse;
535: }
536: }
537:
538: /**
539: * The provider for {@link MolodenskiTransform}. This provider will construct
540: * transforms from {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS geographic}
541: * to {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS geographic} coordinate
542: * reference systems.
543: * <p>
544: * <strong>Note:</strong>
545: * The EPSG does not use src_semi_major, etc. parameters and instead uses
546: * "Semi-major axis length difference" and "Flattening difference".
547: *
548: * @version $Id: MolodenskiTransform.java 24384 2007-02-14 00:23:05Z desruisseaux $
549: * @author Rueben Schulz
550: */
551: public static class Provider extends MathTransformProvider {
552: /**
553: * Serial number for interoperability with different versions.
554: */
555: private static final long serialVersionUID = -5332126871499059030L;
556:
557: /**
558: * The default value for source and target geographic dimensions, which is 2.
559: */
560: // NOTE: If this default value is modified, then
561: // the handling of the 3D cases must be adjusted.
562: static final int DEFAULT_DIMENSION = GeocentricTranslation.Provider.DEFAULT_DIMENSION;
563:
564: /**
565: * The number of geographic dimension (2 or 3). This argument applies on
566: * both the source and the target dimension. The default value is 2.
567: */
568: public static final ParameterDescriptor DIM = new DefaultParameterDescriptor(
569: Collections.singletonMap(NAME_KEY, new NamedIdentifier(
570: Citations.OGC, "dim")), DEFAULT_DIMENSION, 2,
571: 3, false);
572:
573: /**
574: * The number of source geographic dimension (2 or 3).
575: * This is a Geotools-specific argument.
576: *
577: * @todo Not yet used by this provider. See GEOT-411.
578: */
579: public static final ParameterDescriptor SRC_DIM = GeocentricTranslation.Provider.SRC_DIM;
580:
581: /**
582: * The number of target geographic dimension (2 or 3).
583: * This is a Geotools-specific argument.
584: *
585: * @todo Not yet used by this provider. See GEOT-411.
586: */
587: public static final ParameterDescriptor TGT_DIM = GeocentricTranslation.Provider.TGT_DIM;
588:
589: /**
590: * The operation parameter descriptor for the <cite>X-axis translation</cite> ("dx")
591: * parameter value. Valid values range from -infinity to infinity. Units are meters.
592: */
593: public static final ParameterDescriptor DX = GeocentricTranslation.Provider.DX;
594:
595: /**
596: * The operation parameter descriptor for the <cite>Y-axis translation</cite> ("dy")
597: * parameter value. Valid values range from -infinity to infinity. Units are meters.
598: */
599: public static final ParameterDescriptor DY = GeocentricTranslation.Provider.DY;
600:
601: /**
602: * The operation parameter descriptor for the <cite>Z-axis translation</cite> ("dz")
603: * parameter value. Valid values range from -infinity to infinity. Units are meters.
604: */
605: public static final ParameterDescriptor DZ = GeocentricTranslation.Provider.DZ;
606:
607: /**
608: * The operation parameter descriptor for the "src_semi_major" parameter value.
609: * Valid values range from 0 to infinity.
610: */
611: public static final ParameterDescriptor SRC_SEMI_MAJOR = createDescriptor(
612: identifiers(GeocentricTranslation.Provider.SRC_SEMI_MAJOR),
613: Double.NaN, 0.0, Double.POSITIVE_INFINITY, SI.METER);
614:
615: /**
616: * The operation parameter descriptor for the "src_semi_minor" parameter value.
617: * Valid values range from 0 to infinity.
618: */
619: public static final ParameterDescriptor SRC_SEMI_MINOR = createDescriptor(
620: identifiers(GeocentricTranslation.Provider.SRC_SEMI_MINOR),
621: Double.NaN, 0.0, Double.POSITIVE_INFINITY, SI.METER);
622:
623: /**
624: * The operation parameter descriptor for the "tgt_semi_major" parameter value.
625: * Valid values range from 0 to infinity.
626: */
627: public static final ParameterDescriptor TGT_SEMI_MAJOR = createDescriptor(
628: identifiers(GeocentricTranslation.Provider.TGT_SEMI_MAJOR),
629: Double.NaN, 0.0, Double.POSITIVE_INFINITY, SI.METER);
630:
631: /**
632: * The operation parameter descriptor for the "tgt_semi_minor" parameter value.
633: * Valid values range from 0 to infinity.
634: */
635: public static final ParameterDescriptor TGT_SEMI_MINOR = createDescriptor(
636: identifiers(GeocentricTranslation.Provider.TGT_SEMI_MINOR),
637: Double.NaN, 0.0, Double.POSITIVE_INFINITY, SI.METER);
638:
639: /** Helper method for parameter descriptor creation. */
640: private static final NamedIdentifier[] identifiers(
641: final ParameterDescriptor parameter) {
642: final Collection id = parameter.getAlias();
643: return (NamedIdentifier[]) id
644: .toArray(new NamedIdentifier[id.size()]);
645: }
646:
647: /**
648: * The parameters group.
649: */
650: static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
651: new NamedIdentifier[] {
652: new NamedIdentifier(Citations.OGC, "Molodenski"),
653: new NamedIdentifier(Citations.EPSG,
654: "Molodenski"),
655: new NamedIdentifier(Citations.EPSG, "9604"),
656: new NamedIdentifier(
657: Citations.GEOTOOLS,
658: Vocabulary
659: .formatInternational(VocabularyKeys.MOLODENSKI_TRANSFORM)) },
660: new ParameterDescriptor[] { DIM, DX, DY, DZ,
661: SRC_SEMI_MAJOR, SRC_SEMI_MINOR, TGT_SEMI_MAJOR,
662: TGT_SEMI_MINOR });
663:
664: /**
665: * The provider for the 3D case. Will be constructed
666: * by {@link #getMethod} when first needed.
667: */
668: private transient Provider withHeight;
669:
670: /**
671: * Constructs a provider.
672: */
673: public Provider() {
674: super (DEFAULT_DIMENSION, DEFAULT_DIMENSION, PARAMETERS);
675: }
676:
677: /**
678: * Constructs a provider from a set of parameters.
679: *
680: * @param sourceDimensions Number of dimensions in the source CRS of this operation method.
681: * @param targetDimensions Number of dimensions in the target CRS of this operation method.
682: * @param parameters The set of parameters (never {@code null}).
683: */
684: Provider(final int sourceDimensions,
685: final int targetDimensions,
686: final ParameterDescriptorGroup parameters) {
687: super (sourceDimensions, targetDimensions, parameters);
688: }
689:
690: /**
691: * Returns the operation type.
692: */
693: public Class getOperationType() {
694: return Transformation.class;
695: }
696:
697: /**
698: * Creates a math transform from the specified group of parameter values.
699: *
700: * @param values The group of parameter values.
701: * @return The created math transform.
702: * @throws ParameterNotFoundException if a required parameter was not found.
703: */
704: protected MathTransform createMathTransform(
705: final ParameterValueGroup values)
706: throws ParameterNotFoundException {
707: final boolean hasHeight;
708: final int dim = intValue(DIM, values);
709: switch (dim) {
710: case 0: // Default value: fall through
711: case DEFAULT_DIMENSION: {
712: hasHeight = false;
713: break;
714: }
715: case 3: {
716: hasHeight = true;
717: if (withHeight == null) {
718: withHeight = create3D();
719: }
720: break;
721: }
722: default: {
723: throw new IllegalArgumentException(Errors.format(
724: ErrorKeys.ILLEGAL_ARGUMENT_$2, "dim",
725: new Integer(dim)));
726: }
727: }
728: final double a = doubleValue(SRC_SEMI_MAJOR, values);
729: final double b = doubleValue(SRC_SEMI_MINOR, values);
730: final double ta = doubleValue(TGT_SEMI_MAJOR, values);
731: final double tb = doubleValue(TGT_SEMI_MINOR, values);
732: final double dx = doubleValue(DX, values);
733: final double dy = doubleValue(DY, values);
734: final double dz = doubleValue(DZ, values);
735: final boolean abridged = isAbridged();
736: if (!hasHeight) {
737: return new As2D(abridged, a, b, ta, tb, dx, dy, dz);
738: } else {
739: return new Delegate(
740: new MolodenskiTransform(abridged, a, b,
741: hasHeight, ta, tb, hasHeight, dx, dy,
742: dz), withHeight);
743: }
744: }
745:
746: /**
747: * Creates the 3D-version of this provider.
748: * This method is overridden by {@link ProviderAbridged}.
749: */
750: Provider create3D() {
751: return new Provider(3, 3, PARAMETERS);
752: }
753:
754: /**
755: * Returns {@code true} for the abridged formulas.
756: * This method is overridden by {@link ProviderAbridged}.
757: */
758: boolean isAbridged() {
759: return false;
760: }
761: }
762:
763: /**
764: * The provider for abridged {@link MolodenskiTransform}. This provider will construct
765: * transforms from {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS geographic}
766: * to {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS geographic} coordinate
767: * reference systems.
768: * <p>
769: * <strong>Note:</strong>
770: * The EPSG does not use src_semi_major, etc. parameters and instead uses
771: * "Semi-major axis length difference" and "Flattening difference".
772: *
773: * @version $Id: MolodenskiTransform.java 24384 2007-02-14 00:23:05Z desruisseaux $
774: * @author Martin Desruisseaux
775: * @author Rueben Schulz
776: */
777: public static class ProviderAbridged extends Provider {
778: /**
779: * Serial number for interoperability with different versions.
780: */
781: private static final long serialVersionUID = 9148242601566635131L;
782:
783: /**
784: * The parameters group.
785: */
786: static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
787: new NamedIdentifier[] {
788: new NamedIdentifier(Citations.OGC,
789: "Abridged_Molodenski"),
790: new NamedIdentifier(Citations.EPSG,
791: "Abridged Molodenski"),
792: new NamedIdentifier(Citations.EPSG, "9605"),
793: new NamedIdentifier(
794: Citations.GEOTOOLS,
795: Vocabulary
796: .format(VocabularyKeys.ABRIDGED_MOLODENSKI_TRANSFORM)) },
797: new ParameterDescriptor[] { DIM, DX, DY, DZ,
798: SRC_SEMI_MAJOR, SRC_SEMI_MINOR, TGT_SEMI_MAJOR,
799: TGT_SEMI_MINOR });
800:
801: /**
802: * Constructs a provider.
803: */
804: public ProviderAbridged() {
805: super (DEFAULT_DIMENSION, DEFAULT_DIMENSION, PARAMETERS);
806: }
807:
808: /**
809: * Constructs a provider from a set of parameters.
810: *
811: * @param sourceDimensions Number of dimensions in the source CRS of this operation method.
812: * @param targetDimensions Number of dimensions in the target CRS of this operation method.
813: * @param parameters The set of parameters (never {@code null}).
814: */
815: private ProviderAbridged(final int sourceDimensions,
816: final int targetDimensions,
817: final ParameterDescriptorGroup parameters) {
818: super (sourceDimensions, targetDimensions, parameters);
819: }
820:
821: /**
822: * Creates the 3D-version of this provider.
823: */
824: Provider create3D() {
825: return new ProviderAbridged(3, 3, PARAMETERS);
826: }
827:
828: /**
829: * Returns {@code true} for the abridged formulas.
830: */
831: boolean isAbridged() {
832: return true;
833: }
834: }
835: }
|