001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
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;
010: * version 2.1 of the License.
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: * This package contains documentation from OpenGIS specifications.
018: * OpenGIS consortium's work is fully acknowledged here.
019: */
020: package org.geotools.referencing.datum;
021:
022: // J2SE dependencies
023: import java.io.Serializable;
024:
025: // OpenGIS dependencies
026: import org.opengis.referencing.datum.GeodeticDatum;
027: import org.opengis.referencing.operation.Matrix;
028: import org.opengis.util.Cloneable;
029:
030: // Geotools dependencies
031: import org.geotools.referencing.operation.matrix.Matrix4;
032: import org.geotools.referencing.operation.matrix.XMatrix;
033: import org.geotools.referencing.wkt.Formattable;
034: import org.geotools.referencing.wkt.Formatter;
035: import org.geotools.resources.i18n.ErrorKeys;
036: import org.geotools.resources.i18n.Errors;
037: import org.geotools.resources.Utilities;
038:
039: /**
040: * Parameters for a geographic transformation between two datum.
041: * The Bursa Wolf parameters should be applied to geocentric coordinates,
042: * where the <var>X</var> axis points towards the Greenwich Prime Meridian,
043: * the <var>Y</var> axis points East, and the <var>Z</var> axis points North.
044: * The "Bursa-Wolf" formula is expressed in matrix form with 7 parameters:
045: *
046: * <p align="center"><img src="../doc-files/BursaWolf.png"></p>
047: *
048: * @since 2.1
049: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/datum/BursaWolfParameters.java $
050: * @version $Id: BursaWolfParameters.java 20874 2006-08-07 10:00:01Z jgarnett $
051: * @author Martin Desruisseaux
052: */
053: public class BursaWolfParameters extends Formattable implements
054: Cloneable, Serializable {
055: /**
056: * Serial number for interoperability with different versions.
057: */
058: private static final long serialVersionUID = 754825592343010900L;
059:
060: /** Bursa Wolf shift in meters. */
061: public double dx;
062:
063: /** Bursa Wolf shift in meters. */
064: public double dy;
065:
066: /** Bursa Wolf shift in meters. */
067: public double dz;
068:
069: /** Bursa Wolf rotation in arc seconds. */
070: public double ex;
071:
072: /** Bursa Wolf rotation in arc seconds. */
073: public double ey;
074:
075: /** Bursa Wolf rotation in arc seconds. */
076: public double ez;
077:
078: /** Bursa Wolf scaling in parts per million. */
079: public double ppm;
080:
081: /** The target datum for this parameters. */
082: public final GeodeticDatum targetDatum;
083:
084: /**
085: * Constructs a transformation info with all parameters set to 0.
086: *
087: * @param target The target datum for this parameters.
088: */
089: public BursaWolfParameters(final GeodeticDatum target) {
090: this .targetDatum = target;
091: }
092:
093: /**
094: * Returns {@code true} if this Bursa Wolf parameters performs no operation.
095: * This is true when all parameters are set to zero.
096: */
097: public boolean isIdentity() {
098: return dx == 0 && dy == 0 && dz == 0 && ex == 0 && ey == 0
099: && ez == 0 && ppm == 0;
100: }
101:
102: /**
103: * Returns {@code true} if this Bursa Wolf parameters contains only translation terms.
104: */
105: public boolean isTranslation() {
106: return ex == 0 && ey == 0 && ez == 0 && ppm == 0;
107: }
108:
109: /**
110: * Returns an affine transform that can be used to define this
111: * Bursa Wolf transformation. The formula is as follows:
112: *
113: * <blockquote><pre>
114: * S = 1 + {@link #ppm}/1000000
115: *
116: * [ X’ ] [ S -{@link #ez}*S +{@link #ey}*S {@link #dx} ] [ X ]
117: * [ Y’ ] = [ +{@link #ez}*S S -{@link #ex}*S {@link #dy} ] [ Y }
118: * [ Z’ ] [ -{@link #ey}*S +{@link #ex}*S S {@link #dz} ] [ Z ]
119: * [ 1 ] [ 0 0 0 1 ] [ 1 ]
120: * </pre></blockquote>
121: *
122: * This affine transform can be applied on <strong>geocentric</strong> coordinates.
123: */
124: public XMatrix getAffineTransform() {
125: /*
126: * Note: (ex, ey, ez) is a rotation in arc seconds. We need to convert it into radians
127: * (the R factor in RS). TODO: to be strict, are we supposed to take the sinus of
128: * rotation angles?
129: */
130: final double S = 1 + ppm / 1E+6;
131: final double RS = (Math.PI / (180 * 3600)) * S;
132: return new Matrix4(S, -ez * RS, +ey * RS, dx, +ez * RS, S, -ex
133: * RS, dy, -ey * RS, +ex * RS, S, dz, 0, 0, 0, 1);
134: }
135:
136: /**
137: * Sets transformation info from the specified matrix, which must be affine.
138: * In addition, the matrix minus the last row and last column must be
139: * <A HREF="http://mathworld.wolfram.com/AntisymmetricMatrix.html">antisymmetric</a>.
140: *
141: * @param matrix The matrix to fit as a Bursa-Wolf construct.
142: * @param eps The tolerance error for the antisymmetric matrix test. Should be a small
143: * number like {@code 1E-4}.
144: * @throws IllegalArgumentException if the specified matrix doesn't meet the conditions.
145: *
146: * @since 2.2
147: */
148: public void setAffineTransform(final Matrix matrix, final double eps)
149: throws IllegalArgumentException {
150: if (matrix.getNumCol() != 4 || matrix.getNumRow() != 4) {
151: // TODO: localize. Same message than Matrix4
152: throw new IllegalArgumentException("Illegal matrix size.");
153: }
154: for (int i = 0; i < 4; i++) {
155: if (matrix.getElement(3, i) != (i == 3 ? 1 : 0)) {
156: throw new IllegalArgumentException(Errors
157: .format(ErrorKeys.NON_AFFINE_TRANSFORM));
158: }
159: }
160: dx = matrix.getElement(0, 3);
161: dy = matrix.getElement(1, 3);
162: dz = matrix.getElement(2, 3);
163: final double S = (matrix.getElement(0, 0)
164: + matrix.getElement(1, 1) + matrix.getElement(2, 2)) / 3;
165: final double RS = (Math.PI / (180 * 3600)) * S;
166: ppm = (S - 1) * 1E+6;
167: for (int j = 0; j < 2; j++) {
168: final double eltS = (matrix.getElement(j, j) - 1) * 1E+6;
169: if (!(Math.abs(eltS - ppm) <= eps)) {
170: // TODO: localize
171: throw new IllegalArgumentException(
172: "Scale is not uniform.");
173: }
174: for (int i = j + 1; i < 3; i++) {
175: final double elt1 = matrix.getElement(j, i) / RS;
176: final double elt2 = matrix.getElement(i, j) / RS;
177: // Note: compare with +, not -, because the two values should be opposite.
178: if (!(Math.abs(elt1 + elt2) <= eps)) {
179: // TODO: localize
180: throw new IllegalArgumentException(
181: "Matrix is not antisymmetric.");
182: }
183: final double value = 0.5 * (elt1 - elt2);
184: if (j == 0)
185: switch (i) {
186: case 1:
187: ez = -value;
188: continue;
189: case 2:
190: ey = +value;
191: continue;
192: }
193: assert j == 1 && i == 2;
194: ex = -value;
195: }
196: }
197: assert ((Matrix4) getAffineTransform()).epsilonEquals(
198: new Matrix4(matrix), eps * RS);
199: }
200:
201: /**
202: * Returns a hash value for this object.
203: *
204: * @return The hash code value. This value doesn't need to be the same
205: * in past or future versions of this class.
206: */
207: public int hashCode() {
208: long code = serialVersionUID;
209: code = code * 37 + Double.doubleToLongBits(dx);
210: code = code * 37 + Double.doubleToLongBits(dy);
211: code = code * 37 + Double.doubleToLongBits(dz);
212: code = code * 37 + Double.doubleToLongBits(ex);
213: code = code * 37 + Double.doubleToLongBits(ey);
214: code = code * 37 + Double.doubleToLongBits(ez);
215: code = code * 37 + Double.doubleToLongBits(ppm);
216: return (int) (code >>> 32) ^ (int) code;
217: }
218:
219: /**
220: * Returns a copy of this object.
221: */
222: public Object clone() {
223: try {
224: return super .clone();
225: } catch (CloneNotSupportedException exception) {
226: // Should not happen, since we are cloneable.
227: throw new AssertionError(exception);
228: }
229: }
230:
231: /**
232: * Compares the specified object with this object for equality.
233: */
234: public boolean equals(final Object object) {
235: if (object instanceof BursaWolfParameters) {
236: final BursaWolfParameters that = (BursaWolfParameters) object;
237: return Double.doubleToLongBits(this .dx) == Double
238: .doubleToLongBits(that.dx)
239: && Double.doubleToLongBits(this .dy) == Double
240: .doubleToLongBits(that.dy)
241: && Double.doubleToLongBits(this .dz) == Double
242: .doubleToLongBits(that.dz)
243: && Double.doubleToLongBits(this .ex) == Double
244: .doubleToLongBits(that.ex)
245: && Double.doubleToLongBits(this .ey) == Double
246: .doubleToLongBits(that.ey)
247: && Double.doubleToLongBits(this .ez) == Double
248: .doubleToLongBits(that.ez)
249: && Double.doubleToLongBits(this .ppm) == Double
250: .doubleToLongBits(that.ppm)
251: && Utilities.equals(this .targetDatum,
252: that.targetDatum);
253: }
254: return false;
255: }
256:
257: /**
258: * Format the inner part of a
259: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
260: * Known Text</cite> (WKT)</A> element. The WKT contains the parameters in <i>translation</i>,
261: * <i>rotation</i>, <i>scale</i> order, as in
262: * <code>TOWGS84[{@linkplain #dx}, {@linkplain #dy}, {@linkplain #dz},
263: * {@linkplain #ex}, {@linkplain #ey}, {@linkplain #ez}, {@linkplain #ppm}]</code>.
264: *
265: * @param formatter The formatter to use.
266: * @return The WKT element name.
267: */
268: protected String formatWKT(final Formatter formatter) {
269: formatter.append(dx);
270: formatter.append(dy);
271: formatter.append(dz);
272: formatter.append(ex);
273: formatter.append(ey);
274: formatter.append(ez);
275: formatter.append(ppm);
276: if (!DefaultGeodeticDatum.isWGS84(targetDatum)) {
277: if (targetDatum != null) {
278: formatter.append(targetDatum.getName().getCode());
279: }
280: return super .formatWKT(formatter);
281: }
282: return "TOWGS84";
283: }
284: }
|