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; 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.resources;
018:
019: // J2SE dependencies and extensions
020: import java.util.List;
021: import java.util.Iterator;
022: import java.awt.geom.AffineTransform;
023: import java.awt.geom.Point2D;
024: import java.awt.geom.Rectangle2D;
025: import javax.units.Unit;
026:
027: // OpenGIS dependencies
028: import org.opengis.referencing.FactoryException;
029: import org.opengis.referencing.crs.CompoundCRS;
030: import org.opengis.referencing.crs.CoordinateReferenceSystem;
031: import org.opengis.referencing.crs.GeneralDerivedCRS;
032: import org.opengis.referencing.crs.GeographicCRS;
033: import org.opengis.referencing.crs.ProjectedCRS;
034: import org.opengis.referencing.crs.SingleCRS;
035: import org.opengis.referencing.crs.TemporalCRS;
036: import org.opengis.referencing.crs.VerticalCRS;
037: import org.opengis.referencing.cs.AxisDirection;
038: import org.opengis.referencing.cs.CoordinateSystem;
039: import org.opengis.referencing.cs.CoordinateSystemAxis;
040: import org.opengis.referencing.datum.Datum;
041: import org.opengis.referencing.datum.Ellipsoid;
042: import org.opengis.referencing.datum.GeodeticDatum;
043: import org.opengis.referencing.operation.CoordinateOperation;
044: import org.opengis.referencing.operation.MathTransform;
045: import org.opengis.referencing.operation.MathTransform2D;
046: import org.opengis.referencing.operation.TransformException;
047: import org.opengis.geometry.Envelope;
048: import org.opengis.geometry.DirectPosition;
049: import org.opengis.geometry.MismatchedDimensionException;
050:
051: // Geotools dependencies
052: import org.geotools.geometry.GeneralDirectPosition;
053: import org.geotools.geometry.GeneralEnvelope;
054: import org.geotools.measure.AngleFormat;
055: import org.geotools.measure.Latitude;
056: import org.geotools.measure.Longitude;
057: import org.geotools.referencing.CRS;
058: import org.geotools.referencing.ReferencingFactoryFinder;
059: import org.geotools.referencing.crs.DefaultGeographicCRS;
060: import org.geotools.resources.i18n.ErrorKeys;
061: import org.geotools.resources.i18n.Errors;
062: import org.geotools.resources.geometry.XRectangle2D;
063: import org.geotools.util.UnsupportedImplementationException;
064:
065: /**
066: * A set of static methods working on OpenGIS®
067: * {@linkplain CoordinateReferenceSystem coordinate reference system} objects.
068: * Some of those methods are useful, but not really rigorous. This is why they
069: * do not appear in the "official" package, but instead in this private one.
070: * <strong>Do not rely on this API!</strong> It may change in incompatible way
071: * in any future release.
072: *
073: * @since 2.0
074: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/resources/CRSUtilities.java $
075: * @version $Id: CRSUtilities.java 25787 2007-06-10 16:04:55Z desruisseaux $
076: * @author Martin Desruisseaux
077: */
078: public final class CRSUtilities {
079: /**
080: * Do not allow creation of instances of this class.
081: */
082: private CRSUtilities() {
083: }
084:
085: /**
086: * @deprecated Moved into the {@link CRS} class.
087: */
088: public static boolean equalsIgnoreMetadata(final Object object1,
089: final Object object2) {
090: return CRS.equalsIgnoreMetadata(object1, object2);
091: }
092:
093: /**
094: * Returns the dimension within the coordinate system of the first occurrence of an axis
095: * colinear with the specified axis. If an axis with the same
096: * {@linkplain CoordinateSystemAxis#getDirection direction} or an
097: * {@linkplain AxisDirection#inverse opposite} direction than {@code axis}
098: * ocurs in the coordinate system, then the dimension of the first such occurrence
099: * is returned. That is, the a value <i>k</i> such that:
100: *
101: * <blockquote><pre>
102: * cs.getAxis(<i>k</i>).getDirection().absolute() == axis.getDirection().absolute()
103: * </pre></blockquote>
104: *
105: * is {@code true}. If no such axis occurs in this coordinate system,
106: * then {@code -1} is returned.
107: * <p>
108: * For example, {@code dimensionColinearWith(CoordinateSystemAxis.TIME)}
109: * returns the dimension number of time axis.
110: *
111: * @param cs The coordinate system to examine.
112: * @param axis The axis to look for.
113: * @return The dimension number of the specified axis, or {@code -1} if none.
114: */
115: public static int dimensionColinearWith(final CoordinateSystem cs,
116: final CoordinateSystemAxis axis) {
117: int candidate = -1;
118: final int dimension = cs.getDimension();
119: final AxisDirection direction = axis.getDirection().absolute();
120: for (int i = 0; i < dimension; i++) {
121: final CoordinateSystemAxis xi = cs.getAxis(i);
122: if (direction.equals(xi.getDirection().absolute())) {
123: candidate = i;
124: if (axis.equals(xi)) {
125: break;
126: }
127: }
128: }
129: return candidate;
130: }
131:
132: /**
133: * Returns the unit used for all axis in the specified coordinate system.
134: * If not all axis uses the same unit, then this method returns {@code null}.
135: * This convenience method is often used for Well Know Text (WKT) formatting.
136: *
137: * @since 2.2
138: */
139: public static Unit getUnit(final CoordinateSystem cs) {
140: Unit unit = null;
141: for (int i = cs.getDimension(); --i >= 0;) {
142: final Unit candidate = cs.getAxis(i).getUnit();
143: if (candidate != null) {
144: if (unit == null) {
145: unit = candidate;
146: } else if (!unit.equals(candidate)) {
147: return null;
148: }
149: }
150: }
151: return unit;
152: }
153:
154: /**
155: * Returns the dimension of the first coordinate reference system of the given type. The
156: * {@code type} argument must be a subinterface of {@link CoordinateReferenceSystem}.
157: * If no such dimension is found, then this method returns {@code -1}.
158: *
159: * @param crs The coordinate reference system (CRS) to examine.
160: * @param type The CRS type to look for.
161: * Must be a subclass of {@link CoordinateReferenceSystem}.
162: * @return The dimension range of the specified CRS type, or {@code -1} if none.
163: * @throws IllegalArgumentException if the {@code type} is not legal.
164: */
165: public static int getDimensionOf(
166: final CoordinateReferenceSystem crs, final Class type)
167: throws IllegalArgumentException {
168: if (!CoordinateReferenceSystem.class.isAssignableFrom(type)) {
169: throw new IllegalArgumentException(type.getName());
170: }
171: if (type.isAssignableFrom(crs.getClass())) {
172: return 0;
173: }
174: if (crs instanceof CompoundCRS) {
175: final List/*<CoordinateReferenceSystem>*/c = ((CompoundCRS) crs)
176: .getCoordinateReferenceSystems();
177: int offset = 0;
178: for (final Iterator it = c.iterator(); it.hasNext();) {
179: final CoordinateReferenceSystem ci = (CoordinateReferenceSystem) it
180: .next();
181: final int index = getDimensionOf(ci, type);
182: if (index >= 0) {
183: return index + offset;
184: }
185: offset += ci.getCoordinateSystem().getDimension();
186: }
187: }
188: return -1;
189: }
190:
191: /**
192: * Returns a sub-coordinate reference system for the specified dimension range.
193: *
194: * @param crs The coordinate reference system to decompose.
195: * @param lower The first dimension to keep, inclusive.
196: * @param upper The last dimension to keep, exclusive.
197: * @return The sub-coordinate system, or {@code null} if {@code crs} can't
198: * be decomposed for dimensions in the range {@code [lower..upper]}.
199: */
200: public static CoordinateReferenceSystem getSubCRS(
201: CoordinateReferenceSystem crs, int lower, int upper) {
202: int dimension = crs.getCoordinateSystem().getDimension();
203: if (lower < 0 || lower > upper || upper > dimension) {
204: throw new IndexOutOfBoundsException(Errors.format(
205: ErrorKeys.INDEX_OUT_OF_BOUNDS_$1, new Integer(
206: lower < 0 ? lower : upper)));
207: }
208: while (lower != 0 || upper != dimension) {
209: if (!(crs instanceof CompoundCRS)) {
210: return null;
211: }
212: final List/*<CoordinateReferenceSystem>*/c = ((CompoundCRS) crs)
213: .getCoordinateReferenceSystems();
214: if (c == null || c.isEmpty()) {
215: return null;
216: }
217: for (final Iterator it = c.iterator(); it.hasNext();) {
218: crs = (CoordinateReferenceSystem) it.next();
219: dimension = crs.getCoordinateSystem().getDimension();
220: if (lower < dimension) {
221: break;
222: }
223: lower -= dimension;
224: upper -= dimension;
225: }
226: }
227: return crs;
228: }
229:
230: /**
231: * Returns a two-dimensional coordinate reference system representing the two first dimensions
232: * of the specified coordinate reference system. If {@code crs} is already a two-dimensional
233: * CRS, then it is returned unchanged. Otherwise, if it is a {@link CompoundCRS}, then the
234: * head coordinate reference system is examined.
235: *
236: * @param crs The coordinate system, or {@code null}.
237: * @return A two-dimensional coordinate reference system that represents the two first
238: * dimensions of {@code crs}, or {@code null} if {@code crs} was {@code null}.
239: * @throws TransformException if {@code crs} can't be reduced to a two-coordinate system.
240: * We use this exception class since this method is usually invoked in the context
241: * of a transformation process.
242: */
243: public static CoordinateReferenceSystem getCRS2D(
244: CoordinateReferenceSystem crs) throws TransformException {
245: if (crs != null) {
246: while (crs.getCoordinateSystem().getDimension() != 2) {
247: if (!(crs instanceof CompoundCRS)) {
248: throw new TransformException(Errors.format(
249: ErrorKeys.CANT_REDUCE_TO_TWO_DIMENSIONS_$1,
250: crs.getName().toString()));
251: }
252: final List/*<CoordinateReferenceSystem>*/c = ((CompoundCRS) crs)
253: .getCoordinateReferenceSystems();
254: if (c == null || c.isEmpty()) {
255: return null;
256: }
257: crs = (CoordinateReferenceSystem) c.get(0);
258: }
259: }
260: return crs;
261: }
262:
263: /**
264: * @deprecated Moved into the {@link CRS} class.
265: */
266: public static SingleCRS getHorizontalCRS(
267: final CoordinateReferenceSystem crs) {
268: return CRS.getHorizontalCRS(crs);
269: }
270:
271: /**
272: * @deprecated Moved into the {@link CRS} class.
273: */
274: public static ProjectedCRS getProjectedCRS(
275: final CoordinateReferenceSystem crs) {
276: return CRS.getProjectedCRS(crs);
277: }
278:
279: /**
280: * @deprecated Moved into the {@link CRS} class.
281: */
282: public static VerticalCRS getVerticalCRS(
283: final CoordinateReferenceSystem crs) {
284: return CRS.getVerticalCRS(crs);
285: }
286:
287: /**
288: * @deprecated Moved into the {@link CRS} class.
289: */
290: public static TemporalCRS getTemporalCRS(
291: final CoordinateReferenceSystem crs) {
292: return CRS.getTemporalCRS(crs);
293: }
294:
295: /**
296: * Returns the datum of the specified CRS, or {@code null} if none.
297: */
298: public static Datum getDatum(final CoordinateReferenceSystem crs) {
299: return (crs instanceof SingleCRS) ? ((SingleCRS) crs)
300: .getDatum() : null;
301: }
302:
303: /**
304: * @deprecated Moved into the {@link CRS} class.
305: */
306: public static Ellipsoid getEllipsoid(
307: final CoordinateReferenceSystem crs) {
308: return CRS.getEllipsoid(crs);
309: }
310:
311: /**
312: * Returns the ellipsoid used by the specified coordinate reference system, providing that
313: * the two first dimensions use an instance of {@link GeographicCRS}. Otherwise (i.e. if the
314: * two first dimensions are not geographic), returns {@code null}.
315: */
316: public static Ellipsoid getHeadGeoEllipsoid(
317: CoordinateReferenceSystem crs) {
318: while (!(crs instanceof GeographicCRS)) {
319: if (crs instanceof CompoundCRS) {
320: final List/*<CoordinateReferenceSystem>*/c = ((CompoundCRS) crs)
321: .getCoordinateReferenceSystems();
322: if (c != null && !c.isEmpty()) {
323: crs = (CoordinateReferenceSystem) c.get(0);
324: continue;
325: }
326: }
327: return null;
328: }
329: // Remove first cast when covariance will be allowed (J2SE 1.5).
330: return ((GeodeticDatum) ((GeographicCRS) crs).getDatum())
331: .getEllipsoid();
332: }
333:
334: /**
335: * @deprecated Moved into the {@link CRS} class.
336: */
337: public static GeneralEnvelope transform(
338: final MathTransform transform, final Envelope envelope)
339: throws TransformException {
340: return CRS.transform(transform, envelope);
341: }
342:
343: /**
344: * @deprecated Moved into the {@link CRS} class.
345: */
346: public static Rectangle2D transform(
347: final MathTransform2D transform, final Rectangle2D source,
348: final Rectangle2D dest) throws TransformException {
349: return CRS.transform(transform, source, dest);
350: }
351:
352: /**
353: * Transforms the relative distance vector specified by {@code source} and stores
354: * the result in {@code dest}. A relative distance vector is transformed without
355: * applying the translation components.
356: *
357: * @param transform The transform to apply.
358: * @param origin The position where to compute the delta transform in the source CS.
359: * @param source The distance vector to be delta transformed
360: * @return The result of the transformation.
361: * @throws TransformException if the transformation failed.
362: *
363: * @since 2.3
364: */
365: public static DirectPosition deltaTransform(
366: final MathTransform transform, final DirectPosition origin,
367: final DirectPosition source) throws TransformException {
368: final int sourceDim = transform.getSourceDimensions();
369: final int targetDim = transform.getTargetDimensions();
370: DirectPosition P1 = new GeneralDirectPosition(sourceDim);
371: DirectPosition P2 = new GeneralDirectPosition(sourceDim);
372: for (int i = 0; i < sourceDim; i++) {
373: final double c = origin.getOrdinate(i);
374: final double d = source.getOrdinate(i) * 0.5;
375: P1.setOrdinate(i, c - d);
376: P2.setOrdinate(i, c + d);
377: }
378: P1 = transform.transform(P1, (sourceDim == targetDim) ? P1
379: : null);
380: P2 = transform.transform(P2, (sourceDim == targetDim) ? P2
381: : null);
382: for (int i = 0; i < targetDim; i++) {
383: P2.setOrdinate(i, P2.getOrdinate(i) - P1.getOrdinate(i));
384: }
385: return P2;
386: }
387:
388: /**
389: * Transforms the relative distance vector specified by {@code source} and stores
390: * the result in {@code dest}. A relative distance vector is transformed without
391: * applying the translation components.
392: *
393: * @param transform The transform to apply.
394: * @param origin The position where to compute the delta transform in the source CS.
395: * @param source The distance vector to be delta transformed
396: * @param dest The resulting transformed distance vector, or {@code null}
397: * @return The result of the transformation.
398: * @throws TransformException if the transformation failed.
399: *
400: * @see AffineTransform#deltaTransform(Point2D,Point2D)
401: */
402: public static Point2D deltaTransform(
403: final MathTransform2D transform, final Point2D origin,
404: final Point2D source, Point2D dest)
405: throws TransformException {
406: if (transform instanceof AffineTransform) {
407: return ((AffineTransform) transform).deltaTransform(source,
408: dest);
409: }
410: final double ox = origin.getX();
411: final double oy = origin.getY();
412: final double dx = source.getX() * 0.5;
413: final double dy = source.getY() * 0.5;
414: Point2D P1 = new Point2D.Double(ox - dx, oy - dy);
415: Point2D P2 = new Point2D.Double(ox + dx, oy + dy);
416: P1 = transform.transform(P1, P1);
417: P2 = transform.transform(P2, P2);
418: if (dest == null) {
419: dest = P2;
420: }
421: dest.setLocation(P2.getX() - P1.getX(), P2.getY() - P1.getY());
422: return dest;
423: }
424:
425: /**
426: * Returns a character string for the specified geographic area. The string will have the
427: * form "45°00.00'N-50°00.00'N 30°00.00'E-40°00.00'E". If a map projection is required in
428: * order to obtain this representation, it will be automatically applied. This string is
429: * mostly used for debugging purpose.
430: *
431: * @todo Move this method as a static method in {@link org.geotools.referencing.CRS}.
432: * Or yet better: move formatting code in {@code GeographicBoundingBox.toString()}
433: * method, and move the transformation code into {@code GeographicBoundingBox}
434: * constructor.
435: */
436: public static String toWGS84String(CoordinateReferenceSystem crs,
437: Rectangle2D bounds) {
438: Exception exception;
439: StringBuffer buffer = new StringBuffer();
440: try {
441: crs = getCRS2D(crs);
442: if (!CRS.equalsIgnoreMetadata(DefaultGeographicCRS.WGS84,
443: crs)) {
444: final CoordinateOperation op = ReferencingFactoryFinder
445: .getCoordinateOperationFactory(null)
446: .createOperation(crs,
447: DefaultGeographicCRS.WGS84);
448: bounds = CRS.transform(op, bounds, null);
449: }
450: final AngleFormat fmt = new AngleFormat("DD°MM.m'");
451: buffer = fmt.format(new Latitude(bounds.getMinY()), buffer,
452: null);
453: buffer.append('-');
454: buffer = fmt.format(new Latitude(bounds.getMaxY()), buffer,
455: null);
456: buffer.append(' ');
457: buffer = fmt.format(new Longitude(bounds.getMinX()),
458: buffer, null);
459: buffer.append('-');
460: buffer = fmt.format(new Longitude(bounds.getMaxX()),
461: buffer, null);
462: return buffer.toString();
463: } catch (TransformException e) {
464: exception = e;
465: } catch (FactoryException e) {
466: exception = e;
467: }
468: buffer.append(Utilities.getShortClassName(exception));
469: final String message = exception.getLocalizedMessage();
470: if (message != null) {
471: buffer.append(": ");
472: buffer.append(message);
473: }
474: return buffer.toString();
475: }
476: }
|