| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| CRS |
|
| 4.764705882352941;4,765 | ||||
| CRS$1 |
|
| 4.764705882352941;4,765 |
| 1 | /* | |
| 2 | * Geotoolkit.org - An Open Source Java GIS Toolkit | |
| 3 | * http://www.geotoolkit.org | |
| 4 | * | |
| 5 | * (C) 2005-2012, Open Source Geospatial Foundation (OSGeo) | |
| 6 | * (C) 2009-2012, Geomatys | |
| 7 | * | |
| 8 | * This library is free software; you can redistribute it and/or | |
| 9 | * modify it under the terms of the GNU Lesser General Public | |
| 10 | * License as published by the Free Software Foundation; | |
| 11 | * version 2.1 of the License. | |
| 12 | * | |
| 13 | * This library is distributed in the hope that it will be useful, | |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 16 | * Lesser General Public License for more details. | |
| 17 | */ | |
| 18 | package org.geotoolkit.referencing; | |
| 19 | ||
| 20 | import java.util.Set; | |
| 21 | import java.util.Map; | |
| 22 | import java.util.List; | |
| 23 | import java.util.HashSet; | |
| 24 | import java.util.Iterator; | |
| 25 | import java.util.StringTokenizer; | |
| 26 | import java.util.NoSuchElementException; | |
| 27 | import java.awt.geom.Point2D; | |
| 28 | import java.awt.geom.Rectangle2D; | |
| 29 | import java.awt.geom.AffineTransform; | |
| 30 | import java.awt.RenderingHints; | |
| 31 | import javax.swing.event.ChangeEvent; | |
| 32 | import javax.swing.event.ChangeListener; | |
| 33 | ||
| 34 | import org.opengis.geometry.*; | |
| 35 | import org.opengis.referencing.*; | |
| 36 | import org.opengis.referencing.cs.*; | |
| 37 | import org.opengis.referencing.crs.*; | |
| 38 | import org.opengis.referencing.datum.*; | |
| 39 | import org.opengis.referencing.operation.*; | |
| 40 | import org.opengis.metadata.extent.*; | |
| 41 | import org.opengis.util.FactoryException; | |
| 42 | ||
| 43 | import org.geotoolkit.lang.Static; | |
| 44 | import org.geotoolkit.util.Version; | |
| 45 | import org.geotoolkit.util.Utilities; | |
| 46 | import org.geotoolkit.util.ComparisonMode; | |
| 47 | import org.geotoolkit.util.logging.Logging; | |
| 48 | import org.geotoolkit.factory.Hints; | |
| 49 | import org.geotoolkit.factory.Factory; | |
| 50 | import org.geotoolkit.factory.Factories; | |
| 51 | import org.geotoolkit.factory.FactoryFinder; | |
| 52 | import org.geotoolkit.factory.AuthorityFactoryFinder; | |
| 53 | import org.geotoolkit.factory.FactoryNotFoundException; | |
| 54 | import org.geotoolkit.factory.FactoryRegistryException; | |
| 55 | import org.geotoolkit.geometry.Envelopes; | |
| 56 | import org.geotoolkit.geometry.GeneralEnvelope; | |
| 57 | import org.geotoolkit.metadata.iso.extent.DefaultGeographicBoundingBox; | |
| 58 | import org.geotoolkit.referencing.crs.DefaultCompoundCRS; | |
| 59 | import org.geotoolkit.referencing.crs.DefaultVerticalCRS; | |
| 60 | import org.geotoolkit.referencing.crs.DefaultGeographicCRS; | |
| 61 | import org.geotoolkit.referencing.cs.DefaultEllipsoidalCS; | |
| 62 | import org.geotoolkit.referencing.cs.DefaultCoordinateSystemAxis; | |
| 63 | import org.geotoolkit.referencing.operation.MathTransforms; | |
| 64 | import org.geotoolkit.internal.referencing.CRSUtilities; | |
| 65 | import org.geotoolkit.resources.Errors; | |
| 66 | ||
| 67 | import static org.geotoolkit.util.ArgumentChecks.ensureNonNull; | |
| 68 | ||
| 69 | ||
| 70 | /** | |
| 71 | * Utility class for making use of the {@linkplain CoordinateReferenceSystem Coordinate Reference | |
| 72 | * System} and associated {@linkplain org.opengis.util.Factory} implementations. This utility class | |
| 73 | * is made up of static functions working with arbitrary implementations of GeoAPI interfaces. | |
| 74 | * <p> | |
| 75 | * The methods defined in this class can be grouped in three categories: | |
| 76 | * <p> | |
| 77 | * <ul> | |
| 78 | * <li>Methods working with factories, like {@link #decode(String)}.</li> | |
| 79 | * <li>Methods providing informations, like {@link #isHorizontalCRS(CoordinateReferenceSystem)}.</li> | |
| 80 | * <li>Methods performing coordinate transformations, like {@link #transform(CoordinateOperation,Envelope)} | |
| 81 | * Note that many of those methods are also defined in the {@link Envelopes} class.</li> | |
| 82 | * </ul> | |
| 83 | * | |
| 84 | * @author Martin Desruisseaux (IRD, Geomatys) | |
| 85 | * @author Jody Garnett (Refractions) | |
| 86 | * @author Andrea Aime (TOPP) | |
| 87 | * @version 3.19 | |
| 88 | * | |
| 89 | * @see IdentifiedObjects | |
| 90 | * @see Envelopes | |
| 91 | * | |
| 92 | * @since 2.1 | |
| 93 | * @module | |
| 94 | */ | |
| 95 | 204 | public final class CRS extends Static { |
| 96 | /** | |
| 97 | * The CRS factory to use for parsing WKT. Will be fetched when first needed | |
| 98 | * are stored for avoiding indirect synchronization lock in {@link #parseWKT}. | |
| 99 | */ | |
| 100 | private static volatile CRSFactory crsFactory; | |
| 101 | ||
| 102 | /** | |
| 103 | * A factory for CRS creation as specified by the authority, which may have | |
| 104 | * (<var>latitude</var>, <var>longitude</var>) axis order. Will be created | |
| 105 | * only when first needed. | |
| 106 | */ | |
| 107 | private static volatile CRSAuthorityFactory standardFactory; | |
| 108 | ||
| 109 | /** | |
| 110 | * A factory for CRS creation with (<var>longitude</var>, <var>latitude</var>) axis order. | |
| 111 | * Will be created only when first needed. | |
| 112 | */ | |
| 113 | private static volatile CRSAuthorityFactory xyFactory; | |
| 114 | ||
| 115 | /** | |
| 116 | * A factory for default (non-lenient) operations. | |
| 117 | */ | |
| 118 | private static volatile CoordinateOperationFactory strictFactory; | |
| 119 | ||
| 120 | /** | |
| 121 | * A factory for default lenient operations. | |
| 122 | */ | |
| 123 | private static volatile CoordinateOperationFactory lenientFactory; | |
| 124 | ||
| 125 | /** | |
| 126 | * The default value for {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER}, | |
| 127 | * or {@code null} if not yet determined. | |
| 128 | */ | |
| 129 | private static volatile Boolean defaultOrder; | |
| 130 | ||
| 131 | /** | |
| 132 | * The default value for {@link Hints#LENIENT_DATUM_SHIFT}, | |
| 133 | * or {@code null} if not yet determined. | |
| 134 | */ | |
| 135 | private static volatile Boolean defaultLenient; | |
| 136 | ||
| 137 | /** | |
| 138 | * Registers a listener automatically invoked when the system-wide configuration changed. | |
| 139 | */ | |
| 140 | static { | |
| 141 | 1 | Factories.addChangeListener(new ChangeListener() { |
| 142 | @Override public void stateChanged(ChangeEvent e) { | |
| 143 | 29 | synchronized (CRS.class) { |
| 144 | 29 | crsFactory = null; |
| 145 | 29 | standardFactory = null; |
| 146 | 29 | xyFactory = null; |
| 147 | 29 | strictFactory = null; |
| 148 | 29 | lenientFactory = null; |
| 149 | 29 | defaultOrder = null; |
| 150 | 29 | defaultLenient = null; |
| 151 | 29 | } |
| 152 | 29 | } |
| 153 | }); | |
| 154 | 1 | } |
| 155 | ||
| 156 | /** | |
| 157 | * Do not allow instantiation of this class. | |
| 158 | */ | |
| 159 | 0 | private CRS() { |
| 160 | 0 | } |
| 161 | ||
| 162 | ||
| 163 | ////////////////////////////////////////////////////////////// | |
| 164 | //// //// | |
| 165 | //// FACTORIES, CRS CREATION AND INSPECTION //// | |
| 166 | //// //// | |
| 167 | ////////////////////////////////////////////////////////////// | |
| 168 | ||
| 169 | /** | |
| 170 | * Returns the CRS factory. This is used mostly for WKT parsing. | |
| 171 | */ | |
| 172 | private static CRSFactory getCRSFactory() { | |
| 173 | 32 | CRSFactory factory = crsFactory; |
| 174 | 32 | if (factory == null) { |
| 175 | 5 | synchronized (CRS.class) { |
| 176 | // Double-checked locking - was a deprecated practice before Java 5. | |
| 177 | // Is okay since Java 5 provided that the variable is volatile. | |
| 178 | 5 | factory = crsFactory; |
| 179 | 5 | if (factory == null) { |
| 180 | 5 | crsFactory = factory = FactoryFinder.getCRSFactory(null); |
| 181 | } | |
| 182 | 5 | } |
| 183 | } | |
| 184 | 32 | return factory; |
| 185 | } | |
| 186 | ||
| 187 | /** | |
| 188 | * Returns the CRS authority factory used by the {@link #decode(String,boolean) decode} methods. | |
| 189 | * This factory {@linkplain org.geotoolkit.referencing.factory.CachingAuthorityFactory uses a cache}, | |
| 190 | * scans over {@linkplain org.geotoolkit.referencing.factory.AllAuthoritiesFactory all factories} and | |
| 191 | * uses additional factories as {@linkplain org.geotoolkit.referencing.factory.FallbackAuthorityFactory | |
| 192 | * fallbacks} if there is more than one {@linkplain AuthorityFactoryFinder#getCRSAuthorityFactories | |
| 193 | * registered factory} for the same authority. | |
| 194 | * <p> | |
| 195 | * This factory can be used as a kind of <cite>system-wide</cite> factory for all authorities. | |
| 196 | * However for more determinist behavior, consider using a more specific factory (as returned | |
| 197 | * by {@link AuthorityFactoryFinder#getCRSAuthorityFactory}) when the authority is known. | |
| 198 | * | |
| 199 | * @param longitudeFirst {@code true} if axis order should be forced to | |
| 200 | * (<var>longitude</var>, <var>latitude</var>), {@code false} if no order should be | |
| 201 | * forced (i.e. the standard specified by the authority is respected), or {@code null} | |
| 202 | * for the {@linkplain Hints#getSystemDefault system default}. | |
| 203 | * @return The CRS authority factory. | |
| 204 | * @throws FactoryRegistryException if the factory can't be created. | |
| 205 | * | |
| 206 | * @see Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER | |
| 207 | * | |
| 208 | * @category factory | |
| 209 | * @since 2.3 | |
| 210 | */ | |
| 211 | public static CRSAuthorityFactory getAuthorityFactory(Boolean longitudeFirst) | |
| 212 | throws FactoryRegistryException | |
| 213 | { | |
| 214 | // No need to synchronize; this is not a big deal if 'defaultOrder' is computed twice. | |
| 215 | 127 | if (longitudeFirst == null) { |
| 216 | 75 | longitudeFirst = defaultOrder; |
| 217 | 75 | if (longitudeFirst == null) { |
| 218 | 14 | longitudeFirst = Boolean.TRUE.equals(Hints.getSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER)); |
| 219 | 14 | defaultOrder = longitudeFirst; |
| 220 | } | |
| 221 | } | |
| 222 | 127 | CRSAuthorityFactory factory = (longitudeFirst) ? xyFactory : standardFactory; |
| 223 | 127 | if (factory == null) synchronized (CRS.class) { |
| 224 | // Double-checked locking - was a deprecated practice before Java 5. | |
| 225 | // Is okay since Java 5 provided that the variables are volatile. | |
| 226 | 20 | factory = (longitudeFirst) ? xyFactory : standardFactory; |
| 227 | 20 | if (factory == null) try { |
| 228 | 20 | factory = DefaultAuthorityFactory.create(longitudeFirst); |
| 229 | 20 | if (longitudeFirst) { |
| 230 | 8 | xyFactory = factory; |
| 231 | } else { | |
| 232 | 12 | standardFactory = factory; |
| 233 | } | |
| 234 | 0 | } catch (NoSuchElementException exception) { |
| 235 | // No factory registered in FactoryFinder. | |
| 236 | 0 | throw new FactoryNotFoundException(null, exception); |
| 237 | 20 | } |
| 238 | 20 | } |
| 239 | 127 | return factory; |
| 240 | } | |
| 241 | ||
| 242 | /** | |
| 243 | * Returns the coordinate operation factory used by | |
| 244 | * {@link #findMathTransform(CoordinateReferenceSystem, CoordinateReferenceSystem) | |
| 245 | * findMathTransform} convenience methods. | |
| 246 | * | |
| 247 | * @param lenient {@code true} if the coordinate operations should be created | |
| 248 | * even when there is no information available for a datum shift. | |
| 249 | * @return The coordinate operation factory used for finding math transform in this class. | |
| 250 | * | |
| 251 | * @see Hints#LENIENT_DATUM_SHIFT | |
| 252 | * | |
| 253 | * @category factory | |
| 254 | * @since 2.4 | |
| 255 | */ | |
| 256 | public static CoordinateOperationFactory getCoordinateOperationFactory(final boolean lenient) { | |
| 257 | 19221 | CoordinateOperationFactory factory = (lenient) ? lenientFactory : strictFactory; |
| 258 | 19221 | if (factory == null) synchronized (CRS.class) { |
| 259 | // Double-checked locking - was a deprecated practice before Java 5. | |
| 260 | // Is okay since Java 5 provided that the variables are volatile. | |
| 261 | 5 | factory = (lenient) ? lenientFactory : strictFactory; |
| 262 | 5 | if (factory == null) { |
| 263 | 5 | final Hints hints = new Hints(); // Get the system-width default hints. |
| 264 | 5 | if (lenient) { |
| 265 | 2 | hints.put(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE); |
| 266 | } | |
| 267 | 5 | factory = FactoryFinder.getCoordinateOperationFactory(hints); |
| 268 | 5 | if (lenient) { |
| 269 | 2 | lenientFactory = factory; |
| 270 | } else { | |
| 271 | 3 | strictFactory = factory; |
| 272 | } | |
| 273 | } | |
| 274 | 5 | } |
| 275 | 19221 | return factory; |
| 276 | } | |
| 277 | ||
| 278 | /** | |
| 279 | * Returns the version number of the specified authority database, or {@code null} if | |
| 280 | * not available. | |
| 281 | * | |
| 282 | * @param authority The authority name (typically {@code "EPSG"}). | |
| 283 | * @return The version number of the authority database, or {@code null} if unknown. | |
| 284 | * @throws FactoryRegistryException if no {@link CRSAuthorityFactory} implementation | |
| 285 | * was found for the specified authority. | |
| 286 | * | |
| 287 | * @see Hints#VERSION | |
| 288 | * | |
| 289 | * @category factory | |
| 290 | * @since 2.4 | |
| 291 | */ | |
| 292 | public static Version getVersion(final String authority) throws FactoryRegistryException { | |
| 293 | 4 | ensureNonNull("authority", authority); |
| 294 | 4 | Object candidate = AuthorityFactoryFinder.getCRSAuthorityFactory(authority, null); |
| 295 | 4 | final Set<Factory> guard = new HashSet<Factory>(); |
| 296 | 4 | while (candidate instanceof Factory) { |
| 297 | 4 | final Factory factory = (Factory) candidate; |
| 298 | 4 | if (!guard.add(factory)) { |
| 299 | 0 | break; // Safety against never-ending recursivity. |
| 300 | } | |
| 301 | 4 | final Map<RenderingHints.Key,?> hints = factory.getImplementationHints(); |
| 302 | 4 | final Object version = hints.get(Hints.VERSION); |
| 303 | 4 | if (version instanceof Version) { |
| 304 | 4 | return (Version) version; |
| 305 | } | |
| 306 | 0 | candidate = hints.get(Hints.CRS_AUTHORITY_FACTORY); |
| 307 | 0 | } |
| 308 | 0 | return null; |
| 309 | } | |
| 310 | ||
| 311 | /** | |
| 312 | * Gets the list of the codes that are supported by the given authority. For example | |
| 313 | * {@code getSupportedCodes("EPSG")} may returns {@code "EPSG:2000"}, {@code "EPSG:2001"}, | |
| 314 | * {@code "EPSG:2002"}, <i>etc</i>. It may also returns {@code "2000"}, {@code "2001"}, | |
| 315 | * {@code "2002"}, <i>etc.</i> without the {@code "EPSG:"} prefix. Whatever the authority | |
| 316 | * name is prefixed or not is factory implementation dependent. | |
| 317 | * <p> | |
| 318 | * If there is more than one factory for the given authority, then this method merges the | |
| 319 | * code set of all of them. If a factory fails to provide a set of supported code, then | |
| 320 | * this particular factory is ignored. Please be aware of the following potential issues: | |
| 321 | * <p> | |
| 322 | * <ul> | |
| 323 | * <li>If there is more than one EPSG databases (for example an Access and a PostgreSQL ones), | |
| 324 | * then this method will connect to all of them even if their content are identical.</li> | |
| 325 | * | |
| 326 | * <li>If two factories format their codes differently (e.g. {@code "4326"} and | |
| 327 | * {@code "EPSG:4326"}), then the returned set will contain a lot of synonymous | |
| 328 | * codes.</li> | |
| 329 | * | |
| 330 | * <li>For any code <var>c</var> in the returned set, there is no warranty that | |
| 331 | * <code>{@linkplain #decode decode}(c)</code> will use the same authority | |
| 332 | * factory than the one that formatted <var>c</var>.</li> | |
| 333 | * | |
| 334 | * <li>This method doesn't report connection problems since it doesn't throw any exception. | |
| 335 | * {@link FactoryException}s are logged as warnings and otherwise ignored.</li> | |
| 336 | * </ul> | |
| 337 | * <p> | |
| 338 | * If a more determinist behavior is wanted, consider the code below instead. | |
| 339 | * The following code exploit only one factory, the "preferred" one. | |
| 340 | * | |
| 341 | * {@preformat java | |
| 342 | * factory = AuthorityFactoryFinder.getCRSAuthorityFactory(authority, null); | |
| 343 | * Set<String> codes = factory.getAuthorityCodes(CoordinateReferenceSystem.class); | |
| 344 | * String code = ... // Choose a code here. | |
| 345 | * CoordinateReferenceSystem crs = factory.createCoordinateReferenceSystem(code); | |
| 346 | * } | |
| 347 | * | |
| 348 | * @param authority The authority name (for example {@code "EPSG"}). | |
| 349 | * @return The set of supported codes. May be empty, but never null. | |
| 350 | * | |
| 351 | * @see AuthorityFactory#getAuthorityCodes(Class) | |
| 352 | * @see <a href="http://www.geotoolkit.org/modules/referencing/supported-codes.html">List of authority codes</a> | |
| 353 | * | |
| 354 | * @category factory | |
| 355 | */ | |
| 356 | public static Set<String> getSupportedCodes(final String authority) { | |
| 357 | 0 | ensureNonNull("authority", authority); |
| 358 | 0 | return DefaultAuthorityFactory.getSupportedCodes(authority); |
| 359 | } | |
| 360 | ||
| 361 | /** | |
| 362 | * Returns the set of the authority identifiers supported by registered authority factories. | |
| 363 | * This method search only for {@linkplain CRSAuthorityFactory CRS authority factories}. | |
| 364 | * | |
| 365 | * @param returnAliases If {@code true}, the set will contain all identifiers for each | |
| 366 | * authority. If {@code false}, only the first one | |
| 367 | * @return The set of supported authorities. May be empty, but never null. | |
| 368 | * | |
| 369 | * @category factory | |
| 370 | * @since 2.3.1 | |
| 371 | */ | |
| 372 | public static Set<String> getSupportedAuthorities(final boolean returnAliases) { | |
| 373 | 2 | return DefaultAuthorityFactory.getSupportedAuthorities(returnAliases); |
| 374 | } | |
| 375 | ||
| 376 | /** | |
| 377 | * Returns a Coordinate Reference System for the specified code. | |
| 378 | * Note that the code needs to mention the authority. Examples: | |
| 379 | * <p> | |
| 380 | * <ul> | |
| 381 | * <li>{@code EPSG:4326}</li> | |
| 382 | * <li>{@code AUTO:42001,9001,0,30}</li> | |
| 383 | * </ul> | |
| 384 | * <p> | |
| 385 | * If there is more than one factory implementation for the same authority, then all additional | |
| 386 | * factories are {@linkplain org.geotoolkit.referencing.factory.FallbackAuthorityFactory fallbacks} | |
| 387 | * to be used only when the first acceptable factory failed to create the requested CRS object. | |
| 388 | * | |
| 389 | * {@section Common codes} | |
| 390 | * A few commonly used codes are: | |
| 391 | * <p> | |
| 392 | * <ul> | |
| 393 | * <li>Geographic CRS: | |
| 394 | * <ul> | |
| 395 | * <li>WGS 84 (2D only): EPSG:4326</li> | |
| 396 | * <li>WGS 84 with ellipsoidal height: EPSG:4979</li> | |
| 397 | * </ul></li> | |
| 398 | * <li>Simple projected CRS: | |
| 399 | * <ul> | |
| 400 | * <li>Mercator: 3395</li> | |
| 401 | * </ul></li> | |
| 402 | * <li>Universal Transverse Mercator (UTM) projections: | |
| 403 | * <ul> | |
| 404 | * <li>WGS 84 (northern hemisphere): EPSG:32600 + <var>zone</var></li> | |
| 405 | * <li>WGS 84 (southern hemisphere): EPSG:32700 + <var>zone</var></li> | |
| 406 | * <li>WGS 72 (northern hemisphere): EPSG:32200 + <var>zone</var></li> | |
| 407 | * <li>WGS 72 (southern hemisphere): EPSG:32300 + <var>zone</var></li> | |
| 408 | * <li>NAD 83 (northern hemisphere): EPSG:26900 + <var>zone</var> (zone 1 to 23 only)</li> | |
| 409 | * <li>NAD 27 (northern hemisphere): EPSG:26700 + <var>zone</var> (zone 1 to 22 only)</li> | |
| 410 | * </ul></li> | |
| 411 | * </ul> | |
| 412 | * | |
| 413 | * {@section Caching} | |
| 414 | * CRS objects created by previous calls to this method are | |
| 415 | * {@linkplain org.geotoolkit.referencing.factory.CachingAuthorityFactory cached} | |
| 416 | * using {@linkplain java.lang.ref.WeakReference weak references}. Subsequent calls to this | |
| 417 | * method with the same authority code should be fast, unless the CRS object has been garbage | |
| 418 | * collected. | |
| 419 | * | |
| 420 | * @param code The Coordinate Reference System authority code. | |
| 421 | * @return The Coordinate Reference System for the provided code. | |
| 422 | * @throws NoSuchAuthorityCodeException If the code could not be understood. | |
| 423 | * @throws FactoryException if the CRS creation failed for an other reason. | |
| 424 | * | |
| 425 | * @see #getSupportedCodes(String) | |
| 426 | * @see org.geotoolkit.measure.Units#valueOfEPSG(int) | |
| 427 | * @see <a href="http://www.geotoolkit.org/modules/referencing/supported-codes.html">List of authority codes</a> | |
| 428 | * | |
| 429 | * @category factory | |
| 430 | */ | |
| 431 | public static CoordinateReferenceSystem decode(final String code) | |
| 432 | throws NoSuchAuthorityCodeException, FactoryException | |
| 433 | { | |
| 434 | 75 | ensureNonNull("code", code); |
| 435 | 75 | return getAuthorityFactory(null).createCoordinateReferenceSystem(code); |
| 436 | } | |
| 437 | ||
| 438 | /** | |
| 439 | * Returns a Coordinate Reference System for the specified code, maybe forcing the axis order | |
| 440 | * to (<var>longitude</var>, <var>latitude</var>). The {@code code} argument value is parsed | |
| 441 | * as in <code>{@linkplain #decode(String) decode}(code)</code>. The {@code longitudeFirst} | |
| 442 | * argument is the value to be given to the {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER | |
| 443 | * FORCE_LONGITUDE_FIRST_AXIS_ORDER} hint. | |
| 444 | * <p> | |
| 445 | * <b>Example:</b> by default, {@code CRS.decode("EPSG:4326")} returns a Geographic CRS with | |
| 446 | * (<var>latitude</var>, <var>longitude</var>) axis order, while {@code CRS.decode("EPSG:4326", true)} | |
| 447 | * returns the same CRS except for axis order, which is (<var>longitude</var>, <var>latitude</var>). | |
| 448 | * | |
| 449 | * @param code The Coordinate Reference System authority code. | |
| 450 | * @param longitudeFirst {@code true} if axis order should be forced to | |
| 451 | * (<var>longitude</var>, <var>latitude</var>), {@code false} if no order should | |
| 452 | * be forced (i.e. the standard specified by the authority is respected). | |
| 453 | * @return The Coordinate Reference System for the provided code. | |
| 454 | * @throws NoSuchAuthorityCodeException If the code could not be understood. | |
| 455 | * @throws FactoryException if the CRS creation failed for an other reason. | |
| 456 | * | |
| 457 | * @see Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER | |
| 458 | * @see org.geotoolkit.referencing.factory.epsg.LongitudeFirstEpsgFactory | |
| 459 | * @see <a href="http://www.geotoolkit.org/modules/referencing/supported-codes.html">List of authority codes</a> | |
| 460 | * | |
| 461 | * @category factory | |
| 462 | * @since 2.3 | |
| 463 | */ | |
| 464 | public static CoordinateReferenceSystem decode(String code, final boolean longitudeFirst) | |
| 465 | throws NoSuchAuthorityCodeException, FactoryException | |
| 466 | { | |
| 467 | 17 | ensureNonNull("code", code); |
| 468 | 17 | return getAuthorityFactory(longitudeFirst).createCoordinateReferenceSystem(code); |
| 469 | } | |
| 470 | ||
| 471 | /** | |
| 472 | * Parses a | |
| 473 | * <A HREF="http://www.geoapi.org/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well | |
| 474 | * Known Text</cite></A> (WKT) into a CRS object. This convenience method is a | |
| 475 | * shorthand for the following: | |
| 476 | * | |
| 477 | * {@preformat java | |
| 478 | * FactoryFinder.getCRSFactory(null).createFromWKT(wkt); | |
| 479 | * } | |
| 480 | * | |
| 481 | * @param wkt The WKT string to parse. | |
| 482 | * @return The parsed coordinate reference system. | |
| 483 | * @throws FactoryException if the given WKT can't be parsed. | |
| 484 | * | |
| 485 | * @see Envelopes#parseWKT(String) | |
| 486 | * @see CoordinateReferenceSystem#toWKT() | |
| 487 | * | |
| 488 | * @category factory | |
| 489 | */ | |
| 490 | public static CoordinateReferenceSystem parseWKT(final String wkt) throws FactoryException { | |
| 491 | 31 | ensureNonNull("wkt", wkt); |
| 492 | 31 | return getCRSFactory().createFromWKT(wkt); |
| 493 | } | |
| 494 | ||
| 495 | /** | |
| 496 | * Returns the domain of validity for the specified coordinate reference system, | |
| 497 | * or {@code null} if unknown. The returned envelope is expressed in terms of the | |
| 498 | * specified CRS. | |
| 499 | * <p> | |
| 500 | * This method looks in two places: | |
| 501 | * <p> | |
| 502 | * <ul> | |
| 503 | * <li>First, it checks the {@linkplain CoordinateReferenceSystem#getDomainOfValidity domain | |
| 504 | * of validity} associated with the given CRS. Only {@linkplain GeographicExtent | |
| 505 | * geographic extents} of kind {@linkplain BoundingPolygon bounding polygon} are | |
| 506 | * taken in account.</li> | |
| 507 | * <li>If the above step does not found found any bounding polygon, then the | |
| 508 | * {@linkplain #getGeographicBoundingBox geographic bounding boxes} are | |
| 509 | * used as a fallback.</li> | |
| 510 | * </ul> | |
| 511 | * <p> | |
| 512 | * Note that this method is also accessible from the {@link Envelopes} class. | |
| 513 | * | |
| 514 | * @param crs The coordinate reference system, or {@code null}. | |
| 515 | * @return The envelope in terms of the specified CRS, or {@code null} if none. | |
| 516 | * | |
| 517 | * @see #getGeographicBoundingBox(CoordinateReferenceSystem) | |
| 518 | * @see Envelopes#getDomainOfValidity(CoordinateReferenceSystem) | |
| 519 | * @see GeneralEnvelope#reduceToDomain(boolean) | |
| 520 | * | |
| 521 | * @category information | |
| 522 | * @since 2.2 | |
| 523 | */ | |
| 524 | public static Envelope getEnvelope(final CoordinateReferenceSystem crs) { | |
| 525 | 7 | Envelope envelope = null; |
| 526 | 7 | GeneralEnvelope merged = null; |
| 527 | 7 | if (crs != null) { |
| 528 | 6 | final Extent domainOfValidity = crs.getDomainOfValidity(); |
| 529 | 6 | if (domainOfValidity != null) { |
| 530 | 6 | for (final GeographicExtent extent : domainOfValidity.getGeographicElements()) { |
| 531 | 6 | if (Boolean.FALSE.equals(extent.getInclusion())) { |
| 532 | 0 | continue; |
| 533 | } | |
| 534 | 6 | if (extent instanceof BoundingPolygon) { |
| 535 | 0 | for (final Geometry geometry : ((BoundingPolygon) extent).getPolygons()) { |
| 536 | 0 | final Envelope candidate = geometry.getEnvelope(); |
| 537 | 0 | if (candidate != null) { |
| 538 | 0 | final CoordinateReferenceSystem sourceCRS = |
| 539 | candidate.getCoordinateReferenceSystem(); | |
| 540 | 0 | if (sourceCRS == null || equalsIgnoreMetadata(sourceCRS, crs)) { |
| 541 | 0 | if (envelope == null) { |
| 542 | 0 | envelope = candidate; |
| 543 | } else { | |
| 544 | 0 | if (merged == null) { |
| 545 | 0 | envelope = merged = new GeneralEnvelope(envelope); |
| 546 | } | |
| 547 | 0 | merged.add(envelope); |
| 548 | } | |
| 549 | } | |
| 550 | } | |
| 551 | 0 | } |
| 552 | } | |
| 553 | } | |
| 554 | } | |
| 555 | } | |
| 556 | /* | |
| 557 | * If no envelope was found, uses the geographic bounding box as a fallback. We will | |
| 558 | * need to transform it from WGS84 to the supplied CRS. This step was not required in | |
| 559 | * the previous block because the later selected only envelopes in the right CRS. | |
| 560 | */ | |
| 561 | 7 | if (envelope == null) { |
| 562 | 7 | final GeographicBoundingBox bounds = getGeographicBoundingBox(crs); |
| 563 | 7 | if (bounds != null && !Boolean.FALSE.equals(bounds.getInclusion())) { |
| 564 | 6 | envelope = merged = new GeneralEnvelope( |
| 565 | new double[] {bounds.getWestBoundLongitude(), bounds.getSouthBoundLatitude()}, | |
| 566 | new double[] {bounds.getEastBoundLongitude(), bounds.getNorthBoundLatitude()}); | |
| 567 | /* | |
| 568 | * We do not assign WGS84 unconditionally to the geographic bounding box, because | |
| 569 | * it is not defined to be on a particular datum; it is only approximative bounds. | |
| 570 | * We try to get the GeographicCRS from the user-supplied CRS and fallback on WGS | |
| 571 | * 84 only if we found none. | |
| 572 | */ | |
| 573 | 6 | final SingleCRS targetCRS = getHorizontalCRS(crs); |
| 574 | 6 | final GeographicCRS sourceCRS = CRSUtilities.getStandardGeographicCRS2D(targetCRS); |
| 575 | 6 | merged.setCoordinateReferenceSystem(sourceCRS); |
| 576 | try { | |
| 577 | 6 | envelope = transform(envelope, targetCRS); |
| 578 | 0 | } catch (TransformException exception) { |
| 579 | /* | |
| 580 | * The envelope is probably outside the range of validity for this CRS. | |
| 581 | * It should not occurs, since the envelope is supposed to describe the | |
| 582 | * CRS area of validity. Logs a warning and returns null, since it is a | |
| 583 | * legal return value according this method contract. | |
| 584 | */ | |
| 585 | 0 | envelope = null; |
| 586 | 0 | unexpectedException("getEnvelope", exception); |
| 587 | 6 | } |
| 588 | /* | |
| 589 | * If transform(...) created a new envelope, its CRS is already targetCRS so it | |
| 590 | * doesn't matter if 'merged' is not anymore the right instance. If 'transform' | |
| 591 | * returned the envelope unchanged, the 'merged' reference still valid and we | |
| 592 | * want to ensure that it have the user-supplied CRS. | |
| 593 | */ | |
| 594 | 6 | merged.setCoordinateReferenceSystem(targetCRS); |
| 595 | } | |
| 596 | } | |
| 597 | 7 | return envelope; |
| 598 | } | |
| 599 | ||
| 600 | /** | |
| 601 | * Returns the valid geographic area for the specified coordinate reference system, | |
| 602 | * or {@code null} if unknown. | |
| 603 | * | |
| 604 | * This method fetches the {@linkplain CoordinateReferenceSystem#getDomainOfValidity domain | |
| 605 | * of validity} associated with the given CRS. Only {@linkplain GeographicExtent geographic | |
| 606 | * extents} of kind {@linkplain GeographicBoundingBox geographic bounding box} are taken in | |
| 607 | * account. | |
| 608 | * | |
| 609 | * @param crs The coordinate reference system, or {@code null}. | |
| 610 | * @return The geographic area, or {@code null} if none. | |
| 611 | * | |
| 612 | * @see #getEnvelope(CoordinateReferenceSystem) | |
| 613 | * | |
| 614 | * @category information | |
| 615 | * @since 2.3 | |
| 616 | */ | |
| 617 | public static GeographicBoundingBox getGeographicBoundingBox(final CoordinateReferenceSystem crs) { | |
| 618 | 8 | GeographicBoundingBox bounds = null; |
| 619 | 8 | DefaultGeographicBoundingBox merged = null; |
| 620 | 8 | if (crs != null) { |
| 621 | 7 | final Extent domainOfValidity = crs.getDomainOfValidity(); |
| 622 | 7 | if (domainOfValidity != null) { |
| 623 | 7 | for (final GeographicExtent extent : domainOfValidity.getGeographicElements()) { |
| 624 | 7 | if (extent instanceof GeographicBoundingBox) { |
| 625 | 7 | final GeographicBoundingBox candidate = (GeographicBoundingBox) extent; |
| 626 | 7 | if (bounds == null) { |
| 627 | 7 | bounds = candidate; |
| 628 | } else { | |
| 629 | 0 | if (merged == null) { |
| 630 | 0 | bounds = merged = new DefaultGeographicBoundingBox(bounds); |
| 631 | } | |
| 632 | 0 | merged.add(candidate); |
| 633 | } | |
| 634 | 7 | } |
| 635 | } | |
| 636 | } | |
| 637 | } | |
| 638 | 8 | return bounds; |
| 639 | } | |
| 640 | ||
| 641 | /** | |
| 642 | * Returns {@code true} if the given CRS is horizontal. This method is provided because there is | |
| 643 | * a direct way to determine if a CRS is vertical or temporal, but no direct way to determine if | |
| 644 | * it is horizontal. So this method complements the check for spatio-temporal components as below: | |
| 645 | * <p> | |
| 646 | * <ul> | |
| 647 | * <li>{@code if (crs instanceof TemporalCRS)} determines if the CRS is for the temporal component.</li> | |
| 648 | * <li>{@code if (crs instanceof VerticalCRS)} determines if the CRS is for the vertical component.</li> | |
| 649 | * <li>{@code if (CRS.isHorizontalCRS(crs))} determines if the CRS is for the horizontal component.</li> | |
| 650 | * </ul> | |
| 651 | * <p> | |
| 652 | * This method considers a CRS as horizontal if it is two-dimensional and comply | |
| 653 | * with one of the following conditions: | |
| 654 | * <p> | |
| 655 | * <ul> | |
| 656 | * <li>It is an instance of {@link GeographicCRS}.</li> | |
| 657 | * <li>It is an instance of {@link ProjectedCRS} (actually this is not explicitly | |
| 658 | * checked, since this condition is a special case of the condition below).</li> | |
| 659 | * <li>It is an instance of {@link GeneralDerivedCRS} based on a horizontal CRS | |
| 660 | * and using a {@link GeodeticDatum}.</li> | |
| 661 | * </ul> | |
| 662 | * <p> | |
| 663 | * The last condition ({@code GeneralDerivedCRS} based on a horizontal CRS) allows for example | |
| 664 | * to express the coordinates of a projected CRS (which use a Cartesian coordinate system) in | |
| 665 | * a {@linkplain org.opengis.referencing.cs.PolarCS polar coordinate system} and still consider | |
| 666 | * the result as horizontal. However this assumes that the axes of the derived CRS are coplanar | |
| 667 | * with the axes of the base CRS. This is not always true since a derived CRS could be created | |
| 668 | * for an inclined plane, for example a plane fitting the slope of a mountain. ISO 19111 does | |
| 669 | * not specify how to handle this case. In the Geotk implementation, we suggest to define a new | |
| 670 | * {@linkplain Datum datum} for inclined plane which is not a geodetic datum. | |
| 671 | * | |
| 672 | * @param crs The coordinate reference system, or {@code null}. | |
| 673 | * @return {@code true} if the given CRS is non-null and comply with one of the above | |
| 674 | * conditions, or {@code false} otherwise. | |
| 675 | * | |
| 676 | * @see #getHorizontalCRS(CoordinateReferenceSystem) | |
| 677 | * | |
| 678 | * @category information | |
| 679 | * @since 3.05 | |
| 680 | */ | |
| 681 | public static boolean isHorizontalCRS(CoordinateReferenceSystem crs) { | |
| 682 | 19 | if (crs instanceof SingleCRS) { |
| 683 | 17 | final int dimension = crs.getCoordinateSystem().getDimension(); |
| 684 | 17 | if (dimension == 2) { |
| 685 | 17 | final Datum datum = ((SingleCRS) crs).getDatum(); |
| 686 | 17 | if (datum instanceof GeodeticDatum) { |
| 687 | 26 | while (crs instanceof GeneralDerivedCRS) { |
| 688 | 9 | crs = ((GeneralDerivedCRS) crs).getBaseCRS(); |
| 689 | } | |
| 690 | 17 | return (crs instanceof GeographicCRS); |
| 691 | } | |
| 692 | } | |
| 693 | } | |
| 694 | 2 | return false; |
| 695 | } | |
| 696 | ||
| 697 | /** | |
| 698 | * Returns the first horizontal coordinate reference system found in the given CRS, | |
| 699 | * or {@code null} if there is none. A horizontal CRS is usually a two-dimensional | |
| 700 | * {@linkplain GeographicCRS geographic} or {@linkplain ProjectedCRS projected} CRS. | |
| 701 | * See the {@link #isHorizontalCRS(CoordinateReferenceSystem) isHorizontalCRS} method for | |
| 702 | * a more accurate description about the conditions for a CRS to be considered horizontal. | |
| 703 | * | |
| 704 | * @param crs The coordinate reference system, or {@code null}. | |
| 705 | * @return The horizontal CRS, or {@code null} if none. | |
| 706 | * | |
| 707 | * @category information | |
| 708 | * @since 2.4 | |
| 709 | */ | |
| 710 | public static SingleCRS getHorizontalCRS(final CoordinateReferenceSystem crs) { | |
| 711 | 16 | if (crs instanceof SingleCRS) { |
| 712 | 11 | final CoordinateSystem cs = crs.getCoordinateSystem(); |
| 713 | 11 | final int dimension = cs.getDimension(); |
| 714 | 11 | if (dimension == 2) { |
| 715 | /* | |
| 716 | * For two-dimensional CRS, returns the CRS directly if it is either a | |
| 717 | * GeographicCRS, or any kind of derived CRS having a GeographicCRS as | |
| 718 | * its base and a geodetic datum. | |
| 719 | */ | |
| 720 | 10 | final Datum datum = ((SingleCRS) crs).getDatum(); |
| 721 | 10 | if (datum instanceof GeodeticDatum) { |
| 722 | 10 | CoordinateReferenceSystem base = crs; |
| 723 | 14 | while (base instanceof GeneralDerivedCRS) { |
| 724 | 4 | base = ((GeneralDerivedCRS) base).getBaseCRS(); |
| 725 | } | |
| 726 | // No need to test for ProjectedCRS, since the code above unwrap it. | |
| 727 | 10 | if (base instanceof GeographicCRS) { |
| 728 | 10 | assert isHorizontalCRS(crs) : crs; |
| 729 | 10 | return (SingleCRS) crs; // Really returns 'crs', not 'base'. |
| 730 | } | |
| 731 | } | |
| 732 | 0 | } else if (dimension >= 3 && crs instanceof GeographicCRS) { |
| 733 | /* | |
| 734 | * For three-dimensional Geographic CRS, extracts the axis having a direction | |
| 735 | * like "North", "North-East", "East", etc. If we find exactly two of them, | |
| 736 | * we can build a new GeographicCRS using them. | |
| 737 | */ | |
| 738 | 1 | CoordinateSystemAxis axis0 = null, axis1 = null; |
| 739 | 1 | int count = 0; |
| 740 | 4 | for (int i=0; i<dimension; i++) { |
| 741 | 3 | final CoordinateSystemAxis axis = cs.getAxis(i); |
| 742 | 3 | search: if (DefaultCoordinateSystemAxis.isCompassDirection(axis.getDirection())) { |
| 743 | 2 | switch (count++) { |
| 744 | 1 | case 0: axis0 = axis; break; |
| 745 | 1 | case 1: axis1 = axis; break; |
| 746 | default: break search; | |
| 747 | } | |
| 748 | } | |
| 749 | } | |
| 750 | 1 | if (count == 2) { |
| 751 | 1 | final GeodeticDatum datum = ((GeographicCRS) crs).getDatum(); |
| 752 | 1 | Map<String,?> properties = CRSUtilities.changeDimensionInName(cs, "3D", "2D"); |
| 753 | EllipsoidalCS horizontalCS; | |
| 754 | try { | |
| 755 | 1 | horizontalCS = FactoryFinder.getCSFactory(null). |
| 756 | createEllipsoidalCS(properties, axis0, axis1); | |
| 757 | 0 | } catch (FactoryException e) { |
| 758 | 0 | Logging.recoverableException(CRS.class, "getHorizontalCRS", e); |
| 759 | 0 | horizontalCS = new DefaultEllipsoidalCS(properties, axis0, axis1); |
| 760 | 1 | } |
| 761 | 1 | properties = CRSUtilities.changeDimensionInName(crs, "3D", "2D"); |
| 762 | GeographicCRS horizontalCRS; | |
| 763 | try { | |
| 764 | 1 | horizontalCRS = getCRSFactory().createGeographicCRS(properties, datum, horizontalCS); |
| 765 | 0 | } catch (FactoryException e) { |
| 766 | 0 | Logging.recoverableException(CRS.class, "getHorizontalCRS", e); |
| 767 | 0 | horizontalCRS = new DefaultGeographicCRS(properties, datum, horizontalCS); |
| 768 | 1 | } |
| 769 | 1 | assert isHorizontalCRS(horizontalCRS) : horizontalCRS; |
| 770 | 1 | return horizontalCRS; |
| 771 | } | |
| 772 | } | |
| 773 | } | |
| 774 | 5 | if (crs instanceof CompoundCRS) { |
| 775 | 5 | final CompoundCRS cp = (CompoundCRS) crs; |
| 776 | 5 | for (final CoordinateReferenceSystem c : cp.getComponents()) { |
| 777 | 5 | final SingleCRS candidate = getHorizontalCRS(c); |
| 778 | 5 | if (candidate != null) { |
| 779 | 5 | assert isHorizontalCRS(candidate) : candidate; |
| 780 | 5 | return candidate; |
| 781 | } | |
| 782 | 0 | } |
| 783 | } | |
| 784 | 0 | return null; |
| 785 | } | |
| 786 | ||
| 787 | /** | |
| 788 | * Returns the first projected coordinate reference system found in a the given CRS, | |
| 789 | * or {@code null} if there is none. | |
| 790 | * | |
| 791 | * @param crs The coordinate reference system, or {@code null}. | |
| 792 | * @return The projected CRS, or {@code null} if none. | |
| 793 | * | |
| 794 | * @category information | |
| 795 | * @since 2.4 | |
| 796 | */ | |
| 797 | public static ProjectedCRS getProjectedCRS(final CoordinateReferenceSystem crs) { | |
| 798 | 0 | if (crs instanceof ProjectedCRS) { |
| 799 | 0 | return (ProjectedCRS) crs; |
| 800 | } | |
| 801 | 0 | if (crs instanceof CompoundCRS) { |
| 802 | 0 | final CompoundCRS cp = (CompoundCRS) crs; |
| 803 | 0 | for (final CoordinateReferenceSystem c : cp.getComponents()) { |
| 804 | 0 | final ProjectedCRS candidate = getProjectedCRS(c); |
| 805 | 0 | if (candidate != null) { |
| 806 | 0 | return candidate; |
| 807 | } | |
| 808 | 0 | } |
| 809 | } | |
| 810 | 0 | return null; |
| 811 | } | |
| 812 | ||
| 813 | /** | |
| 814 | * Returns the first vertical coordinate reference system found in a the given CRS, | |
| 815 | * or {@code null} if there is none. | |
| 816 | * | |
| 817 | * @param crs The coordinate reference system, or {@code null}. | |
| 818 | * @return The vertical CRS, or {@code null} if none. | |
| 819 | * | |
| 820 | * @category information | |
| 821 | * @since 2.4 | |
| 822 | */ | |
| 823 | public static VerticalCRS getVerticalCRS(final CoordinateReferenceSystem crs) { | |
| 824 | 8 | if (crs instanceof VerticalCRS) { |
| 825 | 2 | return (VerticalCRS) crs; |
| 826 | } | |
| 827 | 6 | if (crs instanceof CompoundCRS) { |
| 828 | 3 | final CompoundCRS cp = (CompoundCRS) crs; |
| 829 | 3 | for (final CoordinateReferenceSystem c : cp.getComponents()) { |
| 830 | 5 | final VerticalCRS candidate = getVerticalCRS(c); |
| 831 | 5 | if (candidate != null) { |
| 832 | 3 | return candidate; |
| 833 | } | |
| 834 | 2 | } |
| 835 | } | |
| 836 | 3 | if (crs instanceof GeographicCRS) { |
| 837 | 0 | final CoordinateSystem cs = crs.getCoordinateSystem(); |
| 838 | 0 | if (cs.getDimension() >= 3) { |
| 839 | assert CRSUtilities.dimensionColinearWith(cs, | |
| 840 | 0 | DefaultCoordinateSystemAxis.ELLIPSOIDAL_HEIGHT) >= 0 : cs; |
| 841 | 0 | return DefaultVerticalCRS.ELLIPSOIDAL_HEIGHT; |
| 842 | } | |
| 843 | } | |
| 844 | 3 | return null; |
| 845 | } | |
| 846 | ||
| 847 | /** | |
| 848 | * Returns the first temporal coordinate reference system found in the given CRS, | |
| 849 | * or {@code null} if there is none. | |
| 850 | * | |
| 851 | * @param crs The coordinate reference system, or {@code null}. | |
| 852 | * @return The temporal CRS, or {@code null} if none. | |
| 853 | * | |
| 854 | * @category information | |
| 855 | * @since 2.4 | |
| 856 | */ | |
| 857 | public static TemporalCRS getTemporalCRS(final CoordinateReferenceSystem crs) { | |
| 858 | 9 | if (crs instanceof TemporalCRS) { |
| 859 | 1 | return (TemporalCRS) crs; |
| 860 | } | |
| 861 | 8 | if (crs instanceof CompoundCRS) { |
| 862 | 3 | final CompoundCRS cp = (CompoundCRS) crs; |
| 863 | 3 | for (final CoordinateReferenceSystem c : cp.getComponents()) { |
| 864 | 6 | final TemporalCRS candidate = getTemporalCRS(c); |
| 865 | 6 | if (candidate != null) { |
| 866 | 1 | return candidate; |
| 867 | } | |
| 868 | 5 | } |
| 869 | } | |
| 870 | 7 | return null; |
| 871 | } | |
| 872 | ||
| 873 | /** | |
| 874 | * Returns the first compound CRS which contains only the given components, in any order. | |
| 875 | * First, this method gets the {@link SingleCRS} components of the given compound CRS. If | |
| 876 | * all those components are {@linkplain #equalsIgnoreMetadata equal, ignoring metadata} | |
| 877 | * and order, to the {@code SingleCRS} components given to this method, then the given | |
| 878 | * {@code CompoundCRS} is returned. Otherwise if the given {@code CompoundCRS} contains | |
| 879 | * nested {@code CompoundCRS}, then those nested CRS are inspected recursively by the same | |
| 880 | * algorithm. Otherwise, this method returns {@code null}. | |
| 881 | * <p> | |
| 882 | * This method is useful for extracting metadata about the 3D spatial CRS part in a 4D | |
| 883 | * spatio-temporal CRS. For example given the following CRS: | |
| 884 | * | |
| 885 | * {@preformat wkt | |
| 886 | * COMPD_CS["Mercator + height + time", | |
| 887 | * COMPD_CS["Mercator + height", | |
| 888 | * PROJCS["Mercator", ...etc...] | |
| 889 | * VERT_CS["Ellipsoidal height", ...etc...]] | |
| 890 | * TemporalCRS["Modified Julian", ...etc...]] | |
| 891 | * } | |
| 892 | * | |
| 893 | * Then the following code will returns the nested {@code COMPD_CS["Mercator + height"]} | |
| 894 | * without prior knowledge of the CRS component order (the time CRS could be first, and | |
| 895 | * the vertical CRS could be before the horizontal one): | |
| 896 | * | |
| 897 | * {@preformat java | |
| 898 | * CompoundCRS crs = ...; | |
| 899 | * SingleCRS horizontalCRS = getHorizontalCRS(crs); | |
| 900 | * VerticalCRS verticalCRS = getVerticalCRS(crs); | |
| 901 | * if (horizontalCRS != null && verticalCRS != null) { | |
| 902 | * CompoundCRS spatialCRS = getCompoundCRS(crs, horizontalCRS, verticalCRS); | |
| 903 | * if (spatialCRS != null) { | |
| 904 | * // ... | |
| 905 | * } | |
| 906 | * } | |
| 907 | * } | |
| 908 | * | |
| 909 | * @param crs The compound CRS to compare with the given component CRS, or {@code null}. | |
| 910 | * @param components The CRS which must be components of the returned CRS. | |
| 911 | * @return A CRS which contains the given components, or {@code null} if none. | |
| 912 | * | |
| 913 | * @see DefaultCompoundCRS#getSingleCRS() | |
| 914 | * | |
| 915 | * @since 3.16 | |
| 916 | */ | |
| 917 | public static CompoundCRS getCompoundCRS(final CompoundCRS crs, final SingleCRS... components) { | |
| 918 | 47 | final List<SingleCRS> actualComponents = DefaultCompoundCRS.getSingleCRS(crs); |
| 919 | 47 | if (actualComponents.size() == components.length) { |
| 920 | 41 | int firstValid = 0; |
| 921 | 41 | final SingleCRS[] toSearch = components.clone(); |
| 922 | 41 | compare: for (final SingleCRS component : actualComponents) { |
| 923 | 92 | for (int i=firstValid; i<toSearch.length; i++) { |
| 924 | 90 | if (equalsIgnoreMetadata(component, toSearch[i])) { |
| 925 | /* | |
| 926 | * Found a match: remove it from the search list. Note that we copy the | |
| 927 | * remaining components to the end of the array (which is unusual) rather | |
| 928 | * than to the begining (as usual), in order to reduce the length of the | |
| 929 | * part to copy on the assumption that the components given to this method | |
| 930 | * are most likely in the same order than the elements in the CompoundCRS. | |
| 931 | */ | |
| 932 | 82 | System.arraycopy(toSearch, firstValid, toSearch, firstValid+1, i - firstValid); |
| 933 | 82 | toSearch[firstValid++] = null; |
| 934 | 82 | continue compare; |
| 935 | } | |
| 936 | } | |
| 937 | // No match found. We can stop the loop now. | |
| 938 | 2 | firstValid = -1; |
| 939 | 2 | break; |
| 940 | } | |
| 941 | /* | |
| 942 | * If we found all the requested components and nothing more, | |
| 943 | * returns the CRS. | |
| 944 | */ | |
| 945 | 41 | if (firstValid == toSearch.length) { |
| 946 | 39 | return crs; |
| 947 | } | |
| 948 | } | |
| 949 | /* | |
| 950 | * Search recursively in the sub-components. | |
| 951 | */ | |
| 952 | 8 | for (final CoordinateReferenceSystem component : crs.getComponents()) { |
| 953 | 12 | if (component instanceof CompoundCRS) { |
| 954 | 5 | final CompoundCRS candidate = getCompoundCRS((CompoundCRS) component, components); |
| 955 | 5 | if (candidate != null) { |
| 956 | 4 | return candidate; |
| 957 | } | |
| 958 | 8 | } |
| 959 | } | |
| 960 | 4 | return null; |
| 961 | } | |
| 962 | ||
| 963 | /** | |
| 964 | * Returns the coordinate reference system in the given range of dimension indices. | |
| 965 | * This method processes as below: | |
| 966 | * <p> | |
| 967 | * <ul> | |
| 968 | * <li>If the given {@code crs} is {@code null}, then this method returns {@code null}.</li> | |
| 969 | * <li>Otherwise if {@code lower} is 0 and {@code upper} if the number of CRS dimensions, | |
| 970 | * then this method returns the given CRS unchanged.</li> | |
| 971 | * <li>Otherwise if the given CRS is an instance of {@link CompoundCRS}, then this method | |
| 972 | * searches for a {@linkplain CompoundCRS#getComponents() component} where: | |
| 973 | * <ul> | |
| 974 | * <li>The {@linkplain CoordinateSystem#getDimension() number of dimensions} is | |
| 975 | * equals to {@code upper - lower};</li> | |
| 976 | * <li>The sum of the number of dimensions of all previous CRS is equals to | |
| 977 | * {@code lower}.</li> | |
| 978 | * </ul> | |
| 979 | * If such component is found, then it is returned.</li> | |
| 980 | * <li>Otherwise (i.e. no component match), this method returns {@code null}.</li> | |
| 981 | * </ul> | |
| 982 | * <p> | |
| 983 | * This method does <strong>not</strong> attempt to build new CRS from the components. | |
| 984 | * For example it does not attempt to create a 3D geographic CRS from a 2D one + a vertical | |
| 985 | * component. If such functionality is desired, consider using the utility methods in | |
| 986 | * {@link org.geotoolkit.referencing.factory.ReferencingFactoryContainer} instead. | |
| 987 | * | |
| 988 | * @param crs The coordinate reference system to decompose, or {@code null}. | |
| 989 | * @param lower The first dimension to keep, inclusive. | |
| 990 | * @param upper The last dimension to keep, exclusive. | |
| 991 | * @return The sub-coordinate system, or {@code null} if the given {@code crs} was {@code null} | |
| 992 | * or can't be decomposed for dimensions in the range {@code [lower..upper]}. | |
| 993 | * @throws IndexOutOfBoundsException If the given index are out of bounds. | |
| 994 | * | |
| 995 | * @see org.geotoolkit.referencing.factory.ReferencingFactoryContainer#separate(CoordinateReferenceSystem, int[]) | |
| 996 | * | |
| 997 | * @since 3.16 | |
| 998 | */ | |
| 999 | public static CoordinateReferenceSystem getSubCRS(CoordinateReferenceSystem crs, int lower, int upper) { | |
| 1000 | 10 | if (crs != null) { |
| 1001 | 10 | int dimension = crs.getCoordinateSystem().getDimension(); |
| 1002 | 10 | if (lower < 0 || lower > upper || upper > dimension) { |
| 1003 | 0 | throw new IndexOutOfBoundsException(Errors.format( |
| 1004 | Errors.Keys.INDEX_OUT_OF_BOUNDS_$1, lower < 0 ? lower : upper)); | |
| 1005 | } | |
| 1006 | 20 | while (lower != 0 || upper != dimension) { |
| 1007 | 11 | final List<? extends CoordinateReferenceSystem> c = CRSUtilities.getComponents(crs); |
| 1008 | 11 | if (c == null) { |
| 1009 | 1 | return null; |
| 1010 | } | |
| 1011 | 10 | for (final Iterator<? extends CoordinateReferenceSystem> it=c.iterator(); it.hasNext();) { |
| 1012 | 16 | crs = it.next(); |
| 1013 | 16 | dimension = crs.getCoordinateSystem().getDimension(); |
| 1014 | 16 | if (lower < dimension) { |
| 1015 | 10 | break; |
| 1016 | } | |
| 1017 | 6 | lower -= dimension; |
| 1018 | 6 | upper -= dimension; |
| 1019 | } | |
| 1020 | 10 | } |
| 1021 | } | |
| 1022 | 9 | return crs; |
| 1023 | } | |
| 1024 | ||
| 1025 | /** | |
| 1026 | * Returns the datum of the specified CRS, or {@code null} if none. | |
| 1027 | * This method processes as below: | |
| 1028 | * <p> | |
| 1029 | * <ul> | |
| 1030 | * <li>If the given CRS is an instance of {@link SingleCRS}, then this method returns | |
| 1031 | * <code>crs.{@linkplain SingleCRS#getDatum() getDatum()}</code>.</li> | |
| 1032 | * <li>Otherwise if the given CRS is an instance of {@link CompoundCRS}, then: | |
| 1033 | * <ul> | |
| 1034 | * <li>If all components have the same datum, then that datum is returned.</li> | |
| 1035 | * <li>Otherwise if the CRS contains only a geodetic datum with a vertical datum | |
| 1036 | * of type <em>ellipsoidal height</em> (no other type accepted), then the | |
| 1037 | * geodetic datum is returned.</li> | |
| 1038 | * </ul></li> | |
| 1039 | * <li>Otherwise this method returns {@code null}.</li> | |
| 1040 | * </ul> | |
| 1041 | * | |
| 1042 | * @param crs The coordinate reference system for which to get the datum. May be {@code null}. | |
| 1043 | * @return The datum in the given CRS, or {@code null} if none. | |
| 1044 | * | |
| 1045 | * @see #getEllipsoid(CoordinateReferenceSystem) | |
| 1046 | * | |
| 1047 | * @category information | |
| 1048 | * @since 3.16 | |
| 1049 | */ | |
| 1050 | public static Datum getDatum(final CoordinateReferenceSystem crs) { | |
| 1051 | 3 | return CRSUtilities.getDatum(crs); |
| 1052 | } | |
| 1053 | ||
| 1054 | /** | |
| 1055 | * Returns the first ellipsoid found in a coordinate reference system, | |
| 1056 | * or {@code null} if there is none. More specifically: | |
| 1057 | * <p> | |
| 1058 | * <ul> | |
| 1059 | * <li>If the given CRS is an instance of {@link SingleCRS} and its datum is a | |
| 1060 | * {@link GeodeticDatum}, then this method returns the datum ellipsoid.</li> | |
| 1061 | * <li>Otherwise if the given CRS is an instance of {@link CompoundCRS}, then this method | |
| 1062 | * invokes itself recursively for each component until a geodetic datum is found.</li> | |
| 1063 | * <li>Otherwise this method returns {@code null}.</li> | |
| 1064 | * </ul> | |
| 1065 | * <p> | |
| 1066 | * Note that this method does not check if there is more than one ellipsoid | |
| 1067 | * (it should never be the case). | |
| 1068 | * | |
| 1069 | * @param crs The coordinate reference system, or {@code null}. | |
| 1070 | * @return The ellipsoid, or {@code null} if none. | |
| 1071 | * | |
| 1072 | * @see #getDatum(CoordinateReferenceSystem) | |
| 1073 | * | |
| 1074 | * @category information | |
| 1075 | * @since 2.4 | |
| 1076 | */ | |
| 1077 | public static Ellipsoid getEllipsoid(final CoordinateReferenceSystem crs) { | |
| 1078 | 1 | if (crs instanceof SingleCRS) { |
| 1079 | 1 | final Datum datum = ((SingleCRS) crs).getDatum(); |
| 1080 | 1 | if (datum instanceof GeodeticDatum) { |
| 1081 | 1 | return ((GeodeticDatum) datum).getEllipsoid(); |
| 1082 | } | |
| 1083 | } | |
| 1084 | 0 | if (crs instanceof CompoundCRS) { |
| 1085 | 0 | for (final CoordinateReferenceSystem c : ((CompoundCRS) crs).getComponents()) { |
| 1086 | 0 | final Ellipsoid candidate = getEllipsoid(c); |
| 1087 | 0 | if (candidate != null) { |
| 1088 | 0 | return candidate; |
| 1089 | } | |
| 1090 | 0 | } |
| 1091 | } | |
| 1092 | 0 | return null; |
| 1093 | } | |
| 1094 | ||
| 1095 | ||
| 1096 | ///////////////////////////////////////////////// | |
| 1097 | //// //// | |
| 1098 | //// COORDINATE OPERATIONS //// | |
| 1099 | //// //// | |
| 1100 | ///////////////////////////////////////////////// | |
| 1101 | ||
| 1102 | /** | |
| 1103 | * Compares the specified objects for equality, ignoring metadata. If this method returns | |
| 1104 | * {@code true}, then: | |
| 1105 | * | |
| 1106 | * <ul> | |
| 1107 | * <li><p>If the two given objects are {@link MathTransform} instances, then transforming | |
| 1108 | * a set of coordinate values using one transform will produce the same results than | |
| 1109 | * transforming the same coordinates with the other transform.</p></li> | |
| 1110 | * | |
| 1111 | * <li><p>If the two given objects are {@link CoordinateReferenceSystem} instances, | |
| 1112 | * then a call to <code>{@linkplain #findMathTransform(CoordinateReferenceSystem, | |
| 1113 | * CoordinateReferenceSystem) findMathTransform}(crs1, crs2)</code> will return | |
| 1114 | * an identity transform.</p></li> | |
| 1115 | * </ul> | |
| 1116 | * | |
| 1117 | * If a more lenient comparison - allowing slight differences in numerical values - is wanted, | |
| 1118 | * then {@link #equalsApproximatively(Object, Object)} can be used instead. | |
| 1119 | * | |
| 1120 | * {@section Implementation note} | |
| 1121 | * This is a convenience method for the following method call: | |
| 1122 | * | |
| 1123 | * {@preformat java | |
| 1124 | * return Utilities.deepEquals(object1, object2, ComparisonMode.IGNORE_METADATA); | |
| 1125 | * } | |
| 1126 | * | |
| 1127 | * @param object1 The first object to compare (may be null). | |
| 1128 | * @param object2 The second object to compare (may be null). | |
| 1129 | * @return {@code true} if both objects are equal, ignoring metadata. | |
| 1130 | * | |
| 1131 | * @see Utilities#deepEquals(Object, Object, ComparisonMode) | |
| 1132 | * @see ComparisonMode#IGNORE_METADATA | |
| 1133 | * | |
| 1134 | * @category information | |
| 1135 | * @since 2.2 | |
| 1136 | */ | |
| 1137 | public static boolean equalsIgnoreMetadata(final Object object1, final Object object2) { | |
| 1138 | 40118 | return Utilities.deepEquals(object1, object2, ComparisonMode.IGNORE_METADATA); |
| 1139 | } | |
| 1140 | ||
| 1141 | /** | |
| 1142 | * Compares the specified objects for equality, ignoring metadata and slight differences | |
| 1143 | * in numerical values. If this method returns {@code true}, then: | |
| 1144 | * | |
| 1145 | * <ul> | |
| 1146 | * <li><p>If the two given objects are {@link MathTransform} instances, then transforming a | |
| 1147 | * set of coordinate values using one transform will produce <em>approximatively</em> | |
| 1148 | * the same results than transforming the same coordinates with the other transform.</p></li> | |
| 1149 | * | |
| 1150 | * <li><p>If the two given objects are {@link CoordinateReferenceSystem} instances, | |
| 1151 | * then a call to <code>{@linkplain #findMathTransform(CoordinateReferenceSystem, | |
| 1152 | * CoordinateReferenceSystem) findMathTransform}(crs1, crs2)</code> will return | |
| 1153 | * a transform close to the identity transform.</p></li> | |
| 1154 | * </ul> | |
| 1155 | * | |
| 1156 | * {@section Implementation note} | |
| 1157 | * This is a convenience method for the following method call: | |
| 1158 | * | |
| 1159 | * {@preformat java | |
| 1160 | * return Utilities.deepEquals(object1, object2, ComparisonMode.APPROXIMATIVE); | |
| 1161 | * } | |
| 1162 | * | |
| 1163 | * @param object1 The first object to compare (may be null). | |
| 1164 | * @param object2 The second object to compare (may be null). | |
| 1165 | * @return {@code true} if both objects are approximatively equal. | |
| 1166 | * | |
| 1167 | * @see Utilities#deepEquals(Object, Object, ComparisonMode) | |
| 1168 | * @see ComparisonMode#APPROXIMATIVE | |
| 1169 | * | |
| 1170 | * @category information | |
| 1171 | * @since 3.18 | |
| 1172 | */ | |
| 1173 | public static boolean equalsApproximatively(final Object object1, final Object object2) { | |
| 1174 | 48819 | return Utilities.deepEquals(object1, object2, ComparisonMode.APPROXIMATIVE); |
| 1175 | } | |
| 1176 | ||
| 1177 | /** | |
| 1178 | * Grabs a transform between two Coordinate Reference Systems. This convenience method is a | |
| 1179 | * shorthand for the following: | |
| 1180 | * | |
| 1181 | * {@preformat java | |
| 1182 | * CoordinateOperationFactory factory = FactoryFinder.getCoordinateOperationFactory(null); | |
| 1183 | * CoordinateOperation operation = factory.createOperation(sourceCRS, targetCRS); | |
| 1184 | * MathTransform transform = operation.getMathTransform(); | |
| 1185 | * } | |
| 1186 | * | |
| 1187 | * Note that some metadata like {@linkplain CoordinateOperation#getCoordinateOperationAccuracy | |
| 1188 | * coordinate operation accuracy} are lost by this method. If those metadata are wanted, use the | |
| 1189 | * {@linkplain CoordinateOperationFactory coordinate operation factory} directly. | |
| 1190 | * <p> | |
| 1191 | * Sample use: | |
| 1192 | * | |
| 1193 | * {@preformat java | |
| 1194 | * CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:42102"); | |
| 1195 | * CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:4326"); | |
| 1196 | * MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS); | |
| 1197 | * } | |
| 1198 | * | |
| 1199 | * @param sourceCRS The source CRS. | |
| 1200 | * @param targetCRS The target CRS. | |
| 1201 | * @return The math transform from {@code sourceCRS} to {@code targetCRS}. | |
| 1202 | * @throws FactoryException If no math transform can be created for the specified source and | |
| 1203 | * target CRS. | |
| 1204 | * | |
| 1205 | * @see CoordinateOperationFactory#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem) | |
| 1206 | * | |
| 1207 | * @category transform | |
| 1208 | */ | |
| 1209 | public static MathTransform findMathTransform(final CoordinateReferenceSystem sourceCRS, | |
| 1210 | final CoordinateReferenceSystem targetCRS) | |
| 1211 | throws FactoryException | |
| 1212 | { | |
| 1213 | // No need to synchronize; this is not a big deal if 'defaultLenient' is computed twice. | |
| 1214 | 9612 | Boolean lenient = defaultLenient; |
| 1215 | 9612 | if (lenient == null) { |
| 1216 | 3 | defaultLenient = lenient = Boolean.TRUE.equals( |
| 1217 | Hints.getSystemDefault(Hints.LENIENT_DATUM_SHIFT)); | |
| 1218 | } | |
| 1219 | 9612 | return findMathTransform(sourceCRS, targetCRS, lenient); |
| 1220 | } | |
| 1221 | ||
| 1222 | /** | |
| 1223 | * Grab a transform between two Coordinate Reference Systems. This method is similar to | |
| 1224 | * <code>{@linkplain #findMathTransform(CoordinateReferenceSystem, CoordinateReferenceSystem) | |
| 1225 | * findMathTransform}(sourceCRS, targetCRS)</code>, except that it specifies whatever this | |
| 1226 | * method should tolerate <cite>lenient datum shift</cite>. If the {@code lenient} argument | |
| 1227 | * is {@code true}, then this method will not throw a "<cite>Bursa-Wolf parameters required</cite>" | |
| 1228 | * exception during datum shifts if the Bursa-Wolf parameters are not specified. | |
| 1229 | * Instead it will assume a no datum shift. | |
| 1230 | * | |
| 1231 | * @param sourceCRS The source CRS. | |
| 1232 | * @param targetCRS The target CRS. | |
| 1233 | * @param lenient {@code true} if the math transform should be created even when there is | |
| 1234 | * no information available for a datum shift. if this argument is not specified, | |
| 1235 | * then the default value is determined from the {@linkplain Hints#getSystemDefault | |
| 1236 | * system default}. | |
| 1237 | * @return The math transform from {@code sourceCRS} to {@code targetCRS}. | |
| 1238 | * @throws FactoryException If no math transform can be created for the specified source and | |
| 1239 | * target CRS. | |
| 1240 | * | |
| 1241 | * @see Hints#LENIENT_DATUM_SHIFT | |
| 1242 | * @see CoordinateOperationFactory#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem) | |
| 1243 | * | |
| 1244 | * @category transform | |
| 1245 | */ | |
| 1246 | public static MathTransform findMathTransform(final CoordinateReferenceSystem sourceCRS, | |
| 1247 | final CoordinateReferenceSystem targetCRS, | |
| 1248 | boolean lenient) | |
| 1249 | throws FactoryException | |
| 1250 | { | |
| 1251 | 19217 | if (equalsIgnoreMetadata(sourceCRS, targetCRS)) { |
| 1252 | // Slight optimization in order to avoid the overhead of loading the full referencing engine. | |
| 1253 | 2 | return MathTransforms.identity(sourceCRS.getCoordinateSystem().getDimension()); |
| 1254 | } | |
| 1255 | 19215 | ensureNonNull("sourceCRS", sourceCRS); |
| 1256 | 19215 | ensureNonNull("targetCRS", targetCRS); |
| 1257 | 19215 | CoordinateOperationFactory operationFactory = getCoordinateOperationFactory(lenient); |
| 1258 | 19215 | return operationFactory.createOperation(sourceCRS, targetCRS).getMathTransform(); |
| 1259 | } | |
| 1260 | ||
| 1261 | // Note: the above 4 transform methods simply delegate their work to the Envelopes class. | |
| 1262 | // We keep those methods mostly for historical reasons. Some Geotk code still reference | |
| 1263 | // those methods instead than Envelopes. We do that when the CRS class is used anyway so | |
| 1264 | // there is no advantage to reference one more class. | |
| 1265 | ||
| 1266 | /** | |
| 1267 | * Transforms the given envelope to the specified CRS. If any argument is null, or if the | |
| 1268 | * {@linkplain Envelope#getCoordinateReferenceSystem() envelope CRS} is null or the same | |
| 1269 | * instance than the given target CRS, then the given envelope is returned unchanged. | |
| 1270 | * Otherwise a new transformed envelope is returned. | |
| 1271 | * <p> | |
| 1272 | * See {@link Envelopes#transform(Envelope, CoordinateReferenceSystem)} for more information. | |
| 1273 | * This method delegates its work to the above-cited {@code Envelopes} class and is defined | |
| 1274 | * in this {@code CRS} class only for convenience. | |
| 1275 | * | |
| 1276 | * @param envelope The envelope to transform (may be {@code null}). | |
| 1277 | * @param targetCRS The target CRS (may be {@code null}). | |
| 1278 | * @return A new transformed envelope, or directly {@code envelope} if no change was required. | |
| 1279 | * @throws TransformException If a transformation was required and failed. | |
| 1280 | * | |
| 1281 | * @category transform | |
| 1282 | * @since 2.5 | |
| 1283 | */ | |
| 1284 | // See the above comment about why some Geotk code still reference this method. | |
| 1285 | public static Envelope transform(Envelope envelope, final CoordinateReferenceSystem targetCRS) | |
| 1286 | throws TransformException | |
| 1287 | { | |
| 1288 | 9 | return Envelopes.transform(envelope, targetCRS); |
| 1289 | } | |
| 1290 | ||
| 1291 | /** | |
| 1292 | * Transforms an envelope using the given {@linkplain MathTransform math transform}. | |
| 1293 | * The transformation is only approximative: the returned envelope may be bigger than | |
| 1294 | * necessary, or smaller than required if the bounding box contains a pole. | |
| 1295 | * <p> | |
| 1296 | * See {@link Envelopes#transform(MathTransform, Envelope)} for more information. | |
| 1297 | * This method delegates its work to the above-cited {@code Envelopes} class and | |
| 1298 | * is defined in this {@code CRS} class only for convenience. | |
| 1299 | * | |
| 1300 | * @param transform The transform to use. | |
| 1301 | * @param envelope Envelope to transform, or {@code null}. This envelope will not be modified. | |
| 1302 | * @return The transformed envelope, or {@code null} if {@code envelope} was null. | |
| 1303 | * @throws TransformException if a transform failed. | |
| 1304 | * | |
| 1305 | * @category transform | |
| 1306 | * @since 2.4 | |
| 1307 | */ | |
| 1308 | // See the above comment about why some Geotk code still reference this method. | |
| 1309 | public static GeneralEnvelope transform(final MathTransform transform, final Envelope envelope) | |
| 1310 | throws TransformException | |
| 1311 | { | |
| 1312 | 0 | return Envelopes.transform(transform, envelope); |
| 1313 | } | |
| 1314 | ||
| 1315 | /** | |
| 1316 | * Transforms an envelope using the given {@linkplain CoordinateOperation coordinate operation}. | |
| 1317 | * The transformation is only approximative: the returned envelope may be bigger than the | |
| 1318 | * smallest possible bounding box, but should not be smaller in most cases. | |
| 1319 | * <p> | |
| 1320 | * See {@link Envelopes#transform(CoordinateOperation, Envelope)} for more information. | |
| 1321 | * This method delegates its work to the above-cited {@code Envelopes} class and is defined | |
| 1322 | * in this {@code CRS} class only for convenience. | |
| 1323 | * | |
| 1324 | * @param operation The operation to use. | |
| 1325 | * @param envelope Envelope to transform, or {@code null}. This envelope will not be modified. | |
| 1326 | * @return The transformed envelope, or {@code null} if {@code envelope} was null. | |
| 1327 | * @throws TransformException if a transform failed. | |
| 1328 | * | |
| 1329 | * @category transform | |
| 1330 | * @since 2.4 | |
| 1331 | */ | |
| 1332 | // See the above comment about why some Geotk code still reference this method. | |
| 1333 | public static GeneralEnvelope transform(final CoordinateOperation operation, Envelope envelope) | |
| 1334 | throws TransformException | |
| 1335 | { | |
| 1336 | 0 | return Envelopes.transform(operation, envelope); |
| 1337 | } | |
| 1338 | ||
| 1339 | /** | |
| 1340 | * Transforms a rectangular envelope using the given {@linkplain MathTransform math transform}. | |
| 1341 | * The transformation is only approximative: the returned envelope may be bigger than | |
| 1342 | * necessary, or smaller than required if the bounding box contains a pole. | |
| 1343 | * <p> | |
| 1344 | * See {@link Envelopes#transform(MathTransform2D, Rectangle2D, Rectangle2D)} for more | |
| 1345 | * information. This method delegates its work to the above-cited {@code Envelopes} class | |
| 1346 | * and is defined in this {@code CRS} class only for convenience. | |
| 1347 | * | |
| 1348 | * @param transform The transform to use. Source and target dimension must be 2. | |
| 1349 | * @param envelope The rectangle to transform (may be {@code null}). | |
| 1350 | * @param destination The destination rectangle (may be {@code envelope}). | |
| 1351 | * If {@code null}, a new rectangle will be created and returned. | |
| 1352 | * @return {@code destination}, or a new rectangle if {@code destination} was non-null | |
| 1353 | * and {@code envelope} was null. | |
| 1354 | * @throws TransformException if a transform failed. | |
| 1355 | * | |
| 1356 | * @category transform | |
| 1357 | * @since 2.4 | |
| 1358 | */ | |
| 1359 | // See the above comment about why some Geotk code still reference this method. | |
| 1360 | public static Rectangle2D transform(final MathTransform2D transform, | |
| 1361 | final Rectangle2D envelope, | |
| 1362 | Rectangle2D destination) | |
| 1363 | throws TransformException | |
| 1364 | { | |
| 1365 | 0 | return Envelopes.transform(transform, envelope, destination); |
| 1366 | } | |
| 1367 | ||
| 1368 | /** | |
| 1369 | * Transforms a rectangular envelope using the given {@linkplain CoordinateOperation coordinate | |
| 1370 | * operation}. The transformation is only approximative: the returned envelope may be bigger | |
| 1371 | * than the smallest possible bounding box, but should not be smaller in most cases. | |
| 1372 | * <p> | |
| 1373 | * See {@link Envelopes#transform(CoordinateOperation, Rectangle2D, Rectangle2D)} for more | |
| 1374 | * information. This method delegates its work to the above-cited {@code Envelopes} class | |
| 1375 | * and is defined in this {@code CRS} class only for convenience. | |
| 1376 | * | |
| 1377 | * @param operation The operation to use. Source and target dimension must be 2. | |
| 1378 | * @param envelope The rectangle to transform (may be {@code null}). | |
| 1379 | * @param destination The destination rectangle (may be {@code envelope}). | |
| 1380 | * If {@code null}, a new rectangle will be created and returned. | |
| 1381 | * @return {@code destination}, or a new rectangle if {@code destination} was non-null | |
| 1382 | * and {@code envelope} was null. | |
| 1383 | * @throws TransformException if a transform failed. | |
| 1384 | * | |
| 1385 | * @category transform | |
| 1386 | * @since 2.4 | |
| 1387 | */ | |
| 1388 | // See the above comment about why some Geotk code still reference this method. | |
| 1389 | public static Rectangle2D transform(final CoordinateOperation operation, | |
| 1390 | final Rectangle2D envelope, | |
| 1391 | Rectangle2D destination) | |
| 1392 | throws TransformException | |
| 1393 | { | |
| 1394 | 0 | return Envelopes.transform(operation, envelope, destination); |
| 1395 | } | |
| 1396 | ||
| 1397 | /** | |
| 1398 | * Transforms the given relative distance using the given transform. A relative distance | |
| 1399 | * vector is transformed without applying the translation components. However it needs to | |
| 1400 | * be computed at a particular location, given by the {@code origin} parameter in units | |
| 1401 | * of the source CRS. | |
| 1402 | * | |
| 1403 | * @param transform The transformation to apply. | |
| 1404 | * @param origin The position where to compute the delta transform in the source CRS. | |
| 1405 | * @param vector The distance vector to be delta transformed. | |
| 1406 | * @return The result of the delta transformation. | |
| 1407 | * @throws TransformException if the transformation failed. | |
| 1408 | * | |
| 1409 | * @see AffineTransform#deltaTransform(Point2D, Point2D) | |
| 1410 | * | |
| 1411 | * @since 3.10 (derived from 2.3) | |
| 1412 | */ | |
| 1413 | public static double[] deltaTransform(final MathTransform transform, | |
| 1414 | final DirectPosition origin, final double... vector) throws TransformException | |
| 1415 | { | |
| 1416 | 1 | ensureNonNull("transform", transform); |
| 1417 | 1 | final int sourceDim = transform.getSourceDimensions(); |
| 1418 | 1 | final int targetDim = transform.getTargetDimensions(); |
| 1419 | 1 | final double[] result = new double[targetDim]; |
| 1420 | 1 | if (vector.length != sourceDim) { |
| 1421 | 0 | throw new IllegalArgumentException(Errors.format(Errors.Keys.MISMATCHED_DIMENSION_$3, |
| 1422 | "vector", vector.length, sourceDim)); | |
| 1423 | } | |
| 1424 | 1 | if (transform instanceof AffineTransform) { |
| 1425 | 0 | ((AffineTransform) transform).deltaTransform(vector, 0, result, 0, 1); |
| 1426 | } else { | |
| 1427 | /* | |
| 1428 | * If the optimized case in the previous "if" statement can't be used, | |
| 1429 | * use a more generic (but more costly) algorithm. | |
| 1430 | */ | |
| 1431 | 1 | final double[] coordinates = new double[2 * Math.max(sourceDim, targetDim)]; |
| 1432 | 3 | for (int i=0; i<sourceDim; i++) { |
| 1433 | 2 | final double c = origin.getOrdinate(i); |
| 1434 | 2 | final double d = vector[i] * 0.5; |
| 1435 | 2 | coordinates[i] = c - d; |
| 1436 | 2 | coordinates[i + sourceDim] = c + d; |
| 1437 | } | |
| 1438 | 1 | transform.transform(coordinates, 0, coordinates, 0, 2); |
| 1439 | 3 | for (int i=0; i<targetDim; i++) { |
| 1440 | 2 | result[i] = coordinates[i + targetDim] - coordinates[i]; |
| 1441 | } | |
| 1442 | } | |
| 1443 | 1 | return result; |
| 1444 | } | |
| 1445 | ||
| 1446 | /** | |
| 1447 | * Invoked when an unexpected exception occurred. Those exceptions must be non-fatal, | |
| 1448 | * i.e. the caller <strong>must</strong> have a reasonable fallback (otherwise it | |
| 1449 | * should propagate the exception). | |
| 1450 | */ | |
| 1451 | static void unexpectedException(final String methodName, final Exception exception) { | |
| 1452 | 0 | Logging.unexpectedException(CRS.class, methodName, exception); |
| 1453 | 0 | } |
| 1454 | ||
| 1455 | /** | |
| 1456 | * Resets some aspects of the referencing system. The aspects to be reset are specified by | |
| 1457 | * a space or comma delimited string, which may include any of the following elements: | |
| 1458 | * <p> | |
| 1459 | * <ul> | |
| 1460 | * <li>{@code "plugins"} for {@linkplain AuthorityFactoryFinder#scanForPlugins searching | |
| 1461 | * the classpath for new plugins}.</li> | |
| 1462 | * </ul> | |
| 1463 | * | |
| 1464 | * @param aspects The aspects to reset, or {@code "all"} for all of them. | |
| 1465 | * Unknown aspects are silently ignored. | |
| 1466 | * | |
| 1467 | * @since 2.5 | |
| 1468 | * | |
| 1469 | * @deprecated This method doesn't do anything more than {@link AuthorityFactoryFinder#scanForPlugins()}. | |
| 1470 | */ | |
| 1471 | @Deprecated | |
| 1472 | public static synchronized void reset(final String aspects) { | |
| 1473 | 0 | ensureNonNull("aspects", aspects); |
| 1474 | 0 | final StringTokenizer tokens = new StringTokenizer(aspects, ", \t\n\r\f"); |
| 1475 | 0 | while (tokens.hasMoreTokens()) { |
| 1476 | 0 | final String aspect = tokens.nextToken().trim(); |
| 1477 | 0 | final boolean all = aspect.equalsIgnoreCase("all"); |
| 1478 | 0 | if (all || aspect.equalsIgnoreCase("plugins")) { |
| 1479 | 0 | AuthorityFactoryFinder.scanForPlugins(); |
| 1480 | 0 | standardFactory = null; |
| 1481 | 0 | xyFactory = null; |
| 1482 | 0 | strictFactory = null; |
| 1483 | 0 | lenientFactory = null; |
| 1484 | } | |
| 1485 | 0 | } |
| 1486 | 0 | } |
| 1487 | } |