001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2003, 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.crs;
021:
022: // J2SE dependencies and extensions
023: import java.util.Map;
024: import javax.units.Unit;
025:
026: // OpenGIS dependencies
027: import org.opengis.referencing.crs.SingleCRS;
028: import org.opengis.referencing.crs.ProjectedCRS;
029: import org.opengis.referencing.crs.GeneralDerivedCRS;
030: import org.opengis.referencing.crs.CoordinateReferenceSystem;
031: import org.opengis.referencing.cs.CoordinateSystem;
032: import org.opengis.referencing.datum.Datum;
033: import org.opengis.referencing.operation.Conversion;
034: import org.opengis.referencing.operation.MathTransform;
035: import org.opengis.referencing.operation.NoninvertibleTransformException;
036: import org.opengis.referencing.operation.OperationMethod;
037: import org.opengis.referencing.operation.Projection;
038: import org.opengis.geometry.MismatchedDimensionException;
039:
040: // Geotools dependencies
041: import org.geotools.parameter.Parameters;
042: import org.geotools.referencing.AbstractIdentifiedObject;
043: import org.geotools.referencing.AbstractReferenceSystem;
044: import org.geotools.referencing.operation.DefaultOperation;
045: import org.geotools.referencing.operation.DefaultConversion;
046: import org.geotools.referencing.operation.DefiningConversion; // For javadoc
047: import org.geotools.referencing.operation.DefaultOperationMethod;
048: import org.geotools.referencing.wkt.Formatter;
049: import org.geotools.resources.i18n.ErrorKeys;
050: import org.geotools.resources.i18n.Errors;
051:
052: /**
053: * A coordinate reference system that is defined by its coordinate
054: * {@linkplain Conversion conversion} from another coordinate reference system
055: * (not by a {@linkplain Datum datum}).
056: * <p>
057: * This class is conceptually <cite>abstract</cite>, even if it is technically possible to
058: * instantiate it. Typical applications should create instances of the most specific subclass with
059: * {@code Default} prefix instead. An exception to this rule may occurs when it is not possible to
060: * identify the exact type.
061: *
062: * @since 2.1
063: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/crs/AbstractDerivedCRS.java $
064: * @version $Id: AbstractDerivedCRS.java 28031 2007-11-23 21:24:22Z desruisseaux $
065: * @author Martin Desruisseaux
066: */
067: public class AbstractDerivedCRS extends AbstractSingleCRS implements
068: GeneralDerivedCRS {
069: /**
070: * Serial number for interoperability with different versions.
071: */
072: private static final long serialVersionUID = -175151161496419854L;
073:
074: /**
075: * Key for the <code>{@value}</code> property to be given to the constructor. The value should
076: * be one of <code>{@linkplain org.opengis.referencing.operation.PlanarProjection}.class</code>,
077: * <code>{@linkplain org.opengis.referencing.operation.CylindricalProjection}.class</code> or
078: * <code>{@linkplain org.opengis.referencing.operation.ConicProjection}.class</code>.
079: * <p>
080: * This is a Geotools specific property used as a hint for creating a {@linkplain Projection
081: * projection} of proper type from a {@linkplain DefiningConversion defining conversion}. In
082: * many cases, this hint is not needed since Geotools is often capable to infer it. This hint is
083: * used mostly by advanced factories like the {@linkplain org.geotools.referencing.factory.epsg
084: * EPSG backed} one.
085: *
086: * @see DefaultConversion#create
087: *
088: * @since 2.4
089: */
090: public static final String CONVERSION_TYPE_KEY = "conversionType";
091:
092: /**
093: * A lock for avoiding never-ending recursivity in the {@code equals} method. This field
094: * contains a {@code boolean} flag set to {@code true} when a comparaison is in progress.
095: * This lock is necessary because {@code AbstractDerivedCRS} objects contain a
096: * {@link #conversionFromBase} field, which contains a {@link DefaultConversion#targetCRS}
097: * field set to this {@code AbstractDerivedCRS} object.
098: * <P>
099: * <STRONG>DO NOT USE THIS FIELD. It is strictly for internal use by {@link #equals} and
100: * {@link org.geotools.referencing.operation.AbstractCoordinateOperation#equals} methods.</STRONG>
101: *
102: * @todo Hide this field from the javadoc. It is not possible to make it package-privated
103: * because {@link org.geotools.referencing.operation.AbstractCoordinateOperation}
104: * lives in a different package.
105: */
106: public static final ThreadLocal/*<Boolean>*/_COMPARING = new ThreadLocal();
107:
108: /**
109: * The base coordinate reference system.
110: */
111: protected final CoordinateReferenceSystem baseCRS;
112:
113: /**
114: * The conversion from the {@linkplain #getBaseCRS base CRS} to this CRS.
115: */
116: protected final Conversion conversionFromBase;
117:
118: /**
119: * Constructs a new derived CRS with the same values than the specified one.
120: * This copy constructor provides a way to wrap an arbitrary implementation into a
121: * Geotools one or a user-defined one (as a subclass), usually in order to leverage
122: * some implementation-specific API. This constructor performs a shallow copy,
123: * i.e. the properties are not cloned.
124: *
125: * @since 2.2
126: */
127: protected AbstractDerivedCRS(final GeneralDerivedCRS crs) {
128: super (crs);
129: baseCRS = crs.getBaseCRS();
130: conversionFromBase = crs.getConversionFromBase();
131: }
132:
133: /**
134: * Constructs a derived CRS from a {@linkplain DefiningConversion defining conversion}.
135: * The properties are given unchanged to the
136: * {@linkplain AbstractReferenceSystem#AbstractReferenceSystem(Map) super-class constructor}.
137: *
138: * @param properties Name and other properties to give to the new derived CRS object.
139: * @param conversionFromBase The {@linkplain DefiningConversion defining conversion}.
140: * @param base Coordinate reference system to base the derived CRS on.
141: * @param baseToDerived The transform from the base CRS to returned CRS.
142: * @param derivedCS The coordinate system for the derived CRS. The number
143: * of axes must match the target dimension of the transform
144: * {@code baseToDerived}.
145: * @throws MismatchedDimensionException if the source and target dimension of
146: * {@code baseToDerived} don't match the dimension of {@code base}
147: * and {@code derivedCS} respectively.
148: */
149: protected AbstractDerivedCRS(final Map properties,
150: final Conversion conversionFromBase,
151: final CoordinateReferenceSystem base,
152: final MathTransform baseToDerived,
153: final CoordinateSystem derivedCS)
154: throws MismatchedDimensionException {
155: super (properties, getDatum(base), derivedCS);
156: ensureNonNull("conversionFromBase", conversionFromBase);
157: ensureNonNull("baseToDerived", baseToDerived);
158: this .baseCRS = base;
159: checkDimensions(base, baseToDerived, derivedCS);
160: DefaultOperationMethod.checkDimensions(conversionFromBase
161: .getMethod(), baseToDerived);
162: final Class typeHint = (Class) properties
163: .get(CONVERSION_TYPE_KEY); // May be null.
164: this .conversionFromBase = DefaultConversion.create(
165: /* definition */conversionFromBase,
166: /* sourceCRS */base,
167: /* targetCRS */this ,
168: /* transform */baseToDerived,
169: /* typeHints */typeHint);
170: }
171:
172: /**
173: * Constructs a derived CRS from a set of properties. The properties are given unchanged to
174: * the {@linkplain AbstractReferenceSystem#AbstractReferenceSystem(Map) super-class constructor}.
175: * The following optional properties are also understood:
176: * <p>
177: * <table border='1'>
178: * <tr bgcolor="#CCCCFF" class="TableHeadingColor">
179: * <th nowrap>Property name</th>
180: * <th nowrap>Value type</th>
181: * <th nowrap>Value given to</th>
182: * </tr>
183: * <tr>
184: * <td nowrap> <code>"conversion.name"</code> </td>
185: * <td nowrap> {@link String} </td>
186: * <td nowrap> <code>{@linkplain #getConversionFromBase}.getName()</code></td>
187: * </tr>
188: * </table>
189: *
190: * <P>
191: * Additional properties for the {@link DefaultConversion} object to be created can be
192: * specified with the <code>"conversion."</code> prefix added in front of property names
193: * (example: <code>"conversion.remarks"</code>). The same applies for operation method,
194: * using the <code>"method."</code> prefix.
195: * </P>
196: *
197: * @param properties Name and other properties to give to the new derived CRS object and to
198: * the underlying {@linkplain DefaultConversion conversion}.
199: * @param method A description of the {@linkplain Conversion#getMethod method for the
200: * conversion}.
201: * @param base Coordinate reference system to base the derived CRS on.
202: * @param baseToDerived The transform from the base CRS to returned CRS.
203: * @param derivedCS The coordinate system for the derived CRS. The number
204: * of axes must match the target dimension of the transform
205: * {@code baseToDerived}.
206: * @throws MismatchedDimensionException if the source and target dimension of
207: * {@code baseToDerived} don't match the dimension of {@code base}
208: * and {@code derivedCS} respectively.
209: *
210: * @deprecated Create explicitly a {@link DefiningConversion} instead.
211: */
212: protected AbstractDerivedCRS(final Map properties,
213: final OperationMethod method,
214: final CoordinateReferenceSystem base,
215: final MathTransform baseToDerived,
216: final CoordinateSystem derivedCS)
217: throws MismatchedDimensionException {
218: super (properties, getDatum(base), derivedCS);
219: ensureNonNull("method", method);
220: ensureNonNull("baseToDerived", baseToDerived);
221: this .baseCRS = base;
222: /*
223: * A method was explicitly specified. Make sure that the source and target
224: * dimensions match. We do not check parameters in current version of this
225: * implementation (we may add this check in a future version), since the
226: * descriptors provided in this user-supplied OperationMethod may be more
227: * accurate than the one inferred from the MathTransform.
228: */
229: checkDimensions(base, baseToDerived, derivedCS);
230: DefaultOperationMethod.checkDimensions(method, baseToDerived);
231: this .conversionFromBase = (Conversion) DefaultOperation
232: .create(
233: /* properties */new UnprefixedMap(properties,
234: "conversion."),
235: /* sourceCRS */base,
236: /* targetCRS */this ,
237: /* transform */baseToDerived,
238: /* method */method,
239: /* type */(this instanceof ProjectedCRS) ? Projection.class
240: : Conversion.class);
241: }
242:
243: /**
244: * Work around for RFE #4093999 in Sun's bug database
245: * ("Relax constraint on placement of this()/super() call in constructors").
246: *
247: * @todo What to do if {@code base} is not an instance of {@link SingleCRS}?
248: */
249: private static Datum getDatum(final CoordinateReferenceSystem base) {
250: ensureNonNull("base", base);
251: return (base instanceof SingleCRS) ? ((SingleCRS) base)
252: .getDatum() : null;
253: }
254:
255: /**
256: * Checks consistency between the base CRS and the "base to derived" transform.
257: */
258: private static void checkDimensions(
259: final CoordinateReferenceSystem base,
260: final MathTransform baseToDerived,
261: final CoordinateSystem derivedCS)
262: throws MismatchedDimensionException {
263: final int dimSource = baseToDerived.getSourceDimensions();
264: final int dimTarget = baseToDerived.getTargetDimensions();
265: int dim1, dim2;
266: if ((dim1 = dimSource) != (dim2 = base.getCoordinateSystem()
267: .getDimension())
268: || (dim1 = dimTarget) != (dim2 = derivedCS
269: .getDimension())) {
270: throw new MismatchedDimensionException(Errors.format(
271: ErrorKeys.MISMATCHED_DIMENSION_$2,
272: new Integer(dim1), new Integer(dim2)));
273: }
274: }
275:
276: /**
277: * Returns the base coordinate reference system.
278: *
279: * @return The base coordinate reference system.
280: */
281: public CoordinateReferenceSystem getBaseCRS() {
282: return baseCRS;
283: }
284:
285: /**
286: * Returns the conversion from the {@linkplain #getBaseCRS base CRS} to this CRS.
287: *
288: * @return The conversion to this CRS.
289: */
290: public Conversion getConversionFromBase() {
291: return conversionFromBase;
292: }
293:
294: /**
295: * Compare this coordinate reference system with the specified object for equality.
296: *
297: * @param object The object to compare to {@code this}.
298: * @param compareMetadata {@code true} for performing a strict comparaison, or
299: * {@code false} for comparing only properties relevant to transformations.
300: * @return {@code true} if both objects are equal.
301: */
302: public boolean equals(final AbstractIdentifiedObject object,
303: final boolean compareMetadata) {
304: if (object == this ) {
305: return true; // Slight optimization.
306: }
307: if (super .equals(object, compareMetadata)) {
308: final AbstractDerivedCRS that = (AbstractDerivedCRS) object;
309: if (equals(this .baseCRS, that.baseCRS, compareMetadata)) {
310: /*
311: * Avoid never-ending recursivity: Conversion has a 'targetCRS' field (inherited from
312: * the AbstractCoordinateOperation super-class) that is set to this AbstractDerivedCRS.
313: */
314: final Boolean comparing = (Boolean) _COMPARING.get();
315: if (comparing != null && comparing.booleanValue()) {
316: return true;
317: }
318: try {
319: _COMPARING.set(Boolean.TRUE);
320: return equals(this .conversionFromBase,
321: that.conversionFromBase, compareMetadata);
322: } finally {
323: _COMPARING.set(Boolean.FALSE);
324: // TODO: use _COMPARING.remove() when we will be allowed to compile for J2SE 1.5.
325: }
326: }
327: }
328: return false;
329: }
330:
331: /**
332: * Returns a hash value for this derived CRS.
333: *
334: * @return The hash code value. This value doesn't need to be the same
335: * in past or future versions of this class.
336: */
337: public int hashCode() {
338: /*
339: * Do not invoke 'conversionFromBase.hashCode()' in order to avoid a never-ending loop.
340: * This is because Conversion has a 'sourceCRS' field (in the AbstractCoordinateOperation
341: * super-class), which is set to this AbstractDerivedCRS. Checking the identifier should
342: * be enough.
343: */
344: return (int) serialVersionUID ^ baseCRS.hashCode()
345: ^ conversionFromBase.getName().hashCode();
346: }
347:
348: /**
349: * Format the inner part of a
350: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
351: * Known Text</cite> (WKT)</A> element.
352: *
353: * @param formatter The formatter to use.
354: * @return The name of the WKT element type, which is {@code "FITTED_CS"}.
355: */
356: protected String formatWKT(final Formatter formatter) {
357: MathTransform inverse = conversionFromBase.getMathTransform();
358: try {
359: inverse = inverse.inverse();
360: } catch (NoninvertibleTransformException exception) {
361: // TODO: provide a more accurate error message. Use J2SE 1.5 constructor.
362: IllegalStateException e = new IllegalStateException(
363: exception.getLocalizedMessage());
364: e.initCause(exception);
365: throw e;
366: }
367: formatter.append(inverse);
368: formatter.append(baseCRS);
369: return "FITTED_CS";
370: }
371: }
|