001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
006: * (C) 2001, 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: * This package contains documentation from OpenGIS specifications.
019: * OpenGIS consortium's work is fully acknowledged here.
020: */
021: package org.geotools.referencing.operation.transform;
022:
023: // J2SE dependencies and extensions
024: import java.io.IOException;
025: import java.io.ObjectInputStream;
026: import java.io.Serializable;
027: import java.util.Collections;
028: import javax.units.Converter;
029: import javax.units.SI;
030: import javax.units.Unit;
031:
032: // OpenGIS dependencies
033: import org.opengis.parameter.ParameterDescriptor;
034: import org.opengis.parameter.ParameterDescriptorGroup;
035: import org.opengis.parameter.ParameterNotFoundException;
036: import org.opengis.parameter.ParameterValue;
037: import org.opengis.parameter.ParameterValueGroup;
038: import org.opengis.referencing.datum.Ellipsoid;
039: import org.opengis.referencing.operation.Conversion;
040: import org.opengis.referencing.operation.MathTransform;
041: import org.opengis.referencing.operation.OperationMethod;
042:
043: // Geotools dependencies
044: import org.geotools.metadata.iso.citation.Citations;
045: import org.geotools.parameter.DefaultParameterDescriptor;
046: import org.geotools.parameter.FloatParameter;
047: import org.geotools.referencing.NamedIdentifier;
048: import org.geotools.referencing.operation.MathTransformProvider;
049: import org.geotools.resources.i18n.VocabularyKeys;
050: import org.geotools.resources.i18n.Vocabulary;
051: import org.geotools.resources.i18n.ErrorKeys;
052: import org.geotools.resources.i18n.Errors;
053:
054: /**
055: * Transforms three dimensional {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS
056: * geographic} points to {@linkplain org.geotools.referencing.crs.DefaultGeocentricCRS geocentric}
057: * coordinate points. Input points must be longitudes, latitudes and heights above the ellipsoid.
058: *
059: * @since 2.0
060: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/transform/GeocentricTransform.java $
061: * @version $Id: GeocentricTransform.java 24384 2007-02-14 00:23:05Z desruisseaux $
062: * @author Frank Warmerdam
063: * @author Martin Desruisseaux
064: */
065: public class GeocentricTransform extends AbstractMathTransform
066: implements Serializable {
067: /**
068: * Serial number for interoperability with different versions.
069: */
070: private static final long serialVersionUID = -3352045463953828140L;
071:
072: /**
073: * Maximal error tolerance in metres during assertions, in metres. If assertions
074: * are enabled (JDK 1.4 only), then every coordinates transformed with
075: * {@link #inverseTransform} will be transformed again with {@link #mathTransform}.
076: * If the distance between the resulting position and the original position
077: * is greater than {@code MAX_ERROR}, then a {@link AssertionError} is thrown.
078: */
079: private static final double MAX_ERROR = 0.01;
080:
081: /**
082: * Cosine of 67.5 decimal degrees.
083: */
084: private static final double COS_67P5 = 0.38268343236508977;
085:
086: /**
087: * Toms region 1 constant.
088: */
089: private static final double AD_C = 1.0026000;
090:
091: /**
092: * Semi-major axis of ellipsoid in meters.
093: */
094: private final double a;
095:
096: /**
097: * Semi-minor axis of ellipsoid in meters.
098: */
099: private final double b;
100:
101: /**
102: * Square of semi-major axis (<var>a</var>˛).
103: */
104: private final double a2;
105:
106: /**
107: * Square of semi-minor axis (<var>b</var>˛).
108: */
109: private final double b2;
110:
111: /**
112: * Eccentricity squared.
113: */
114: private final double e2;
115:
116: /**
117: * 2nd eccentricity squared.
118: */
119: private final double ep2;
120:
121: /**
122: * {@code true} if geographic coordinates include an ellipsoidal
123: * height (i.e. are 3-D), or {@code false} if they are strictly 2-D.
124: */
125: private final boolean hasHeight;
126:
127: /**
128: * The inverse of this transform. Will be created only when needed.
129: */
130: private transient MathTransform inverse;
131:
132: /**
133: * Constructs a transform from the specified ellipsoid.
134: *
135: * @param ellipsoid The ellipsoid.
136: * @param hasHeight {@code true} if geographic coordinates
137: * include an ellipsoidal height (i.e. are 3-D),
138: * or {@code false} if they are only 2-D.
139: */
140: public GeocentricTransform(final Ellipsoid ellipsoid,
141: final boolean hasHeight) {
142: this (ellipsoid.getSemiMajorAxis(),
143: ellipsoid.getSemiMinorAxis(), ellipsoid.getAxisUnit(),
144: hasHeight);
145: }
146:
147: /**
148: * Constructs a transform from the specified parameters.
149: *
150: * @param semiMajor The semi-major axis length.
151: * @param semiMinor The semi-minor axis length.
152: * @param units The axis units.
153: * @param hasHeight {@code true} if geographic coordinates
154: * include an ellipsoidal height (i.e. are 3-D),
155: * or {@code false} if they are only 2-D.
156: */
157: public GeocentricTransform(final double semiMajor,
158: final double semiMinor, final Unit units,
159: final boolean hasHeight) {
160: this .hasHeight = hasHeight;
161: final Converter converter = units.getConverterTo(SI.METER);
162: a = converter.convert(semiMajor);
163: b = converter.convert(semiMinor);
164: a2 = a * a;
165: b2 = b * b;
166: e2 = (a2 - b2) / a2;
167: ep2 = (a2 - b2) / b2;
168: checkArgument("a", a, Double.MAX_VALUE);
169: checkArgument("b", b, a);
170: }
171:
172: /**
173: * Checks an argument value. The argument must be greater
174: * than 0 and finite, otherwise an exception is thrown.
175: *
176: * @param name The argument name.
177: * @param value The argument value.
178: * @param max The maximal legal argument value.
179: */
180: private static void checkArgument(final String name,
181: final double value, final double max)
182: throws IllegalArgumentException {
183: if (!(value >= 0 && value <= max)) {
184: // Use '!' in order to trap NaN
185: throw new IllegalArgumentException(Errors.format(
186: ErrorKeys.ILLEGAL_ARGUMENT_$2, name, new Double(
187: value)));
188: }
189: }
190:
191: /**
192: * Returns the parameter descriptors for this math transform.
193: */
194: public ParameterDescriptorGroup getParameterDescriptors() {
195: return Provider.PARAMETERS;
196: }
197:
198: /**
199: * Returns the parameter values for this math transform.
200: *
201: * @return A copy of the parameter values for this math transform.
202: */
203: public ParameterValueGroup getParameterValues() {
204: return getParameterValues(getParameterDescriptors());
205: }
206:
207: /**
208: * Returns the parameter values using the specified descriptor.
209: *
210: * @param descriptor The parameter descriptor.
211: * @return A copy of the parameter values for this math transform.
212: */
213: private ParameterValueGroup getParameterValues(
214: final ParameterDescriptorGroup descriptor) {
215: final ParameterValue[] parameters = new ParameterValue[hasHeight ? 2
216: : 3];
217: int index = 0;
218: if (!hasHeight) {
219: final ParameterValue p = new org.geotools.parameter.Parameter(
220: Provider.DIM);
221: p.setValue(2);
222: parameters[index++] = p;
223: }
224: parameters[index++] = new FloatParameter(Provider.SEMI_MAJOR, a);
225: parameters[index++] = new FloatParameter(Provider.SEMI_MINOR, b);
226: return new org.geotools.parameter.ParameterGroup(descriptor,
227: parameters);
228: }
229:
230: /**
231: * Gets the dimension of input points, which is 2 or 3.
232: */
233: public int getSourceDimensions() {
234: return hasHeight ? 3 : 2;
235: }
236:
237: /**
238: * Gets the dimension of output points, which is 3.
239: */
240: public final int getTargetDimensions() {
241: return 3;
242: }
243:
244: /**
245: * Converts geodetic coordinates (longitude, latitude, height) to geocentric
246: * coordinates (x, y, z) according to the current ellipsoid parameters.
247: */
248: public void transform(double[] srcPts, int srcOff, double[] dstPts,
249: int dstOff, int numPts) {
250: transform(srcPts, srcOff, dstPts, dstOff, numPts, false);
251: }
252:
253: /**
254: * Implementation of geodetic to geocentric conversion. This implementation allows the caller
255: * to use height in computation. This is used for assertion with {@link #checkTransform}.
256: */
257: private void transform(double[] srcPts, int srcOff,
258: final double[] dstPts, int dstOff, int numPts,
259: boolean hasHeight) {
260: final int dimSource = getSourceDimensions();
261: hasHeight |= (dimSource >= 3);
262: if (srcPts == dstPts
263: && needCopy(srcOff, dimSource, dstOff, 3, numPts)) {
264: // Source and destination arrays overlaps: copy in a temporary buffer.
265: final double[] old = srcPts;
266: srcPts = new double[numPts * (hasHeight ? 3 : 2)];
267: System.arraycopy(old, srcOff, srcPts, 0, srcPts.length);
268: srcOff = 0;
269: }
270: while (--numPts >= 0) {
271: final double L = Math.toRadians(srcPts[srcOff++]); // Longitude
272: final double P = Math.toRadians(srcPts[srcOff++]); // Latitude
273: final double h = hasHeight ? srcPts[srcOff++] : 0; // Height above the ellipsoid (m)
274:
275: final double cosLat = Math.cos(P);
276: final double sinLat = Math.sin(P);
277: final double rn = a / Math.sqrt(1 - e2 * (sinLat * sinLat));
278:
279: dstPts[dstOff++] = (rn + h) * cosLat * Math.cos(L); // X: Toward prime meridian
280: dstPts[dstOff++] = (rn + h) * cosLat * Math.sin(L); // Y: Toward East
281: dstPts[dstOff++] = (rn * (1 - e2) + h) * sinLat; // Z: Toward North
282: }
283: }
284:
285: /**
286: * Converts geodetic coordinates (longitude, latitude, height) to geocentric
287: * coordinates (x, y, z) according to the current ellipsoid parameters.
288: */
289: public void transform(float[] srcPts, int srcOff,
290: final float[] dstPts, int dstOff, int numPts) {
291: final int dimSource = getSourceDimensions();
292: final boolean hasHeight = (dimSource >= 3);
293: if (srcPts == dstPts
294: && needCopy(srcOff, dimSource, dstOff, 3, numPts)) {
295: // Source and destination arrays overlaps: copy in a temporary buffer.
296: final float[] old = srcPts;
297: srcPts = new float[numPts * dimSource];
298: System.arraycopy(old, srcOff, srcPts, 0, srcPts.length);
299: srcOff = 0;
300: }
301: while (--numPts >= 0) {
302: final double L = Math.toRadians(srcPts[srcOff++]); // Longitude
303: final double P = Math.toRadians(srcPts[srcOff++]); // Latitude
304: final double h = hasHeight ? srcPts[srcOff++] : 0; // Height above the ellipsoid (m)
305:
306: final double cosLat = Math.cos(P);
307: final double sinLat = Math.sin(P);
308: final double rn = a / Math.sqrt(1 - e2 * (sinLat * sinLat));
309:
310: dstPts[dstOff++] = (float) ((rn + h) * cosLat * Math.cos(L)); // X: Toward prime meridian
311: dstPts[dstOff++] = (float) ((rn + h) * cosLat * Math.sin(L)); // Y: Toward East
312: dstPts[dstOff++] = (float) ((rn * (1 - e2) + h) * sinLat); // Z: Toward North
313: }
314: }
315:
316: /**
317: * Converts geocentric coordinates (x, y, z) to geodetic coordinates
318: * (longitude, latitude, height), according to the current ellipsoid
319: * parameters. The method used here is derived from "An Improved
320: * Algorithm for Geocentric to Geodetic Coordinate Conversion", by
321: * Ralph Toms, Feb 1996.
322: */
323: public void inverseTransform(double[] srcPts, int srcOff,
324: final double[] dstPts, int dstOff, final int numPts) {
325: final int dimTarget = getSourceDimensions();
326: if (srcPts == dstPts
327: && needCopy(srcOff, 3, dstOff, dimTarget, numPts)) {
328: // Source and destination arrays overlaps: copy in a temporary buffer.
329: final double[] old = srcPts;
330: srcPts = new double[numPts * 3];
331: System.arraycopy(old, srcOff, srcPts, 0, srcPts.length);
332: srcOff = 0;
333: }
334: inverseTransform(null, srcPts, srcOff, null, dstPts, dstOff,
335: numPts, dimTarget);
336: }
337:
338: /**
339: * Converts geocentric coordinates (x, y, z) to geodetic coordinates
340: * (longitude, latitude, height), according to the current ellipsoid
341: * parameters. The method used here is derived from "An Improved
342: * Algorithm for Geocentric to Geodetic Coordinate Conversion", by
343: * Ralph Toms, Feb 1996.
344: */
345: public void inverseTransform(float[] srcPts, int srcOff,
346: final float[] dstPts, int dstOff, final int numPts) {
347: final int dimTarget = getSourceDimensions();
348: if (srcPts == dstPts
349: && needCopy(srcOff, 3, dstOff, dimTarget, numPts)) {
350: // Source and destination arrays overlaps: copy in a temporary buffer.
351: final float[] old = srcPts;
352: srcPts = new float[numPts * 3];
353: System.arraycopy(old, srcOff, srcPts, 0, srcPts.length);
354: srcOff = 0;
355: }
356: inverseTransform(srcPts, null, srcOff, dstPts, null, dstOff,
357: numPts, dimTarget);
358: }
359:
360: /**
361: * Implementation of the inverse transformation.
362: */
363: private void inverseTransform(final float[] srcPts1,
364: final double[] srcPts2, int srcOff, final float[] dstPts1,
365: final double[] dstPts2, int dstOff, int numPts,
366: final int dimTarget) {
367: final boolean hasHeight = (dimTarget >= 3);
368: boolean computeHeight = hasHeight;
369: assert (computeHeight = true) == true; // Force computeHeight to true if assertions are enabled.
370: while (--numPts >= 0) {
371: final double x, y, z;
372: if (srcPts2 != null) {
373: x = srcPts2[srcOff++]; // Toward prime meridian
374: y = srcPts2[srcOff++]; // Toward East
375: z = srcPts2[srcOff++]; // Toward North
376: } else {
377: x = srcPts1[srcOff++]; // Toward prime meridian
378: y = srcPts1[srcOff++]; // Toward East
379: z = srcPts1[srcOff++]; // Toward North
380: }
381: // Note: The Java version of 'atan2' work correctly for x==0.
382: // No need for special handling like in the C version.
383: // No special handling neither for latitude. Formulas
384: // below are generic enough, considering that 'atan'
385: // work correctly with infinities (1/0).
386:
387: // Note: Variable names follow the notation used in Toms, Feb 1996
388: final double W2 = x * x + y * y; // square of distance from Z axis
389: final double W = Math.sqrt(W2); // distance from Z axis
390: final double T0 = z * AD_C; // initial estimate of vertical component
391: final double S0 = Math.sqrt(T0 * T0 + W2); // initial estimate of horizontal component
392: final double sin_B0 = T0 / S0; // sin(B0), B0 is estimate of Bowring aux variable
393: final double cos_B0 = W / S0; // cos(B0)
394: final double sin3_B0 = sin_B0 * sin_B0 * sin_B0; // cube of sin(B0)
395: final double T1 = z + b * ep2 * sin3_B0; // corrected estimate of vertical component
396: final double sum = W - a * e2 * (cos_B0 * cos_B0 * cos_B0); // numerator of cos(phi1)
397: final double S1 = Math.sqrt(T1 * T1 + sum * sum); // corrected estimate of horizontal component
398: final double sin_p1 = T1 / S1; // sin(phi1), phi1 is estimated latitude
399: final double cos_p1 = sum / S1; // cos(phi1)
400:
401: final double longitude = Math.toDegrees(Math.atan2(y, x));
402: final double latitude = Math.toDegrees(Math.atan(sin_p1
403: / cos_p1));
404: final double height;
405:
406: if (dstPts2 != null) {
407: dstPts2[dstOff++] = longitude;
408: dstPts2[dstOff++] = latitude;
409: } else {
410: dstPts1[dstOff++] = (float) longitude;
411: dstPts1[dstOff++] = (float) latitude;
412: }
413: if (computeHeight) {
414: final double rn = a
415: / Math.sqrt(1 - e2 * (sin_p1 * sin_p1)); // Earth radius at location
416: if (cos_p1 >= +COS_67P5)
417: height = W / +cos_p1 - rn;
418: else if (cos_p1 <= -COS_67P5)
419: height = W / -cos_p1 - rn;
420: else
421: height = z / sin_p1 + rn * (e2 - 1.0);
422: if (hasHeight) {
423: if (dstPts2 != null) {
424: dstPts2[dstOff++] = height;
425: } else {
426: dstPts1[dstOff++] = (float) height;
427: }
428: }
429: // If assertion are enabled, then transform the
430: // result and compare it with the input array.
431: double distance;
432: assert MAX_ERROR > (distance = checkTransform(new double[] {
433: x, y, z, longitude, latitude, height })) : distance;
434: }
435: }
436: }
437:
438: /**
439: * Transform the last half if the specified array and returns
440: * the distance with the first half. Array {@code points}
441: * must have a length of 6.
442: */
443: private double checkTransform(final double[] points) {
444: transform(points, 3, points, 3, 1, true);
445: final double dx = points[0] - points[3];
446: final double dy = points[1] - points[4];
447: final double dz = points[2] - points[5];
448: return Math.sqrt(dx * dx + dy * dy + dz * dz);
449: }
450:
451: /**
452: * Returns the inverse of this transform.
453: */
454: public MathTransform inverse() {
455: if (inverse == null) {
456: // No need to synchronize; this is not a big deal if this object is created twice.
457: inverse = new Inverse();
458: }
459: return inverse;
460: }
461:
462: /**
463: * Returns a hash value for this transform.
464: */
465: public int hashCode() {
466: final long code = Double.doubleToLongBits(a)
467: + 37
468: * (Double.doubleToLongBits(b) + 37 * (Double
469: .doubleToLongBits(a2) + 37 * (Double
470: .doubleToLongBits(b2) + 37 * (Double
471: .doubleToLongBits(e2) + 37 * (Double
472: .doubleToLongBits(ep2))))));
473: return (int) code ^ (int) (code >>> 32)
474: ^ (int) serialVersionUID;
475: }
476:
477: /**
478: * Compares the specified object with this math transform for equality.
479: */
480: public boolean equals(final Object object) {
481: if (object == this ) {
482: // Slight optimization
483: return true;
484: }
485: if (super .equals(object)) {
486: final GeocentricTransform that = (GeocentricTransform) object;
487: return Double.doubleToLongBits(this .a) == Double
488: .doubleToLongBits(that.a)
489: && Double.doubleToLongBits(this .b) == Double
490: .doubleToLongBits(that.b)
491: && Double.doubleToLongBits(this .a2) == Double
492: .doubleToLongBits(that.a2)
493: && Double.doubleToLongBits(this .b2) == Double
494: .doubleToLongBits(that.b2)
495: && Double.doubleToLongBits(this .e2) == Double
496: .doubleToLongBits(that.e2)
497: && Double.doubleToLongBits(this .ep2) == Double
498: .doubleToLongBits(that.ep2)
499: && this .hasHeight == that.hasHeight;
500: }
501: return false;
502: }
503:
504: /**
505: * Inverse of a geocentric transform.
506: *
507: * @version $Id: GeocentricTransform.java 24384 2007-02-14 00:23:05Z desruisseaux $
508: * @author Martin Desruisseaux
509: */
510: private final class Inverse extends AbstractMathTransform.Inverse
511: implements Serializable {
512: /**
513: * Serial number for interoperability with different versions.
514: */
515: private static final long serialVersionUID = 6942084702259211803L;
516:
517: /**
518: * Default constructor.
519: */
520: public Inverse() {
521: GeocentricTransform.this .super ();
522: }
523:
524: /**
525: * Returns the parameter descriptors for this math transform.
526: */
527: public ParameterDescriptorGroup getParameterDescriptors() {
528: return ProviderInverse.PARAMETERS;
529: }
530:
531: /**
532: * Returns the parameter values for this math transform.
533: *
534: * @return A copy of the parameter values for this math transform.
535: */
536: public ParameterValueGroup getParameterValues() {
537: return GeocentricTransform.this
538: .getParameterValues(getParameterDescriptors());
539: }
540:
541: /**
542: * Inverse transform an array of points.
543: */
544: public void transform(final double[] source,
545: final int srcOffset, final double[] dest,
546: final int dstOffset, final int length) {
547: GeocentricTransform.this .inverseTransform(source,
548: srcOffset, dest, dstOffset, length);
549: }
550:
551: /**
552: * Inverse transform an array of points.
553: */
554: public void transform(final float[] source,
555: final int srcOffset, final float[] dest,
556: final int dstOffset, final int length) {
557: GeocentricTransform.this .inverseTransform(source,
558: srcOffset, dest, dstOffset, length);
559: }
560:
561: /**
562: * Restore reference to this object after deserialization.
563: */
564: private void readObject(ObjectInputStream in)
565: throws IOException, ClassNotFoundException {
566: in.defaultReadObject();
567: GeocentricTransform.this .inverse = this ;
568: }
569: }
570:
571: /**
572: * The provider for {@link GeocentricTransform}. This provider will constructs transforms
573: * from {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS geographic} to
574: * {@linkplain org.geotools.referencing.crs.DefaultGeocentricCRS geocentric} coordinate
575: * reference systems.
576: *
577: * @version $Id: GeocentricTransform.java 24384 2007-02-14 00:23:05Z desruisseaux $
578: * @author Martin Desruisseaux
579: */
580: public static class Provider extends MathTransformProvider {
581: /**
582: * Serial number for interoperability with different versions.
583: */
584: private static final long serialVersionUID = 7043216580786030251L;
585:
586: /**
587: * The operation parameter descriptor for the "semi_major" parameter value.
588: * Valid values range from 0 to infinity.
589: */
590: public static final ParameterDescriptor SEMI_MAJOR = createDescriptor(
591: new NamedIdentifier[] {
592: new NamedIdentifier(Citations.OGC, "semi_major"),
593: new NamedIdentifier(Citations.EPSG,
594: "semi-major axis") //epsg does not specifically define this parameter
595: }, Double.NaN, 0, Double.POSITIVE_INFINITY, SI.METER);
596:
597: /**
598: * The operation parameter descriptor for the "semi_minor" parameter value.
599: * Valid values range from 0 to infinity.
600: */
601: public static final ParameterDescriptor SEMI_MINOR = createDescriptor(
602: new NamedIdentifier[] {
603: new NamedIdentifier(Citations.OGC, "semi_minor"),
604: new NamedIdentifier(Citations.EPSG,
605: "semi-minor axis") //epsg does not specifically define this parameter
606: }, Double.NaN, 0, Double.POSITIVE_INFINITY, SI.METER);
607:
608: /**
609: * The number of geographic dimension (2 or 3). This is a Geotools-specific argument.
610: * The default value is 3, which is the value implied in OGC's WKT.
611: */
612: static final ParameterDescriptor DIM = new DefaultParameterDescriptor(
613: Collections.singletonMap(NAME_KEY, new NamedIdentifier(
614: Citations.GEOTOOLS, "dim")), 3, 2, 3, false);
615:
616: /**
617: * The parameters group.
618: */
619: static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
620: "Ellipsoid_To_Geocentric", // OGC name
621: "Geographic/geocentric conversions", // EPSG name
622: "9602", // EPSG identifier
623: VocabularyKeys.GEOCENTRIC_TRANSFORM); // Geotools name
624:
625: /**
626: * Constructs the parameters group.
627: */
628: static ParameterDescriptorGroup createDescriptorGroup(
629: final String ogc, final String epsgName,
630: final String epsgCode, final int geotools) {
631: return createDescriptorGroup(new NamedIdentifier[] {
632: new NamedIdentifier(Citations.OGC, ogc),
633: new NamedIdentifier(Citations.EPSG, epsgName),
634: new NamedIdentifier(Citations.EPSG, epsgCode),
635: new NamedIdentifier(Citations.GEOTOOLS, Vocabulary
636: .formatInternational(geotools)) },
637: new ParameterDescriptor[] { SEMI_MAJOR, SEMI_MINOR,
638: DIM });
639: }
640:
641: /**
642: * The provider for the 2D case. Will be constructed when first needed.
643: */
644: transient Provider noHeight;
645:
646: /**
647: * Constructs a provider with default parameters.
648: */
649: public Provider() {
650: super (3, 3, PARAMETERS);
651: }
652:
653: /**
654: * Constructs a provider from a set of parameters.
655: *
656: * @param sourceDimensions Number of dimensions in the source CRS of this operation method.
657: * @param targetDimensions Number of dimensions in the target CRS of this operation method.
658: * @param parameters The set of parameters (never {@code null}).
659: */
660: Provider(final int sourceDimensions,
661: final int targetDimensions,
662: final ParameterDescriptorGroup parameters) {
663: super (sourceDimensions, targetDimensions, parameters);
664: }
665:
666: /**
667: * Returns the operation type.
668: */
669: public Class getOperationType() {
670: return Conversion.class;
671: }
672:
673: /**
674: * Creates a transform from the specified group of parameter values.
675: *
676: * @param values The group of parameter values.
677: * @return The created math transform.
678: * @throws ParameterNotFoundException if a required parameter was not found.
679: */
680: protected MathTransform createMathTransform(
681: final ParameterValueGroup values)
682: throws ParameterNotFoundException {
683: final int dimGeographic = intValue(DIM, values);
684: final double semiMajor = doubleValue(SEMI_MAJOR, values);
685: final double semiMinor = doubleValue(SEMI_MINOR, values);
686: final boolean hasHeight = (dimGeographic != 2); // Value may be 0, which default as 3.
687: MathTransform transform = new GeocentricTransform(
688: semiMajor, semiMinor, SI.METER, hasHeight);
689: if (!hasHeight) {
690: if (noHeight == null) {
691: noHeight = new Provider(2, 3, PARAMETERS);
692: }
693: transform = new Delegate(transform, noHeight);
694: }
695: return transform;
696: }
697: }
698:
699: /**
700: * The provider for inverse of {@link GeocentricTransform}. This provider will construct
701: * transforms from {@linkplain org.geotools.referencing.crs.DefaultGeocentricCRS geocentric}
702: * to {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS geographic} coordinate
703: * reference systems.
704: *
705: * @version $Id: GeocentricTransform.java 24384 2007-02-14 00:23:05Z desruisseaux $
706: * @author Martin Desruisseaux
707: */
708: public static class ProviderInverse extends Provider {
709: /**
710: * Serial number for interoperability with different versions.
711: */
712: private static final long serialVersionUID = -7356791540110076789L;
713:
714: /**
715: * The parameters group.
716: *
717: * @todo The EPSG code seems to be the same than for the direct transform.
718: */
719: static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(
720: "Geocentric_To_Ellipsoid", // OGC name
721: "Geographic/geocentric conversions", // EPSG name
722: "9602", // EPSG identifier
723: VocabularyKeys.GEOCENTRIC_TRANSFORM); // Geotools name
724:
725: /**
726: * Creates a provider.
727: */
728: public ProviderInverse() {
729: super (3, 3, PARAMETERS);
730: }
731:
732: /**
733: * Constructs a provider from a set of parameters.
734: *
735: * @param sourceDimensions Number of dimensions in the source CRS of this operation method.
736: * @param targetDimensions Number of dimensions in the target CRS of this operation method.
737: * @param parameters The set of parameters (never {@code null}).
738: */
739: ProviderInverse(final int sourceDimensions,
740: final int targetDimensions,
741: final ParameterDescriptorGroup parameters) {
742: super (sourceDimensions, targetDimensions, parameters);
743: }
744:
745: /**
746: * Creates a transform from the specified group of parameter values.
747: *
748: * @param values The group of parameter values.
749: * @return The created math transform.
750: * @throws ParameterNotFoundException if a required parameter was not found.
751: */
752: public MathTransform createMathTransform(
753: final ParameterValueGroup values)
754: throws ParameterNotFoundException {
755: final int dimGeographic = intValue(DIM, values);
756: final double semiMajor = doubleValue(SEMI_MAJOR, values);
757: final double semiMinor = doubleValue(SEMI_MINOR, values);
758: final boolean hasHeight = (dimGeographic != 2); // Value may be 0, which default as 3.
759: MathTransform transform = new GeocentricTransform(
760: semiMajor, semiMinor, SI.METER, hasHeight)
761: .inverse();
762: if (!hasHeight) {
763: if (noHeight == null) {
764: noHeight = new ProviderInverse(3, 2, PARAMETERS);
765: }
766: transform = new Delegate(transform, noHeight);
767: }
768: return transform;
769: }
770: }
771: }
|