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.util.Arrays;
024: import java.util.Collections;
025: import java.util.HashMap;
026: import java.util.HashSet;
027: import java.util.LinkedHashSet;
028: import java.util.Map;
029: import java.util.Set;
030:
031: // OpenGIS dependencies
032: import org.opengis.referencing.ReferenceIdentifier;
033: import org.opengis.referencing.datum.Datum;
034: import org.opengis.referencing.datum.Ellipsoid;
035: import org.opengis.referencing.datum.PrimeMeridian;
036: import org.opengis.referencing.datum.GeodeticDatum;
037: import org.opengis.referencing.operation.Matrix;
038:
039: // Geotools dependencies
040: import org.geotools.metadata.iso.citation.Citations;
041: import org.geotools.referencing.operation.matrix.XMatrix;
042: import org.geotools.referencing.AbstractIdentifiedObject;
043: import org.geotools.referencing.NamedIdentifier;
044: import org.geotools.referencing.wkt.Formatter;
045:
046: /**
047: * Defines the location and precise orientation in 3-dimensional space of a defined ellipsoid
048: * (or sphere) that approximates the shape of the earth. Used also for Cartesian coordinate
049: * system centered in this ellipsoid (or sphere).
050: *
051: * @since 2.1
052: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/datum/DefaultGeodeticDatum.java $
053: * @version $Id: DefaultGeodeticDatum.java 25262 2007-04-23 21:11:16Z desruisseaux $
054: * @author Martin Desruisseaux
055: *
056: * @see Ellipsoid
057: * @see PrimeMeridian
058: */
059: public class DefaultGeodeticDatum extends AbstractDatum implements
060: GeodeticDatum {
061: /**
062: * Serial number for interoperability with different versions.
063: */
064: private static final long serialVersionUID = 8832100095648302943L;
065:
066: /**
067: * The default WGS 1984 datum.
068: */
069: public static final DefaultGeodeticDatum WGS84;
070: static {
071: final ReferenceIdentifier[] identifiers = {
072: new NamedIdentifier(Citations.OGC, "WGS84"),
073: new NamedIdentifier(Citations.ORACLE, "WGS 84"),
074: new NamedIdentifier(null, "WGS_84"),
075: new NamedIdentifier(null, "WGS 1984"),
076: new NamedIdentifier(Citations.EPSG, "WGS_1984"),
077: new NamedIdentifier(Citations.ESRI, "D_WGS_1984"),
078: new NamedIdentifier(Citations.EPSG,
079: "World Geodetic System 1984") };
080: final Map properties = new HashMap(4);
081: properties.put(NAME_KEY, identifiers[0]);
082: properties.put(ALIAS_KEY, identifiers);
083: WGS84 = new DefaultGeodeticDatum(properties,
084: DefaultEllipsoid.WGS84, DefaultPrimeMeridian.GREENWICH);
085: }
086:
087: /**
088: * The <code>{@value #BURSA_WOLF_KEY}</code> property for
089: * {@linkplain #getAffineTransform datum shifts}.
090: */
091: public static final String BURSA_WOLF_KEY = "bursaWolf";
092:
093: /**
094: * The ellipsoid.
095: */
096: private final Ellipsoid ellipsoid;
097:
098: /**
099: * The prime meridian.
100: */
101: private final PrimeMeridian primeMeridian;
102:
103: /**
104: * Bursa Wolf parameters for datum shifts, or {@code null} if none.
105: */
106: private final BursaWolfParameters[] bursaWolf;
107:
108: /**
109: * Constructs a new datum with the same values than the specified one.
110: * This copy constructor provides a way to wrap an arbitrary implementation into a
111: * Geotools one or a user-defined one (as a subclass), usually in order to leverage
112: * some implementation-specific API. This constructor performs a shallow copy,
113: * i.e. the properties are not cloned.
114: *
115: * @since 2.2
116: */
117: public DefaultGeodeticDatum(final GeodeticDatum datum) {
118: super (datum);
119: ellipsoid = datum.getEllipsoid();
120: primeMeridian = datum.getPrimeMeridian();
121: bursaWolf = (datum instanceof DefaultGeodeticDatum) ? ((DefaultGeodeticDatum) datum).bursaWolf
122: : null;
123: }
124:
125: /**
126: * Constructs a geodetic datum from a name.
127: *
128: * @param name The datum name.
129: * @param ellipsoid The ellipsoid.
130: * @param primeMeridian The prime meridian.
131: */
132: public DefaultGeodeticDatum(final String name,
133: final Ellipsoid ellipsoid, final PrimeMeridian primeMeridian) {
134: this (Collections.singletonMap(NAME_KEY, name), ellipsoid,
135: primeMeridian);
136: }
137:
138: /**
139: * Constructs a geodetic datum from a set of properties. The properties map is given
140: * unchanged to the {@linkplain AbstractDatum#AbstractDatum(Map) super-class constructor}.
141: * Additionally, the following properties are understood by this construtor:
142: * <p>
143: * <table border='1'>
144: * <tr bgcolor="#CCCCFF" class="TableHeadingColor">
145: * <th nowrap>Property name</th>
146: * <th nowrap>Value type</th>
147: * <th nowrap>Value given to</th>
148: * </tr>
149: * <tr>
150: * <td nowrap> {@link #BURSA_WOLF_KEY "bursaWolf"} </td>
151: * <td nowrap> {@link BursaWolfParameters} or an array of those </td>
152: * <td nowrap> {@link #getBursaWolfParameters}</td>
153: * </tr>
154: * </table>
155: *
156: * @param properties Set of properties. Should contains at least <code>"name"</code>.
157: * @param ellipsoid The ellipsoid.
158: * @param primeMeridian The prime meridian.
159: */
160: public DefaultGeodeticDatum(final Map properties,
161: final Ellipsoid ellipsoid, final PrimeMeridian primeMeridian) {
162: super (properties);
163: this .ellipsoid = ellipsoid;
164: this .primeMeridian = primeMeridian;
165: ensureNonNull("ellipsoid", ellipsoid);
166: ensureNonNull("primeMeridian", primeMeridian);
167: BursaWolfParameters[] bursaWolf;
168: final Object object = properties.get(BURSA_WOLF_KEY);
169: if (object instanceof BursaWolfParameters) {
170: bursaWolf = new BursaWolfParameters[] { (BursaWolfParameters) ((BursaWolfParameters) object)
171: .clone() };
172: } else {
173: bursaWolf = (BursaWolfParameters[]) object;
174: if (bursaWolf != null) {
175: if (bursaWolf.length == 0) {
176: bursaWolf = null;
177: } else {
178: final Set s = new LinkedHashSet();
179: for (int i = 0; i < bursaWolf.length; i++) {
180: s.add((BursaWolfParameters) bursaWolf[i]
181: .clone());
182: }
183: bursaWolf = (BursaWolfParameters[]) s
184: .toArray(new BursaWolfParameters[s.size()]);
185: }
186: }
187: }
188: this .bursaWolf = bursaWolf;
189: }
190:
191: /**
192: * Returns the ellipsoid.
193: */
194: public Ellipsoid getEllipsoid() {
195: return ellipsoid;
196: }
197:
198: /**
199: * Returns the prime meridian.
200: */
201: public PrimeMeridian getPrimeMeridian() {
202: return primeMeridian;
203: }
204:
205: /**
206: * Returns all Bursa Wolf parameters specified in the {@code properties} map at
207: * construction time.
208: *
209: * @since 2.4
210: */
211: public BursaWolfParameters[] getBursaWolfParameters() {
212: if (bursaWolf != null) {
213: return (BursaWolfParameters[]) bursaWolf.clone();
214: }
215: return new BursaWolfParameters[0];
216: }
217:
218: /**
219: * Returns Bursa Wolf parameters for a datum shift toward the specified target, or {@code null}
220: * if none. This method search only for Bursa-Wolf parameters explicitly specified in the
221: * {@code properties} map at construction time. This method doesn't try to infer a set of
222: * parameters from indirect informations. For example it doesn't try to inverse the parameters
223: * specified in the {@code target} datum if none were found in this datum. If such an elaborated
224: * search is wanted, use {@link #getAffineTransform} instead.
225: */
226: public BursaWolfParameters getBursaWolfParameters(
227: final GeodeticDatum target) {
228: if (bursaWolf != null) {
229: for (int i = 0; i < bursaWolf.length; i++) {
230: final BursaWolfParameters candidate = bursaWolf[i];
231: if (equals(target, candidate.targetDatum, false)) {
232: return (BursaWolfParameters) candidate.clone();
233: }
234: }
235: }
236: return null;
237: }
238:
239: /**
240: * Returns a matrix that can be used to define a transformation to the specified datum.
241: * If no transformation path is found, then this method returns {@code null}.
242: *
243: * @param source The source datum.
244: * @param target The target datum.
245: * @return An affine transform from {@code source} to {@code target}, or {@code null} if none.
246: *
247: * @see BursaWolfParameters#getAffineTransform
248: */
249: public static Matrix getAffineTransform(final GeodeticDatum source,
250: final GeodeticDatum target) {
251: return getAffineTransform(source, target, null);
252: }
253:
254: /**
255: * Returns a matrix that can be used to define a transformation to the specified datum.
256: * If no transformation path is found, then this method returns {@code null}.
257: *
258: * @param source The source datum.
259: * @param target The target datum.
260: * @param exclusion The set of datum to exclude from the search, or {@code null}.
261: * This is used in order to avoid never-ending recursivity.
262: * @return An affine transform from {@code source} to {@code target}, or {@code null} if none.
263: *
264: * @see BursaWolfParameters#getAffineTransform
265: */
266: private static XMatrix getAffineTransform(
267: final GeodeticDatum source, final GeodeticDatum target,
268: Set exclusion) {
269: ensureNonNull("source", source);
270: ensureNonNull("target", target);
271: if (source instanceof DefaultGeodeticDatum) {
272: final BursaWolfParameters[] bursaWolf = ((DefaultGeodeticDatum) source).bursaWolf;
273: if (bursaWolf != null) {
274: for (int i = 0; i < bursaWolf.length; i++) {
275: final BursaWolfParameters transformation = bursaWolf[i];
276: if (equals(target, transformation.targetDatum,
277: false)) {
278: return transformation.getAffineTransform();
279: }
280: }
281: }
282: }
283: /*
284: * No transformation found to the specified target datum.
285: * Search if a transform exists in the opposite direction.
286: */
287: if (target instanceof DefaultGeodeticDatum) {
288: final BursaWolfParameters[] bursaWolf = ((DefaultGeodeticDatum) target).bursaWolf;
289: if (bursaWolf != null) {
290: for (int i = 0; i < bursaWolf.length; i++) {
291: final BursaWolfParameters transformation = bursaWolf[i];
292: if (equals(source, transformation.targetDatum,
293: false)) {
294: final XMatrix matrix = transformation
295: .getAffineTransform();
296: matrix.invert();
297: return matrix;
298: }
299: }
300: }
301: }
302: /*
303: * No direct tranformation found. Search for a path through some intermediate datum.
304: * First, search if there is some BursaWolfParameters for the same target in both
305: * 'source' and 'target' datum. If such an intermediate is found, ask for a path
306: * as below:
307: *
308: * source --> [common datum] --> target
309: */
310: if (source instanceof DefaultGeodeticDatum
311: && target instanceof DefaultGeodeticDatum) {
312: final BursaWolfParameters[] sourceParam = ((DefaultGeodeticDatum) source).bursaWolf;
313: final BursaWolfParameters[] targetParam = ((DefaultGeodeticDatum) target).bursaWolf;
314: if (sourceParam != null && targetParam != null) {
315: GeodeticDatum sourceStep;
316: GeodeticDatum targetStep;
317: for (int i = 0; i < sourceParam.length; i++) {
318: sourceStep = sourceParam[i].targetDatum;
319: for (int j = 0; j < targetParam.length; j++) {
320: targetStep = targetParam[j].targetDatum;
321: if (equals(sourceStep, targetStep, false)) {
322: final XMatrix step1, step2;
323: if (exclusion == null) {
324: exclusion = new HashSet();
325: }
326: if (exclusion.add(source)) {
327: if (exclusion.add(target)) {
328: step1 = getAffineTransform(source,
329: sourceStep, exclusion);
330: if (step1 != null) {
331: step2 = getAffineTransform(
332: targetStep, target,
333: exclusion);
334: if (step2 != null) {
335: /*
336: * Note: XMatrix.multiply(XMatrix) is equivalent to
337: * AffineTransform.concatenate(...): First
338: * transform by the supplied transform and
339: * then transform the result by the original
340: * transform.
341: */
342: step2.multiply(step1);
343: return step2;
344: }
345: }
346: exclusion.remove(target);
347: }
348: exclusion.remove(source);
349: }
350: }
351: }
352: }
353: }
354: }
355: return null;
356: }
357:
358: /**
359: * Returns {@code true} if the specified object is equals (at least on
360: * computation purpose) to the {@link #WGS84} datum. This method may conservatively
361: * returns {@code false} if the specified datum is uncertain (for example
362: * because it come from an other implementation).
363: */
364: public static boolean isWGS84(final Datum datum) {
365: if (datum instanceof AbstractIdentifiedObject) {
366: return WGS84
367: .equals((AbstractIdentifiedObject) datum, false);
368: }
369: // Maybe the specified object has its own test...
370: return datum != null && datum.equals(WGS84);
371: }
372:
373: /**
374: * Compare this datum with the specified object for equality.
375: *
376: * @param object The object to compare to {@code this}.
377: * @param compareMetadata {@code true} for performing a strict comparaison, or
378: * {@code false} for comparing only properties relevant to transformations.
379: * @return {@code true} if both objects are equal.
380: */
381: public boolean equals(final AbstractIdentifiedObject object,
382: final boolean compareMetadata) {
383: if (object == this ) {
384: return true; // Slight optimization.
385: }
386: if (super .equals(object, compareMetadata)) {
387: final DefaultGeodeticDatum that = (DefaultGeodeticDatum) object;
388: if (equals(this .ellipsoid, that.ellipsoid, compareMetadata)
389: && equals(this .primeMeridian, that.primeMeridian,
390: compareMetadata)) {
391: /*
392: * HACK: We do not consider Bursa Wolf parameters as a non-metadata field.
393: * This is needed in order to get equalsIgnoreMetadata(...) to returns
394: * 'true' when comparing the WGS84 constant in this class with a WKT
395: * DATUM element with a TOWGS84[0,0,0,0,0,0,0] element. Furthermore,
396: * the Bursa Wolf parameters are not part of ISO 19111 specification.
397: * We don't want two CRS to be considered as different because one has
398: * more of those transformation informations (which is nice, but doesn't
399: * change the CRS itself).
400: */
401: return !compareMetadata
402: || Arrays
403: .equals(this .bursaWolf, that.bursaWolf);
404: }
405: }
406: return false;
407: }
408:
409: /**
410: * Returns a hash value for this geodetic datum. {@linkplain #getName Name},
411: * {@linkplain #getRemarks remarks} and the like are not taken in account. In
412: * other words, two geodetic datums will return the same hash value if they
413: * are equal in the sense of
414: * <code>{@link #equals equals}(AbstractIdentifiedObject, <strong>false</strong>)</code>.
415: *
416: * @return The hash code value. This value doesn't need to be the same
417: * in past or future versions of this class.
418: */
419: public int hashCode() {
420: int code = (int) serialVersionUID
421: ^ 37
422: * (super .hashCode() ^ 37 * (ellipsoid.hashCode() ^ 37 * (primeMeridian
423: .hashCode())));
424: return code;
425: }
426:
427: /**
428: * Format the inner part of a
429: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
430: * Known Text</cite> (WKT)</A> element.
431: *
432: * @param formatter The formatter to use.
433: * @return The WKT element name, which is "DATUM"
434: */
435: protected String formatWKT(final Formatter formatter) {
436: // Do NOT invokes the super-class method, because
437: // horizontal datum do not write the datum type.
438: formatter.append(ellipsoid);
439: if (bursaWolf != null) {
440: for (int i = 0; i < bursaWolf.length; i++) {
441: final BursaWolfParameters transformation = bursaWolf[i];
442: if (isWGS84(transformation.targetDatum)) {
443: formatter.append(transformation);
444: break;
445: }
446: }
447: }
448: return "DATUM";
449: }
450: }
|