0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
0005: *
0006: * This library is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation;
0009: * version 2.1 of the License.
0010: *
0011: * This library is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: */
0016: package org.geotools.referencing;
0017:
0018: // J2SE dependencies
0019: import java.util.Set;
0020: import java.util.Map;
0021: import java.util.List;
0022: import java.util.HashSet;
0023: import java.util.Iterator;
0024: import java.util.Collections;
0025: import java.util.NoSuchElementException;
0026: import java.awt.geom.Rectangle2D;
0027: import java.awt.geom.Point2D;
0028: import javax.swing.event.ChangeEvent;
0029: import javax.swing.event.ChangeListener;
0030:
0031: // OpenGIS dependencies
0032: import org.opengis.metadata.Identifier;
0033: import org.opengis.metadata.extent.Extent;
0034: import org.opengis.metadata.extent.BoundingPolygon;
0035: import org.opengis.metadata.extent.GeographicExtent;
0036: import org.opengis.metadata.extent.GeographicBoundingBox;
0037: import org.opengis.referencing.*;
0038: import org.opengis.referencing.crs.*;
0039: import org.opengis.referencing.datum.*;
0040: import org.opengis.referencing.operation.*;
0041: import org.opengis.referencing.cs.CoordinateSystem;
0042: import org.opengis.referencing.cs.CoordinateSystemAxis;
0043: import org.opengis.referencing.operation.CoordinateOperation;
0044: import org.opengis.referencing.operation.CoordinateOperationFactory;
0045: import org.opengis.referencing.operation.MathTransform;
0046: import org.opengis.referencing.operation.TransformException;
0047: import org.opengis.geometry.Envelope;
0048: import org.opengis.geometry.DirectPosition;
0049: import org.opengis.geometry.MismatchedDimensionException;
0050: import org.opengis.geometry.MismatchedReferenceSystemException;
0051:
0052: // Geotools dependencies
0053: import org.geotools.factory.GeoTools;
0054: import org.geotools.factory.Hints;
0055: import org.geotools.factory.Factory;
0056: import org.geotools.factory.FactoryNotFoundException;
0057: import org.geotools.factory.FactoryRegistryException;
0058: import org.geotools.geometry.Envelope2D;
0059: import org.geotools.geometry.GeneralEnvelope;
0060: import org.geotools.geometry.GeneralDirectPosition;
0061: import org.geotools.metadata.iso.extent.GeographicBoundingBoxImpl;
0062: import org.geotools.referencing.crs.DefaultGeographicCRS;
0063: import org.geotools.referencing.factory.AbstractAuthorityFactory;
0064: import org.geotools.referencing.factory.IdentifiedObjectFinder;
0065: import org.geotools.referencing.operation.transform.IdentityTransform;
0066: import org.geotools.resources.geometry.XRectangle2D;
0067: import org.geotools.resources.CRSUtilities;
0068: import org.geotools.resources.i18n.Errors;
0069: import org.geotools.resources.i18n.ErrorKeys;
0070: import org.geotools.util.Version;
0071: import org.geotools.util.logging.Logging;
0072: import org.geotools.util.UnsupportedImplementationException;
0073:
0074: /**
0075: * Simple utility class for making use of the {@linkplain CoordinateReferenceSystem
0076: * coordinate reference system} and associated {@linkplain org.opengis.referencing.Factory}
0077: * implementations. This utility class is made up of static final functions. This class is
0078: * not a factory or a builder. It makes use of the GeoAPI factory interfaces provided by
0079: * {@link ReferencingFactoryFinder}.
0080: * <p>
0081: * The following methods may be added in a future version:
0082: * <ul>
0083: * <li>{@code CoordinateReferenceSystem parseXML(String)}</li>
0084: * </ul>
0085: *
0086: * @since 2.1
0087: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/CRS.java $
0088: * @version $Id: CRS.java 29058 2008-02-03 17:47:07Z desruisseaux $
0089: * @author Jody Garnett (Refractions Research)
0090: * @author Martin Desruisseaux
0091: * @author Andrea Aime
0092: *
0093: * @tutorial http://docs.codehaus.org/display/GEOTOOLS/Coordinate+Transformation+Services+for+Geotools+2.1
0094: */
0095: public final class CRS {
0096: /**
0097: * The logger name to use for logging messages in this class.
0098: */
0099: private static final String LOGGER = "org.geotools.referencing";
0100:
0101: /**
0102: * A factory for CRS creation with (<var>latitude</var>, <var>longitude</var>) axis order
0103: * (unless otherwise specified in system property). Will be created only when first needed.
0104: */
0105: private static CRSAuthorityFactory defaultFactory;
0106:
0107: /**
0108: * A factory for CRS creation with (<var>longitude</var>, <var>latitude</var>) axis order.
0109: * Will be created only when first needed.
0110: */
0111: private static CRSAuthorityFactory xyFactory;
0112:
0113: /**
0114: * A factory for default (non-lenient) operations.
0115: */
0116: private static CoordinateOperationFactory strictFactory;
0117:
0118: /**
0119: * A factory for default lenient operations.
0120: */
0121: private static CoordinateOperationFactory lenientFactory;
0122:
0123: static {
0124: GeoTools.addChangeListener(new ChangeListener() {
0125: // Automatically invoked when the system-wide configuration changed.
0126: public void stateChanged(ChangeEvent e) {
0127: synchronized (CRS.class) {
0128: defaultFactory = null;
0129: xyFactory = null;
0130: strictFactory = null;
0131: lenientFactory = null;
0132: }
0133: }
0134: });
0135: }
0136:
0137: /**
0138: * Do not allow instantiation of this class.
0139: */
0140: private CRS() {
0141: }
0142:
0143: //////////////////////////////////////////////////////////////
0144: //// ////
0145: //// FACTORIES, CRS CREATION AND INSPECTION ////
0146: //// ////
0147: //////////////////////////////////////////////////////////////
0148:
0149: /**
0150: * Returns the CRS authority factory used by the {@link #decode(String,boolean) decode} methods.
0151: * This factory is {@linkplain org.geotools.referencing.factory.BufferedAuthorityFactory buffered},
0152: * scans over {@linkplain org.geotools.referencing.factory.AllAuthoritiesFactory all factories} and
0153: * uses additional factories as {@linkplain org.geotools.referencing.factory.FallbackAuthorityFactory
0154: * fallbacks} if there is more than one {@linkplain ReferencingFactoryFinder#getCRSAuthorityFactories
0155: * registered factory} for the same authority.
0156: * <p>
0157: * This factory can be used as a kind of <cite>system-wide</cite> factory for all authorities.
0158: * However for more determinist behavior, consider using a more specific factory (as returned
0159: * by {@link ReferencingFactoryFinder#getCRSAuthorityFactory} when the authority in known.
0160: *
0161: * @param longitudeFirst {@code true} if axis order should be forced to
0162: * (<var>longitude</var>,<var>latitude</var>). Note that {@code false} means
0163: * "<cite>use default</cite>", <strong>not</strong> "<cite>latitude first</cite>".
0164: * @return The CRS authority factory.
0165: * @throws FactoryRegistryException if the factory can't be created.
0166: *
0167: * @since 2.3
0168: */
0169: public static synchronized CRSAuthorityFactory getAuthorityFactory(
0170: final boolean longitudeFirst)
0171: throws FactoryRegistryException {
0172: CRSAuthorityFactory factory = (longitudeFirst) ? xyFactory
0173: : defaultFactory;
0174: if (factory == null)
0175: try {
0176: factory = new DefaultAuthorityFactory(longitudeFirst);
0177: if (longitudeFirst) {
0178: xyFactory = factory;
0179: } else {
0180: defaultFactory = factory;
0181: }
0182: } catch (NoSuchElementException exception) {
0183: // No factory registered in FactoryFinder.
0184: throw new FactoryNotFoundException(null, exception);
0185: }
0186: return factory;
0187: }
0188:
0189: /**
0190: * Returns the coordinate operation factory used by
0191: * {@link #findMathTransform(CoordinateReferenceSystem, CoordinateReferenceSystem)
0192: * findMathTransform} convenience methods.
0193: *
0194: * @param lenient {@code true} if the coordinate operations should be created
0195: * even when there is no information available for a datum shift.
0196: *
0197: * @since 2.4
0198: */
0199: public static synchronized CoordinateOperationFactory getCoordinateOperationFactory(
0200: final boolean lenient) {
0201: CoordinateOperationFactory factory = (lenient) ? lenientFactory
0202: : strictFactory;
0203: if (factory == null) {
0204: final Hints hints = GeoTools.getDefaultHints();
0205: if (lenient) {
0206: hints.put(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE);
0207: }
0208: factory = ReferencingFactoryFinder
0209: .getCoordinateOperationFactory(hints);
0210: if (lenient) {
0211: lenientFactory = factory;
0212: } else {
0213: strictFactory = factory;
0214: }
0215: }
0216: return factory;
0217: }
0218:
0219: /**
0220: * Returns the version number of the specified authority database, or {@code null} if
0221: * not available.
0222: *
0223: * @param authority The authority name (typically {@code "EPSG"}).
0224: * @return The version number of the authority database, or {@code null} if unknown.
0225: * @throws FactoryRegistryException if no {@link CRSAuthorityFactory} implementation
0226: * was found for the specified authority.
0227: *
0228: * @since 2.4
0229: */
0230: public static Version getVersion(final String authority)
0231: throws FactoryRegistryException {
0232: Object factory = ReferencingFactoryFinder
0233: .getCRSAuthorityFactory(authority, null);
0234: final Set guard = new HashSet(); // Safety against never-ending recursivity.
0235: while (factory instanceof Factory && guard.add(factory)) {
0236: final Map hints = ((Factory) factory)
0237: .getImplementationHints();
0238: final Object version = hints.get(Hints.VERSION);
0239: if (version instanceof Version) {
0240: return (Version) version;
0241: }
0242: factory = hints.get(Hints.CRS_AUTHORITY_FACTORY);
0243: }
0244: return null;
0245: }
0246:
0247: /**
0248: * Get the list of the codes that are supported by the given authority. For example
0249: * {@code getSupportedCodes("EPSG")} may returns {@code "EPSG:2000"}, {@code "EPSG:2001"},
0250: * {@code "EPSG:2002"}, <cite>etc</cite>. It may also returns {@code "2000"}, {@code "2001"},
0251: * {@code "2002"}, <cite>etc.</cite> without the {@code "EPSG:"} prefix. Whatever the authority
0252: * name is prefixed or not is factory implementation dependent.
0253: * <p>
0254: * If there is more than one factory for the given authority, then this method merges the
0255: * code set of all of them. If a factory fails to provide a set of supported code, then
0256: * this particular factory is ignored. Please be aware of the following potential issues:
0257: * <p>
0258: * <ul>
0259: * <li>If there is more than one EPSG databases (for example an
0260: * {@linkplain org.geotools.referencing.factory.epsg.AccessDataSource Access} and a
0261: * {@linkplain org.geotools.referencing.factory.epsg.PostgreDataSource PostgreSQL} ones),
0262: * then this method will connect to all of them even if their content are identical.</li>
0263: *
0264: * <li>If two factories format their codes differently (e.g. {@code "4326"} and
0265: * {@code "EPSG:4326"}), then the returned set will contain a lot of synonymous
0266: * codes.</li>
0267: *
0268: * <li>For any code <var>c</var> in the returned set, there is no warranty that
0269: * <code>{@linkplain #decode decode}(c)</code> will use the same authority
0270: * factory than the one that formatted <var>c</var>.</li>
0271: *
0272: * <li>This method doesn't report connection problems since it doesn't throw any exception.
0273: * {@link FactoryException}s are logged as warnings and otherwise ignored.</li>
0274: * </ul>
0275: * <p>
0276: * If a more determinist behavior is wanted, consider the code below instead.
0277: * The following code exploit only one factory, the "preferred" one.
0278: *
0279: * <blockquote><code>
0280: * {@linkplain CRSAuthorityFactory} factory = FactoryFinder.{@linkplain
0281: * ReferencingFactoryFinder#getCRSAuthorityFactory getCRSAuthorityFactory}(authority, null);<br>
0282: * Set<String> codes = factory.{@linkplain CRSAuthorityFactory#getAuthorityCodes
0283: * getAuthorityCodes}(CoordinateReferenceSystem.class);<br>
0284: * String code = <cite>...choose a code here...</cite><br>
0285: * {@linkplain CoordinateReferenceSystem} crs = factory.createCoordinateReferenceSystem(code);
0286: * </code></blockquote>
0287: *
0288: * @param authority The authority name (for example {@code "EPSG"}).
0289: * @return The set of supported codes. May be empty, but never null.
0290: */
0291: public static Set/*<String>*/getSupportedCodes(
0292: final String authority) {
0293: return DefaultAuthorityFactory.getSupportedCodes(authority);
0294: }
0295:
0296: /**
0297: * Returns the set of the authority identifiers supported by registered authority factories.
0298: * This method search only for {@linkplain CRSAuthorityFactory CRS authority factories}.
0299: *
0300: * @param returnAliases If {@code true}, the set will contain all identifiers for each
0301: * authority. If {@code false}, only the first one
0302: * @return The set of supported authorities. May be empty, but never null.
0303: *
0304: * @since 2.3.1
0305: */
0306: public static Set/*<String>*/getSupportedAuthorities(
0307: final boolean returnAliases) {
0308: return DefaultAuthorityFactory
0309: .getSupportedAuthorities(returnAliases);
0310: }
0311:
0312: /**
0313: * Return a Coordinate Reference System for the specified code.
0314: * Note that the code needs to mention the authority. Examples:
0315: *
0316: * <blockquote><pre>
0317: * EPSG:1234
0318: * AUTO:42001, ..., ..., ...
0319: * </pre></blockquote>
0320: *
0321: * If there is more than one factory implementation for the same authority, then all additional
0322: * factories are {@linkplain org.geotools.referencing.factory.FallbackAuthorityFactory fallbacks}
0323: * to be used only when the first acceptable factory failed to create the requested CRS object.
0324: * <p>
0325: * CRS objects created by previous calls to this method are
0326: * {@linkplain org.geotools.referencing.factory.BufferedAuthorityFactory cached in a buffer}
0327: * using {@linkplain java.lang.ref.WeakReference weak references}. Subsequent calls to this
0328: * method with the same authority code should be fast, unless the CRS object has been garbage
0329: * collected.
0330: *
0331: * @param code The Coordinate Reference System authority code.
0332: * @return The Coordinate Reference System for the provided code.
0333: * @throws NoSuchAuthorityCodeException If the code could not be understood.
0334: * @throws FactoryException if the CRS creation failed for an other reason.
0335: *
0336: * @see #getSupportedCodes
0337: * @see org.geotools.referencing.factory.AllAuthoritiesFactory#createCoordinateReferenceSystem
0338: */
0339: public static CoordinateReferenceSystem decode(final String code)
0340: throws NoSuchAuthorityCodeException, FactoryException {
0341: /*
0342: * Do not use Boolean.getBoolean(GeoTools.FORCE_LONGITUDE_FIRST_AXIS_ORDER).
0343: * The boolean argument should be 'false', which means "use system default"
0344: * (not "latitude first").
0345: */
0346: return decode(code, false);
0347: }
0348:
0349: /**
0350: * Return a Coordinate Reference System for the specified code, maybe forcing the axis order
0351: * to (<var>longitude</var>, <var>latitude</var>). The {@code code} argument value is parsed
0352: * as in "<code>{@linkplain #decode(String) decode}(code)</code>". The {@code longitudeFirst}
0353: * argument value controls the hints to be given to the {@linkplain ReferencingFactoryFinder
0354: * factory finder} as in the following pseudo-code:
0355: * <p>
0356: * <blockquote><pre>
0357: * if (longitudeFirst) {
0358: * hints.put({@linkplain Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER}, {@linkplain Boolean#TRUE});
0359: * } else {
0360: * // Do not set the FORCE_LONGITUDE_FIRST_AXIS_ORDER hint to FALSE.
0361: * // Left it unset, which means "use system default".
0362: * }
0363: * </pre></blockquote>
0364: *
0365: * The following table compare this method {@code longitudeFirst} argument with the
0366: * hint meaning:
0367: * <p>
0368: * <table border='1'>
0369: * <tr>
0370: * <th>This method argument</th>
0371: * <th>{@linkplain Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER Hint} value</th>
0372: * <th>Meaning</th>
0373: * </tr>
0374: * <tr>
0375: * <td>{@code true}</td>
0376: * <td>{@link Boolean#TRUE TRUE}</td>
0377: * <td>All coordinate reference systems are forced to
0378: * (<var>longitude</var>, <var>latitude</var>) axis order.</td>
0379: * </tr>
0380: * <tr>
0381: * <td>{@code false}</td>
0382: * <td>{@code null}</td>
0383: * <td>Coordinate reference systems may or may not be forced to
0384: * (<var>longitude</var>, <var>latitude</var>) axis order. The behavior depends on user
0385: * setting, for example the value of the <code>{@value
0386: * org.geotools.referencing.factory.epsg.LongitudeFirstFactory#SYSTEM_DEFAULT_KEY}</code>
0387: * system property.</td>
0388: * </tr>
0389: * <tr>
0390: * <td></td>
0391: * <td>{@link Boolean#FALSE FALSE}</td>
0392: * <td>Forcing (<var>longitude</var>, <var>latitude</var>) axis order is not allowed,
0393: * no matter the value of the <code>{@value
0394: * org.geotools.referencing.factory.epsg.LongitudeFirstFactory#SYSTEM_DEFAULT_KEY}</code>
0395: * system property.</td>
0396: * </tr>
0397: * </table>
0398: *
0399: * @param code The Coordinate Reference System authority code.
0400: * @param longitudeFirst {@code true} if axis order should be forced to
0401: * (<var>longitude</var>, <var>latitude</var>). Note that {@code false} means
0402: * "<cite>use default</cite>", <strong>not</strong> "<cite>latitude first</cite>".
0403: * @return The Coordinate Reference System for the provided code.
0404: * @throws NoSuchAuthorityCodeException If the code could not be understood.
0405: * @throws FactoryException if the CRS creation failed for an other reason.
0406: *
0407: * @see #getSupportedCodes
0408: * @see Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER
0409: *
0410: * @since 2.3
0411: */
0412: public static CoordinateReferenceSystem decode(String code,
0413: final boolean longitudeFirst)
0414: throws NoSuchAuthorityCodeException, FactoryException {
0415: // @deprecated: 'toUpperCase()' is required only for epsg-wkt.
0416: // Remove after we deleted the epsg-wkt module.
0417: code = code.trim().toUpperCase();
0418: return getAuthorityFactory(longitudeFirst)
0419: .createCoordinateReferenceSystem(code);
0420: }
0421:
0422: /**
0423: * Parses a
0424: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
0425: * Known Text</cite></A> (WKT) into a CRS object. This convenience method is a
0426: * shorthand for the following:
0427: *
0428: * <blockquote><code>
0429: * FactoryFinder.{@linkplain ReferencingFactoryFinder#getCRSFactory getCRSFactory}(null).{@linkplain
0430: * org.opengis.referencing.crs.CRSFactory#createFromWKT createFromWKT}(wkt);
0431: * </code></blockquote>
0432: */
0433: public static CoordinateReferenceSystem parseWKT(final String wkt)
0434: throws FactoryException {
0435: return ReferencingFactoryFinder.getCRSFactory(null)
0436: .createFromWKT(wkt);
0437: }
0438:
0439: /**
0440: * Returns the valid area bounding box for the specified coordinate reference system, or
0441: * {@code null} if unknown. This method search in the metadata informations associated with
0442: * the given CRS. The returned envelope is expressed in terms of the specified CRS.
0443: *
0444: * @param crs The coordinate reference system, or {@code null}.
0445: * @return The envelope in terms of the specified CRS, or {@code null} if none.
0446: *
0447: * @since 2.2
0448: */
0449: public static Envelope getEnvelope(CoordinateReferenceSystem crs) {
0450: Envelope envelope = getGeographicEnvelope(crs);
0451: if (envelope != null) {
0452: final CoordinateReferenceSystem sourceCRS = envelope
0453: .getCoordinateReferenceSystem();
0454: if (sourceCRS != null)
0455: try {
0456: crs = CRSUtilities.getCRS2D(crs);
0457: if (!equalsIgnoreMetadata(sourceCRS, crs)) {
0458: final GeneralEnvelope e;
0459: e = transform(findMathTransform(sourceCRS, crs,
0460: true), envelope);
0461: e.setCoordinateReferenceSystem(crs);
0462: envelope = e;
0463: }
0464: } catch (FactoryException exception) {
0465: /*
0466: * No transformation path was found for the specified CRS. Logs a warning and
0467: * returns null, since it is a legal return value according this method contract.
0468: */
0469: envelope = null;
0470: unexpectedException("getEnvelope", exception);
0471: } catch (TransformException exception) {
0472: /*
0473: * The envelope is probably outside the range of validity for this CRS.
0474: * It should not occurs, since the envelope is supposed to describe the
0475: * CRS area of validity. Logs a warning and returns null, since it is a
0476: * legal return value according this method contract.
0477: */
0478: envelope = null;
0479: unexpectedException("getEnvelope", exception);
0480: }
0481: }
0482: return envelope;
0483: }
0484:
0485: /**
0486: * Returns the valid area bounding box for the specified coordinate reference system, or
0487: * {@code null} if unknown. This method search in the metadata informations associated with
0488: * the given CRS. The returned envelope is always expressed in terms of the
0489: * {@linkplain DefaultGeographicCRS#WGS_84 WGS 84} CRS.
0490: *
0491: * @param crs The coordinate reference system, or {@code null}.
0492: * @return The envelope, or {@code null} if none.
0493: */
0494: private static Envelope getGeographicEnvelope(
0495: final CoordinateReferenceSystem crs) {
0496: GeneralEnvelope envelope = null;
0497: if (crs != null) {
0498: final Extent validArea = crs.getValidArea();
0499: if (validArea != null) {
0500: for (final Iterator it = validArea
0501: .getGeographicElements().iterator(); it
0502: .hasNext();) {
0503: final GeographicExtent geo = (GeographicExtent) it
0504: .next();
0505: final GeneralEnvelope candidate;
0506: if (geo instanceof GeographicBoundingBox) {
0507: final GeographicBoundingBox bounds = (GeographicBoundingBox) geo;
0508: final Boolean inclusion = bounds.getInclusion();
0509: if (inclusion == null) {
0510: // Status unknow; ignore this bounding box.
0511: continue;
0512: }
0513: if (!inclusion.booleanValue()) {
0514: // TODO: we could uses Envelope.substract if such
0515: // a method is defined in a future version.
0516: continue;
0517: }
0518: candidate = new GeneralEnvelope(
0519: new double[] {
0520: bounds.getWestBoundLongitude(),
0521: bounds.getSouthBoundLatitude() },
0522: new double[] {
0523: bounds.getEastBoundLongitude(),
0524: bounds.getNorthBoundLatitude() });
0525: candidate
0526: .setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84);
0527: } else if (geo instanceof BoundingPolygon) {
0528: // TODO: iterates through all polygons and invoke Polygon.getEnvelope();
0529: continue;
0530: } else {
0531: continue;
0532: }
0533: if (envelope == null) {
0534: envelope = candidate;
0535: } else {
0536: envelope.add(candidate);
0537: }
0538: }
0539: }
0540: }
0541: return envelope;
0542: }
0543:
0544: /**
0545: * Returns the valid geographic area for the specified coordinate reference system, or
0546: * {@code null} if unknown. This method search in the metadata informations associated
0547: * with the given CRS.
0548: *
0549: * @param crs The coordinate reference system, or {@code null}.
0550: * @return The geographic area, or {@code null} if none.
0551: *
0552: * @since 2.3
0553: */
0554: public static GeographicBoundingBox getGeographicBoundingBox(
0555: final CoordinateReferenceSystem crs) {
0556: final Envelope envelope = getGeographicEnvelope(crs);
0557: if (envelope != null)
0558: try {
0559: return new GeographicBoundingBoxImpl(envelope);
0560: } catch (TransformException exception) {
0561: /*
0562: * Should not occurs, since envelopes are usually already in geographic coordinates.
0563: * If it occurs anyway, returns null since it is allowed by this method contract.
0564: */
0565: unexpectedException("getGeographicBoundingBox",
0566: exception);
0567: }
0568: return null;
0569: }
0570:
0571: /**
0572: * Returns the first horizontal coordinate reference system found in the given CRS,
0573: * or {@code null} if there is none. A horizontal CRS is usually a two-dimensional
0574: * {@linkplain GeographicCRS geographic} or {@linkplain ProjectedCRS projected} CRS.
0575: *
0576: * @since 2.4
0577: */
0578: public static SingleCRS getHorizontalCRS(
0579: final CoordinateReferenceSystem crs) {
0580: if (crs instanceof SingleCRS
0581: && crs.getCoordinateSystem().getDimension() == 2) {
0582: CoordinateReferenceSystem base = crs;
0583: while (base instanceof GeneralDerivedCRS) {
0584: base = ((GeneralDerivedCRS) base).getBaseCRS();
0585: }
0586: // No need to test for ProjectedCRS, since the code above unwrap it.
0587: if (base instanceof GeographicCRS) {
0588: return (SingleCRS) crs; // Really returns 'crs', not 'base'.
0589: }
0590: }
0591: if (crs instanceof CompoundCRS) {
0592: final List/*<CoordinateReferenceSystem>*/c = ((CompoundCRS) crs)
0593: .getCoordinateReferenceSystems();
0594: for (final Iterator it = c.iterator(); it.hasNext();) {
0595: final SingleCRS candidate = getHorizontalCRS((CoordinateReferenceSystem) it
0596: .next());
0597: if (candidate != null) {
0598: return candidate;
0599: }
0600: }
0601: }
0602: return null;
0603: }
0604:
0605: /**
0606: * Returns the first projected coordinate reference system found in a the given CRS,
0607: * or {@code null} if there is none.
0608: *
0609: * @since 2.4
0610: */
0611: public static ProjectedCRS getProjectedCRS(
0612: final CoordinateReferenceSystem crs) {
0613: if (crs instanceof ProjectedCRS) {
0614: return (ProjectedCRS) crs;
0615: }
0616: if (crs instanceof CompoundCRS) {
0617: final List/*<CoordinateReferenceSystem>*/c = ((CompoundCRS) crs)
0618: .getCoordinateReferenceSystems();
0619: for (final Iterator it = c.iterator(); it.hasNext();) {
0620: final ProjectedCRS candidate = getProjectedCRS((CoordinateReferenceSystem) it
0621: .next());
0622: if (candidate != null) {
0623: return candidate;
0624: }
0625: }
0626: }
0627: return null;
0628: }
0629:
0630: /**
0631: * Returns the first vertical coordinate reference system found in a the given CRS,
0632: * or {@code null} if there is none.
0633: *
0634: * @since 2.4
0635: */
0636: public static VerticalCRS getVerticalCRS(
0637: final CoordinateReferenceSystem crs) {
0638: if (crs instanceof VerticalCRS) {
0639: return (VerticalCRS) crs;
0640: }
0641: if (crs instanceof CompoundCRS) {
0642: final List/*<CoordinateReferenceSystem>*/c = ((CompoundCRS) crs)
0643: .getCoordinateReferenceSystems();
0644: for (final Iterator it = c.iterator(); it.hasNext();) {
0645: final VerticalCRS candidate = getVerticalCRS((CoordinateReferenceSystem) it
0646: .next());
0647: if (candidate != null) {
0648: return candidate;
0649: }
0650: }
0651: }
0652: return null;
0653: }
0654:
0655: /**
0656: * Returns the first temporal coordinate reference system found in the given CRS,
0657: * or {@code null} if there is none.
0658: *
0659: * @since 2.4
0660: */
0661: public static TemporalCRS getTemporalCRS(
0662: final CoordinateReferenceSystem crs) {
0663: if (crs instanceof TemporalCRS) {
0664: return (TemporalCRS) crs;
0665: }
0666: if (crs instanceof CompoundCRS) {
0667: final List/*<CoordinateReferenceSystem>*/c = ((CompoundCRS) crs)
0668: .getCoordinateReferenceSystems();
0669: for (final Iterator it = c.iterator(); it.hasNext();) {
0670: final TemporalCRS candidate = getTemporalCRS((CoordinateReferenceSystem) it
0671: .next());
0672: if (candidate != null) {
0673: return candidate;
0674: }
0675: }
0676: }
0677: return null;
0678: }
0679:
0680: /**
0681: * Returns the first ellipsoid found in a coordinate reference system,
0682: * or {@code null} if there is none.
0683: *
0684: * @since 2.4
0685: */
0686: public static Ellipsoid getEllipsoid(
0687: final CoordinateReferenceSystem crs) {
0688: final Datum datum = CRSUtilities.getDatum(crs);
0689: if (datum instanceof GeodeticDatum) {
0690: return ((GeodeticDatum) datum).getEllipsoid();
0691: }
0692: if (crs instanceof CompoundCRS) {
0693: final List/*<CoordinateReferenceSystem>*/c = ((CompoundCRS) crs)
0694: .getCoordinateReferenceSystems();
0695: for (final Iterator it = c.iterator(); it.hasNext();) {
0696: final Ellipsoid candidate = getEllipsoid((CoordinateReferenceSystem) it
0697: .next());
0698: if (candidate != null) {
0699: return candidate;
0700: }
0701: }
0702: }
0703: return null;
0704: }
0705:
0706: /**
0707: * Compares the specified objects for equality. If both objects are Geotools
0708: * implementations of class {@link AbstractIdentifiedObject}, then this method
0709: * will ignore the metadata during the comparaison.
0710: *
0711: * @param object1 The first object to compare (may be null).
0712: * @param object2 The second object to compare (may be null).
0713: * @return {@code true} if both objects are equals.
0714: *
0715: * @since 2.2
0716: */
0717: public static boolean equalsIgnoreMetadata(final Object object1,
0718: final Object object2) {
0719: if (object1 == object2) {
0720: return true;
0721: }
0722: if (object1 instanceof AbstractIdentifiedObject
0723: && object2 instanceof AbstractIdentifiedObject) {
0724: return ((AbstractIdentifiedObject) object1).equals(
0725: ((AbstractIdentifiedObject) object2), false);
0726: }
0727: return object1 != null && object1.equals(object2);
0728: }
0729:
0730: /**
0731: * Looks up an identifier for the specified object. This method searchs in registered factories
0732: * for an object {@linkplain #equalsIgnoreMetadata equals, ignoring metadata}, to the specified
0733: * object. If such object is found, then its identifier is returned. Otherwise this method
0734: * returns {@code null}.
0735: * <p>
0736: * This convenience method delegates its work to {@link IdentifiedObjectFinder}. Consider using
0737: * the later if more control are wanted, for example if the search shall be performed only on
0738: * some {@linkplain AuthorityFactory authority factories} instead of all registered onez, or
0739: * if the full {@linkplain IdentifiedObject identified object} is wanted instead of only its
0740: * identifier.
0741: *
0742: * @param object The object (usually a {@linkplain CoordinateReferenceSystem coordinate
0743: * reference system}) looked up.
0744: * @param fullScan If {@code true}, an exhaustive full scan against all registered objects
0745: * will be performed (may be slow). Otherwise only a fast lookup based on embedded
0746: * identifiers and names will be performed.
0747: * @return The identifier, or {@code null} if not found.
0748: * @throws FactoryException if an unexpected failure occured during the search.
0749: *
0750: * @see AbstractAuthorityFactory#getIdentifiedObjectFinder
0751: * @see IdentifiedObjectFinder#find
0752: *
0753: * @since 2.4
0754: */
0755: public static String lookupIdentifier(
0756: final IdentifiedObject object, final boolean fullScan)
0757: throws FactoryException {
0758: /*
0759: * We perform the search using the 'xyFactory' because our implementation of
0760: * IdentifiedObjectFinder should be able to inspect both the (x,y) and (y,x)
0761: * axis order using this factory.
0762: */
0763: final AbstractAuthorityFactory xyFactory = (AbstractAuthorityFactory) getAuthorityFactory(true);
0764: final IdentifiedObjectFinder finder = xyFactory
0765: .getIdentifiedObjectFinder(object.getClass());
0766: finder.setFullScanAllowed(fullScan);
0767: return finder.findIdentifier(object);
0768: }
0769:
0770: /**
0771: * Looks up an identifier for the specified coordinate reference system.
0772: *
0773: * @param crs the coordinate reference system looked up.
0774: * @param authorities the authority that we should look up the identifier into.
0775: * If {@code null} the search will to be performed against all authorities.
0776: * @param fullScan if {@code true}, an exhaustive full scan against all registered CRS
0777: * will be performed (may be slow). Otherwise only a fast lookup based on embedded
0778: * identifiers and names will be performed.
0779: * @return The identifier, or {@code null} if not found.
0780: *
0781: * @since 2.3.1
0782: *
0783: * @deprecated Replaced by {@link #lookupIdentifier(IdentifiedObject, boolean)},
0784: * which should be faster since it tries to leverage database index.
0785: */
0786: public static String lookupIdentifier(
0787: final CoordinateReferenceSystem crs,
0788: Set/*<String>*/authorities, final boolean fullScan) {
0789: // gather the authorities we're considering
0790: if (authorities == null) {
0791: authorities = getSupportedAuthorities(false);
0792: }
0793: // first check if one of the identifiers can be used to spot directly
0794: // a CRS (and check it's actually equal to one in the db)
0795: for (Iterator it = crs.getIdentifiers().iterator(); it
0796: .hasNext();) {
0797: final Identifier id = (Identifier) it.next();
0798: final CoordinateReferenceSystem candidate;
0799: try {
0800: candidate = CRS.decode(id.toString());
0801: } catch (FactoryException e) {
0802: // the identifier was not recognized, no problem, let's go on
0803: continue;
0804: }
0805: if (equalsIgnoreMetadata(candidate, crs)) {
0806: String identifier = getSRSFromCRS(candidate,
0807: authorities);
0808: if (identifier != null) {
0809: return identifier;
0810: }
0811: }
0812: }
0813:
0814: // try a quick name lookup
0815: try {
0816: CoordinateReferenceSystem candidate = CRS.decode(crs
0817: .getName().toString());
0818: if (equalsIgnoreMetadata(candidate, crs)) {
0819: String identifier = getSRSFromCRS(candidate,
0820: authorities);
0821: if (identifier != null) {
0822: return identifier;
0823: }
0824: }
0825: } catch (Exception e) {
0826: // the name was not recognized, no problem, let's go on
0827: }
0828:
0829: // here we exhausted the quick paths, bail out if the user does not want a full scan
0830: if (!fullScan) {
0831: return null;
0832: }
0833: // a direct lookup did not work, let's try a full scan of known CRS then
0834: // TODO: implement a smarter method in the actual EPSG authorities, which may
0835: // well be this same loop if they do have no other search capabilities
0836: for (Iterator itAuth = authorities.iterator(); itAuth.hasNext();) {
0837: String authority = (String) itAuth.next();
0838: Set codes = CRS.getSupportedCodes(authority);
0839: for (Iterator itCodes = codes.iterator(); itCodes.hasNext();) {
0840: String code = (String) itCodes.next();
0841: try {
0842: final CoordinateReferenceSystem candidate;
0843: if (code.indexOf(':') == -1) {
0844: candidate = CRS.decode(authority + ':' + code);
0845: } else {
0846: candidate = CRS.decode(code);
0847: }
0848: if (CRS.equalsIgnoreMetadata(candidate, crs)) {
0849: return getSRSFromCRS(candidate, Collections
0850: .singleton(authority));
0851: }
0852: } catch (Exception e) {
0853: // some CRS cannot be decoded properly
0854: }
0855: }
0856: }
0857: return null;
0858: }
0859:
0860: /**
0861: * Scans the identifiers list looking for an EPSG id
0862: *
0863: * @deprecated Used by deprecated methods only.
0864: */
0865: private static String getSRSFromCRS(
0866: final CoordinateReferenceSystem crs, final Set authorities) {
0867: for (Iterator itAuth = authorities.iterator(); itAuth.hasNext();) {
0868: final String authority = (String) itAuth.next();
0869: final String prefix = authority + ":";
0870: for (Iterator itIdent = crs.getIdentifiers().iterator(); itIdent
0871: .hasNext();) {
0872: NamedIdentifier id = (NamedIdentifier) itIdent.next();
0873: String idName = id.toString();
0874: if (idName.startsWith(prefix))
0875: return idName;
0876: }
0877: }
0878: return null;
0879: }
0880:
0881: /////////////////////////////////////////////////
0882: //// ////
0883: //// COORDINATE OPERATIONS ////
0884: //// ////
0885: /////////////////////////////////////////////////
0886:
0887: /**
0888: * Grab a transform between two Coordinate Reference Systems. This convenience method is a
0889: * shorthand for the following:
0890: *
0891: * <blockquote><code>FactoryFinder.{@linkplain ReferencingFactoryFinder#getCoordinateOperationFactory
0892: * getCoordinateOperationFactory}(null).{@linkplain CoordinateOperationFactory#createOperation
0893: * createOperation}(sourceCRS, targetCRS).{@linkplain CoordinateOperation#getMathTransform
0894: * getMathTransform}();</code></blockquote>
0895: *
0896: * Note that some metadata like {@linkplain CoordinateOperation#getPositionalAccuracy
0897: * positional accuracy} are lost by this method. If those metadata are wanted, use the
0898: * {@linkplain CoordinateOperationFactory coordinate operation factory} directly.
0899: * <p>
0900: * Sample use:
0901: * <blockquote><code>
0902: * {@linkplain MathTransform} transform = CRS.findMathTransform(
0903: * CRS.{@linkplain #decode decode}("EPSG:42102"),
0904: * CRS.{@linkplain #decode decode}("EPSG:4326") );
0905: * </blockquote></code>
0906: *
0907: * @param sourceCRS The source CRS.
0908: * @param targetCRS The target CRS.
0909: * @return The math transform from {@code sourceCRS} to {@code targetCRS}.
0910: * @throws FactoryException If no math transform can be created for the specified source and
0911: * target CRS.
0912: */
0913: public static MathTransform findMathTransform(
0914: final CoordinateReferenceSystem sourceCRS,
0915: final CoordinateReferenceSystem targetCRS)
0916: throws FactoryException {
0917: return findMathTransform(sourceCRS, targetCRS, false);
0918: }
0919:
0920: /**
0921: * Grab a transform between two Coordinate Reference Systems. This method is similar to
0922: * <code>{@linkplain #findMathTransform(CoordinateReferenceSystem, CoordinateReferenceSystem)
0923: * findMathTransform}(sourceCRS, targetCRS)</code>, except that it can optionally tolerate
0924: * <cite>lenient datum shift</cite>. If the {@code lenient} argument is {@code true},
0925: * then this method will not throw a "<cite>Bursa-Wolf parameters required</cite>"
0926: * exception during datum shifts if the Bursa-Wolf paramaters are not specified.
0927: * Instead it will assume a no datum shift.
0928: *
0929: * @param sourceCRS The source CRS.
0930: * @param targetCRS The target CRS.
0931: * @param lenient {@code true} if the math transform should be created even when there is
0932: * no information available for a datum shift. The default value is {@code false}.
0933: * @return The math transform from {@code sourceCRS} to {@code targetCRS}.
0934: * @throws FactoryException If no math transform can be created for the specified source and
0935: * target CRS.
0936: *
0937: * @see Hints#LENIENT_DATUM_SHIFT
0938: */
0939: public static MathTransform findMathTransform(
0940: final CoordinateReferenceSystem sourceCRS,
0941: final CoordinateReferenceSystem targetCRS, boolean lenient)
0942: throws FactoryException {
0943: if (equalsIgnoreMetadata(sourceCRS, targetCRS)) {
0944: // Slight optimization in order to avoid the overhead of loading the full referencing engine.
0945: return IdentityTransform.create(sourceCRS
0946: .getCoordinateSystem().getDimension());
0947: }
0948: return getCoordinateOperationFactory(lenient).createOperation(
0949: sourceCRS, targetCRS).getMathTransform();
0950: }
0951:
0952: /**
0953: * Transforms an envelope using the given {@linkplain MathTransform math transform}.
0954: * The transformation is only approximative. Note that the returned envelope may not
0955: * have the same number of dimensions than the original envelope.
0956: * <p>
0957: * Note that this method can not handle the case where the envelope contains the North or
0958: * South pole, or when it cross the ±180� longitude, because {@linkplain MathTransform
0959: * math transforms} do not carry suffisient informations. For a more robust envelope
0960: * transformation, use {@link #transform(CoordinateOperation, Envelope)} instead.
0961: *
0962: * @param transform The transform to use.
0963: * @param envelope Envelope to transform, or {@code null}. This envelope will not be modified.
0964: * @return The transformed envelope, or {@code null} if {@code envelope} was null.
0965: * @throws TransformException if a transform failed.
0966: *
0967: * @since 2.4
0968: */
0969: public static GeneralEnvelope transform(
0970: final MathTransform transform, final Envelope envelope)
0971: throws TransformException {
0972: return transform(transform, envelope, null);
0973: }
0974:
0975: /**
0976: * Implementation of {@link #transform(MathTransform, Envelope)} with the opportunity to
0977: * save the projected center coordinate. If {@code targetPt} is non-null, then this method
0978: * will set it to the center of the source envelope projected to the target CRS.
0979: */
0980: private static GeneralEnvelope transform(
0981: final MathTransform transform, final Envelope envelope,
0982: GeneralDirectPosition targetPt) throws TransformException {
0983: if (envelope == null) {
0984: return null;
0985: }
0986: if (transform.isIdentity()) {
0987: /*
0988: * Slight optimisation: Just copy the envelope. Note that we need to set the CRS
0989: * to null because we don't know what the target CRS was supposed to be. Even if
0990: * an identity transform often imply that the target CRS is the same one than the
0991: * source CRS, it is not always the case. The metadata may be differents, or the
0992: * transform may be a datum shift without Bursa-Wolf parameters, etc.
0993: */
0994: final GeneralEnvelope e = new GeneralEnvelope(envelope);
0995: e.setCoordinateReferenceSystem(null);
0996: return e;
0997: }
0998: /*
0999: * Checks argument validity: envelope and math transform dimensions must be consistent.
1000: */
1001: final int sourceDim = transform.getSourceDimensions();
1002: if (envelope.getDimension() != sourceDim) {
1003: throw new MismatchedDimensionException(Errors.format(
1004: ErrorKeys.MISMATCHED_DIMENSION_$2, new Integer(
1005: sourceDim), new Integer(envelope
1006: .getDimension())));
1007: }
1008: int coordinateNumber = 0;
1009: GeneralEnvelope transformed = null;
1010: if (targetPt == null) {
1011: targetPt = new GeneralDirectPosition(transform
1012: .getTargetDimensions());
1013: }
1014: /*
1015: * Before to run the loops, we must initialize the coordinates to the minimal values.
1016: * This coordinates will be updated in the 'switch' statement inside the 'while' loop.
1017: */
1018: final GeneralDirectPosition sourcePt = new GeneralDirectPosition(
1019: sourceDim);
1020: for (int i = sourceDim; --i >= 0;) {
1021: sourcePt.setOrdinate(i, envelope.getMinimum(i));
1022: }
1023: loop: while (true) {
1024: /*
1025: * Transform a point and add the transformed point to the destination envelope.
1026: * Note that the very last point to be projected must be the envelope center.
1027: */
1028: if (targetPt != transform.transform(sourcePt, targetPt)) {
1029: throw new UnsupportedImplementationException(transform
1030: .getClass());
1031: }
1032: if (transformed != null) {
1033: transformed.add(targetPt);
1034: } else {
1035: transformed = new GeneralEnvelope(targetPt, targetPt);
1036: }
1037: /*
1038: * Get the next point's coordinates. The 'coordinateNumber' variable should
1039: * be seen as a number in base 3 where the number of digits is equals to the
1040: * number of dimensions. For example, a 4-D space would have numbers ranging
1041: * from "0000" to "2222" (numbers in base 3). The digits are then translated
1042: * into minimal, central or maximal ordinates. The outer loop stops when the
1043: * counter roll back to "0000". Note that 'targetPt' must keep the value of
1044: * the last projected point, which must be the envelope center identified by
1045: * "2222" in the 4-D case.
1046: */
1047: int n = ++coordinateNumber;
1048: for (int i = sourceDim; --i >= 0;) {
1049: switch (n % 3) {
1050: case 0:
1051: sourcePt.setOrdinate(i, envelope.getMinimum(i));
1052: n /= 3;
1053: break;
1054: case 1:
1055: sourcePt.setOrdinate(i, envelope.getMaximum(i));
1056: continue loop;
1057: case 2:
1058: sourcePt.setOrdinate(i, envelope.getCenter(i));
1059: continue loop;
1060: default:
1061: throw new AssertionError(n); // Should never happen
1062: }
1063: }
1064: break;
1065: }
1066: return transformed;
1067: }
1068:
1069: /**
1070: * Transforms an envelope using the given {@linkplain CoordinateOperation coordinate pperation}.
1071: * The transformation is only approximative. Note that the returned envelope may not have the
1072: * same number of dimensions than the original envelope.
1073: * <p>
1074: * This method can handle the case where the envelope contains the North or South pole,
1075: * or when it cross the ±180� longitude.
1076: *
1077: * @param operation The operation to use. Source and target dimension must be 2.
1078: * @param envelope Envelope to transform, or {@code null}. This envelope will not be modified.
1079: * @return The transformed envelope, or {@code null} if {@code envelope} was null.
1080: * @throws TransformException if a transform failed.
1081: *
1082: * @since 2.4
1083: */
1084: public static GeneralEnvelope transform(
1085: final CoordinateOperation operation, final Envelope envelope)
1086: throws TransformException {
1087: if (envelope == null) {
1088: return null;
1089: }
1090: final CoordinateReferenceSystem sourceCRS = operation
1091: .getSourceCRS();
1092: if (sourceCRS != null) {
1093: final CoordinateReferenceSystem crs = envelope
1094: .getCoordinateReferenceSystem();
1095: if (crs != null && !equalsIgnoreMetadata(crs, sourceCRS)) {
1096: throw new MismatchedReferenceSystemException(
1097: Errors
1098: .format(ErrorKeys.MISMATCHED_COORDINATE_REFERENCE_SYSTEM));
1099: }
1100: }
1101: MathTransform mt = operation.getMathTransform();
1102: final GeneralDirectPosition centerPt = new GeneralDirectPosition(
1103: mt.getTargetDimensions());
1104: final GeneralEnvelope transformed = transform(mt, envelope,
1105: centerPt);
1106: final CoordinateReferenceSystem targetCRS = operation
1107: .getTargetCRS();
1108: if (targetCRS == null) {
1109: return transformed;
1110: }
1111: transformed.setCoordinateReferenceSystem(targetCRS);
1112: final CoordinateSystem targetCS = targetCRS
1113: .getCoordinateSystem();
1114: if (targetCS == null) {
1115: // It should be an error, but we keep this method tolerant.
1116: return transformed;
1117: }
1118: /*
1119: * Checks for singularity points. For example the south pole is a singularity point in
1120: * geographic CRS because we reach the maximal value allowed on one particular geographic
1121: * axis, namely latitude. This point is not a singularity in the stereographic projection,
1122: * where axis extends toward infinity in all directions (mathematically) and south pole
1123: * has nothing special apart being the origin (0,0).
1124: *
1125: * Algorithm:
1126: *
1127: * 1) Inspect the target axis, looking if there is any bounds. If bounds are found, get
1128: * the coordinates of singularity points and project them from target to source CRS.
1129: *
1130: * Example: if the transformed envelope above is (80�S to 85�S, 10�W to 50�W), and if
1131: * target axis inspection reveal us that the latitude in target CRS is bounded
1132: * at 90�S, then project (90�S,30�W) to source CRS. Note that the longitude is
1133: * set to the the center of the envelope longitude range (more on this later).
1134: *
1135: * 2) If the singularity point computed above is inside the source envelope, add that
1136: * point to the target (transformed) envelope.
1137: *
1138: * Note: We could choose to project the (-180, -90), (180, -90), (-180, 90), (180, 90)
1139: * points, or the (-180, centerY), (180, centerY), (centerX, -90), (centerX, 90) points
1140: * where (centerX, centerY) are transformed from the source envelope center. It make
1141: * no difference for polar projections because the longitude is irrelevant at pole, but
1142: * may make a difference for the 180� longitude bounds. Consider a Mercator projection
1143: * where the transformed envelope is between 20�N and 40�N. If we try to project (-180,90),
1144: * we will get a TransformException because the Mercator projection is not supported at
1145: * pole. If we try to project (-180, 30) instead, we will get a valid point. If this point
1146: * is inside the source envelope because the later overlaps the 180� longitude, then the
1147: * transformed envelope will be expanded to the full (-180 to 180) range. This is quite
1148: * large, but at least it is correct (while the envelope without expansion is not).
1149: */
1150: GeneralEnvelope generalEnvelope = null;
1151: DirectPosition sourcePt = null;
1152: DirectPosition targetPt = null;
1153: final int dimension = targetCS.getDimension();
1154: for (int i = 0; i < dimension; i++) {
1155: final CoordinateSystemAxis axis = targetCS.getAxis(i);
1156: boolean testMax = false; // Tells if we are testing the minimal or maximal value.
1157: do {
1158: final double extremum = testMax ? axis
1159: .getMaximumValue() : axis.getMinimumValue();
1160: if (Double.isInfinite(extremum)
1161: || Double.isNaN(extremum)) {
1162: /*
1163: * The axis is unbounded. It should always be the case when the target CRS is
1164: * a map projection, in which case this loop will finish soon and this method
1165: * will do nothing more (no object instantiated, no MathTransform inversed...)
1166: */
1167: continue;
1168: }
1169: if (targetPt == null) {
1170: try {
1171: mt = mt.inverse();
1172: } catch (NoninvertibleTransformException exception) {
1173: /*
1174: * If the transform is non invertible, this method can't do anything. This
1175: * is not a fatal error because the envelope has already be transformed by
1176: * the caller. We lost the check for singularity points performed by this
1177: * method, but it make no difference in the common case where the source
1178: * envelope didn't contains any of those points.
1179: *
1180: * Note that this exception is normal if target dimension is smaller than
1181: * source dimension, since the math transform can not reconstituate the
1182: * lost dimensions. So we don't log any warning in this case.
1183: */
1184: if (dimension >= mt.getSourceDimensions()) {
1185: unexpectedException("transform", exception);
1186: }
1187: return transformed;
1188: }
1189: targetPt = new GeneralDirectPosition(mt
1190: .getSourceDimensions());
1191: for (int j = 0; j < dimension; j++) {
1192: targetPt
1193: .setOrdinate(j, centerPt.getOrdinate(j));
1194: }
1195: // TODO: avoid the hack below if we provide a contains(DirectPosition)
1196: // method in GeoAPI Envelope interface.
1197: if (envelope instanceof GeneralEnvelope) {
1198: generalEnvelope = (GeneralEnvelope) envelope;
1199: } else {
1200: generalEnvelope = new GeneralEnvelope(envelope);
1201: }
1202: }
1203: targetPt.setOrdinate(i, extremum);
1204: try {
1205: sourcePt = mt.transform(targetPt, sourcePt);
1206: } catch (TransformException e) {
1207: /*
1208: * This exception may be normal. For example we are sure to get this exception
1209: * when trying to project the latitude extremums with a cylindrical Mercator
1210: * projection. Do not log any message and try the other points.
1211: */
1212: continue;
1213: }
1214: if (generalEnvelope.contains(sourcePt)) {
1215: transformed.add(targetPt);
1216: }
1217: } while ((testMax = !testMax) == true);
1218: if (targetPt != null) {
1219: targetPt.setOrdinate(i, centerPt.getOrdinate(i));
1220: }
1221: }
1222: return transformed;
1223: }
1224:
1225: /**
1226: * Transforms a rectangular envelope using the given {@linkplain MathTransform math transform}.
1227: * The transformation is only approximative. Invoking this method is equivalent to invoking the
1228: * following:
1229: * <p>
1230: * <pre>transform(transform, new GeneralEnvelope(envelope)).toRectangle2D()</pre>
1231: * <p>
1232: * Note that this method can not handle the case where the rectangle contains the North or
1233: * South pole, or when it cross the ±180� longitude, because {@linkplain MathTransform
1234: * math transforms} do not carry suffisient informations. For a more robust rectangle
1235: * transformation, use {@link #transform(CoordinateOperation, Rectangle2D, Rectangle2D)}
1236: * instead.
1237: *
1238: * @param transform The transform to use. Source and target dimension must be 2.
1239: * @param envelope The rectangle to transform (may be {@code null}).
1240: * @param destination The destination rectangle (may be {@code envelope}).
1241: * If {@code null}, a new rectangle will be created and returned.
1242: * @return {@code destination}, or a new rectangle if {@code destination} was non-null
1243: * and {@code envelope} was null.
1244: * @throws TransformException if a transform failed.
1245: *
1246: * @since 2.4
1247: */
1248: public static Rectangle2D transform(
1249: final MathTransform2D transform,
1250: final Rectangle2D envelope, Rectangle2D destination)
1251: throws TransformException {
1252: return transform(transform, envelope, destination,
1253: new Point2D.Double());
1254: }
1255:
1256: /**
1257: * Implementation of {@link #transform(MathTransform, Rectangle2D, Rectangle2D)} with the
1258: * opportunity to save the projected center coordinate. This method sets {@code point} to
1259: * the center of the source envelope projected to the target CRS.
1260: */
1261: private static Rectangle2D transform(
1262: final MathTransform2D transform,
1263: final Rectangle2D envelope, Rectangle2D destination,
1264: final Point2D.Double point) throws TransformException {
1265: if (envelope == null) {
1266: return null;
1267: }
1268: double xmin = Double.POSITIVE_INFINITY;
1269: double ymin = Double.POSITIVE_INFINITY;
1270: double xmax = Double.NEGATIVE_INFINITY;
1271: double ymax = Double.NEGATIVE_INFINITY;
1272: for (int i = 0; i <= 8; i++) {
1273: /*
1274: * (0)----(5)----(1)
1275: * | |
1276: * (4) (8) (7)
1277: * | |
1278: * (2)----(6)----(3)
1279: *
1280: * (note: center must be last)
1281: */
1282: point.x = (i & 1) == 0 ? envelope.getMinX() : envelope
1283: .getMaxX();
1284: point.y = (i & 2) == 0 ? envelope.getMinY() : envelope
1285: .getMaxY();
1286: switch (i) {
1287: case 5: // fall through
1288: case 6:
1289: point.x = envelope.getCenterX();
1290: break;
1291: case 8:
1292: point.x = envelope.getCenterX(); // fall through
1293: case 7: // fall through
1294: case 4:
1295: point.y = envelope.getCenterY();
1296: break;
1297: }
1298: if (point != transform.transform(point, point)) {
1299: throw new UnsupportedImplementationException(transform
1300: .getClass());
1301: }
1302: if (point.x < xmin)
1303: xmin = point.x;
1304: if (point.x > xmax)
1305: xmax = point.x;
1306: if (point.y < ymin)
1307: ymin = point.y;
1308: if (point.y > ymax)
1309: ymax = point.y;
1310: }
1311: if (destination != null) {
1312: destination.setRect(xmin, ymin, xmax - xmin, ymax - ymin);
1313: } else {
1314: destination = XRectangle2D.createFromExtremums(xmin, ymin,
1315: xmax, ymax);
1316: }
1317: // Attempt the 'equalsEpsilon' assertion only if source and destination are not same.
1318: assert (destination == envelope)
1319: || XRectangle2D.equalsEpsilon(destination, transform(
1320: transform, new Envelope2D(null, envelope))
1321: .toRectangle2D()) : destination;
1322: return destination;
1323: }
1324:
1325: /**
1326: * Transforms a rectangular envelope using the given {@linkplain CoordinateOperation coordinate
1327: * operation}. The transformation is only approximative. Invoking this method is equivalent to
1328: * invoking the following:
1329: * <p>
1330: * <pre>transform(operation, new GeneralEnvelope(envelope)).toRectangle2D()</pre>
1331: * <p>
1332: * This method can handle the case where the rectangle contains the North or South pole,
1333: * or when it cross the ±180� longitude.
1334: *
1335: * @param operation The operation to use. Source and target dimension must be 2.
1336: * @param envelope The rectangle to transform (may be {@code null}).
1337: * @param destination The destination rectangle (may be {@code envelope}).
1338: * If {@code null}, a new rectangle will be created and returned.
1339: * @return {@code destination}, or a new rectangle if {@code destination} was non-null
1340: * and {@code envelope} was null.
1341: * @throws TransformException if a transform failed.
1342: *
1343: * @since 2.4
1344: */
1345: public static Rectangle2D transform(
1346: final CoordinateOperation operation,
1347: final Rectangle2D envelope, Rectangle2D destination)
1348: throws TransformException {
1349: if (envelope == null) {
1350: return null;
1351: }
1352: final MathTransform transform = operation.getMathTransform();
1353: if (!(transform instanceof MathTransform2D)) {
1354: throw new MismatchedDimensionException(Errors
1355: .format(ErrorKeys.NO_TRANSFORM2D_AVAILABLE));
1356: }
1357: MathTransform2D mt = (MathTransform2D) transform;
1358: final Point2D.Double center = new Point2D.Double();
1359: destination = transform(mt, envelope, destination, center);
1360: final CoordinateReferenceSystem targetCRS = operation
1361: .getTargetCRS();
1362: if (targetCRS == null) {
1363: return destination;
1364: }
1365: final CoordinateSystem targetCS = targetCRS
1366: .getCoordinateSystem();
1367: if (targetCS == null || targetCS.getDimension() != 2) {
1368: // It should be an error, but we keep this method tolerant.
1369: return destination;
1370: }
1371: /*
1372: * Checks for singularity points. See the transform(CoordinateOperation, Envelope)
1373: * method for comments about the algorithm. The code below is the same algorithm
1374: * adapted for the 2D case and the related objects (Point2D, Rectangle2D, etc.).
1375: */
1376: Point2D sourcePt = null;
1377: Point2D targetPt = null;
1378: for (int flag = 0; flag < 4; flag++) { // 2 dimensions and 2 extremums compacted in a flag.
1379: final int i = flag >> 1; // The dimension index being examined.
1380: final CoordinateSystemAxis axis = targetCS.getAxis(i);
1381: final double extremum = (flag & 1) == 0 ? axis
1382: .getMinimumValue() : axis.getMaximumValue();
1383: if (Double.isInfinite(extremum) || Double.isNaN(extremum)) {
1384: continue;
1385: }
1386: if (targetPt == null) {
1387: try {
1388: // TODO: remove the cast when we will be allowed to compile for J2SE 1.5.
1389: mt = (MathTransform2D) mt.inverse();
1390: } catch (NoninvertibleTransformException exception) {
1391: unexpectedException("transform", exception);
1392: return destination;
1393: }
1394: targetPt = new Point2D.Double();
1395: }
1396: switch (i) {
1397: case 0:
1398: targetPt.setLocation(extremum, center.y);
1399: break;
1400: case 1:
1401: targetPt.setLocation(center.x, extremum);
1402: break;
1403: default:
1404: throw new AssertionError(flag);
1405: }
1406: try {
1407: sourcePt = mt.transform(targetPt, sourcePt);
1408: } catch (TransformException e) {
1409: // Do not log; this exception is often expected here.
1410: continue;
1411: }
1412: if (envelope.contains(sourcePt)) {
1413: destination.add(targetPt);
1414: }
1415: }
1416: // Attempt the 'equalsEpsilon' assertion only if source and destination are not same.
1417: assert (destination == envelope)
1418: || XRectangle2D.equalsEpsilon(destination, transform(
1419: operation, new GeneralEnvelope(envelope))
1420: .toRectangle2D()) : destination;
1421: return destination;
1422: }
1423:
1424: /**
1425: * Invoked when an unexpected exception occured. Those exceptions must be non-fatal,
1426: * i.e. the caller <strong>must</strong> have a raisonable fallback (otherwise it
1427: * should propagate the exception).
1428: */
1429: static void unexpectedException(final String methodName,
1430: final Exception exception) {
1431: Logging.unexpectedException(LOGGER, CRS.class, methodName,
1432: exception);
1433: }
1434:
1435: /**
1436: * Prints to the {@linkplain System#out standard output stream} some information about
1437: * {@linkplain CoordinateReferenceSystem coordinate reference systems} specified by their
1438: * authority codes. This method can be invoked from the command line in order to test the
1439: * {@linkplain #getAuthorityFactory authority factory} content for some specific CRS.
1440: * <p>
1441: * By default, this method prints all enumerated objects as <cite>Well Known Text</cite>.
1442: * However this method can prints different kind of information if an option such as
1443: * {@code -factories}, {@code -codes} or {@code -bursawolfs} is provided.
1444: * <p>
1445: * <b>Usage:</b> {@code java org.geotools.referencing.CRS [options] [codes]}<br>
1446: * <b>Options:</b>
1447: *
1448: * <blockquote>
1449: * <p><b>{@code -authority}=<var>name</var></b><br>
1450: * Uses the specified authority factory, for example {@code "EPSG"}. The authority
1451: * name can be any of the authorities listed by the {@code -factories} option. If
1452: * this option is not specified, then the default is all factories.</p>
1453: *
1454: * <p><b>{@code -bursawolfs} <var>codes</var></b><br>
1455: * Lists the Bursa-Wolf parameters for the specified CRS ou datum objects. For some
1456: * transformations, there is more than one set of Bursa-Wolf parameters available.
1457: * The standard <cite>Well Known Text</cite> format prints only what look like the
1458: * "main" one. This option display all Bursa-Wolf parameters in a table for a given
1459: * object.</p>
1460: *
1461: * <p><b>{@code -codes}</b><br>
1462: * Lists all available authority codes. Use the {@code -authority} option if the
1463: * list should be restricted to a single authority.</p>
1464: *
1465: * <p><b>{@code -colors}</b><br>
1466: * Enable syntax coloring on <A HREF="http://en.wikipedia.org/wiki/ANSI_escape_code">ANSI
1467: * X3.64</A> compatible (aka ECMA-48 and ISO/IEC 6429) terminal. This option tries to
1468: * highlight most of the elements relevant to the {@link #equalsIgnoreMetadata
1469: * equalsIgnoreMetadata} method, with the addition of Bursa-Wolf parameters.</p>
1470: *
1471: * <p><b>{@code -encoding}=<var>charset</var></b><br>
1472: * Sets the console encoding for this application output. This value has no impact
1473: * on data, but may improve the output quality. This is not needed on Linux terminal
1474: * using UTF-8 encoding (tip: the <cite>terminus font</cite> gives good results).
1475: * Windows users may need to set this encoding to the value returned by the
1476: * {@code chcp} command line. This parameter need to be specified only once.</p>
1477: *
1478: * <p><b>{@code -dependencies}</b><br>
1479: * Lists authority factory dependencies as a tree.</p>
1480: *
1481: * <p><b>{@code -factories}</b><br>
1482: * Lists all availables CRS authority factories.</p>
1483: *
1484: * <p><b>{@code -forcexy}</b><br>
1485: * Force "longitude first" axis order.</p>
1486: *
1487: * <p><b>{@code -help}</b><br>
1488: * Prints the list of options.</p>
1489: *
1490: * <p><b>{@code -locale}=<var>name</var></b><br>
1491: * Formats texts in the specified {@linkplain java.util.Locale locale}.</p>
1492: *
1493: * <p><b>{@code -operations} <var>sourceCRS</var> <var>targetCRS</var></b><br>
1494: * Prints all available coordinate operations between a pair of CRS. This option
1495: * prints only the operations explicitly defined in a database like EPSG. There
1496: * is sometime many such operations, and sometime none (in which case this option
1497: * prints nothing - it doesn't try to find an operation by itself).</p>
1498: *
1499: * <p><b>{@code -transform} <var>sourceCRS</var> <var>targetCRS</var></b><br>
1500: * Prints the preferred math transform between a pair of CRS. At the difference of
1501: * the {@code "-operations"} option, this option pick up only one operation (usually
1502: * the most accurate one), inferring it if none were explicitly specified in the
1503: * database.</p>
1504: * </blockquote>
1505: *
1506: * <strong>Examples</strong> (assuming that {@code "CRS"} is a shortcut for
1507: * {@code "java org.geotools.referencing.CRS"}):
1508: *
1509: * <blockquote>
1510: * <p><b>{@code CRS EPSG:4181 EPSG:4326 CRS:84 AUTO:42001,30,0}</b><br>
1511: * Prints the "Luxembourg 1930" CRS, the "WGS 84" CRS (from EPSG database),
1512: * the ""WGS84" CRS (from the <cite>Web Map Service</cite> specification) and a UTM
1513: * projection in WKT format.</p>
1514: *
1515: * <p><b>{@code CRS -authority=EPSG 4181 4326}</b><br>
1516: * Prints the "Luxembourg 1930" and "WGS 84" CRS, looking only in the EPSG
1517: * database (so there is no need to prefix the codes with {@code "EPSG"}).</p>
1518: *
1519: * <p><b>{@code CRS -colors EPSG:7411}</b><br>
1520: * Prints the "NTF (Paris) / Lambert zone II + NGF Lallemand" CRS with syntax
1521: * coloring enabled.</p>
1522: *
1523: * <p><b>{@code CRS -bursawolfs EPSG:4230}</b><br>
1524: * Prints three set of Bursa-Wolf parameters for a CRS based on
1525: * "European Datum 1950".</p>
1526: *
1527: * <p><b>{@code CRS -authority=EPSG -operations 4230 4326}</b><br>
1528: * Prints all operations declared in the EPSG database from "ED50" to "WGS 84"
1529: * geographic CRS. Note that for this particular pair of CRS, there is close
1530: * to 40 operations declared in the EPSG database. This method prints only the
1531: * ones that Geotools can handle.</p>
1532: *
1533: * <p><b>{@code CRS -transform EPSG:4230 EPSG:4326}</b><br>
1534: * Prints the math transform that Geotools would use by default for coordinate
1535: * transformation from "ED50" to "WGS 84".</p>
1536: * </blockquote>
1537: *
1538: * @param args Options and list of object codes to display.
1539: *
1540: * @since 2.4
1541: */
1542: public static void main(final String[] args) {
1543: Command.execute(args);
1544: }
1545: }
|