All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.referencing.CRS Maven / Gradle / Ivy

Go to download

Implementations of Coordinate Reference Systems (CRS), conversion and transformation services derived from ISO 19111.

There is a newer version: 3.20-geoapi-3.0
Show newest version
/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2005-2011, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2011, Geomatys
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotoolkit.referencing;

import java.util.Set;
import java.util.Map;
import java.util.List;
import java.util.HashSet;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.NoSuchElementException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.AffineTransform;
import java.awt.RenderingHints;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.opengis.geometry.*;
import org.opengis.referencing.*;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.datum.*;
import org.opengis.referencing.operation.*;
import org.opengis.metadata.extent.*;
import org.opengis.util.FactoryException;

import org.geotoolkit.lang.Static;
import org.geotoolkit.util.Version;
import org.geotoolkit.util.Utilities;
import org.geotoolkit.util.ComparisonMode;
import org.geotoolkit.util.logging.Logging;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.factory.Factory;
import org.geotoolkit.factory.Factories;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.factory.AuthorityFactoryFinder;
import org.geotoolkit.factory.FactoryNotFoundException;
import org.geotoolkit.factory.FactoryRegistryException;
import org.geotoolkit.geometry.Envelopes;
import org.geotoolkit.geometry.GeneralEnvelope;
import org.geotoolkit.metadata.iso.extent.DefaultGeographicBoundingBox;
import org.geotoolkit.referencing.crs.DefaultCompoundCRS;
import org.geotoolkit.referencing.crs.DefaultVerticalCRS;
import org.geotoolkit.referencing.crs.DefaultGeographicCRS;
import org.geotoolkit.referencing.cs.DefaultEllipsoidalCS;
import org.geotoolkit.referencing.cs.DefaultCoordinateSystemAxis;
import org.geotoolkit.referencing.operation.projection.UnitaryProjection;
import org.geotoolkit.referencing.operation.transform.IdentityTransform;
import org.geotoolkit.internal.referencing.CRSUtilities;
import org.geotoolkit.resources.Errors;

import static org.geotoolkit.util.ArgumentChecks.ensureNonNull;


/**
 * Utility class for making use of the {@linkplain CoordinateReferenceSystem Coordinate Reference
 * System} and associated {@linkplain org.opengis.util.Factory} implementations. This utility class
 * is made up of static functions working with arbitrary implementations of GeoAPI interfaces.
 * 

* The methods defined in this class can be grouped in three categories: *

*

    *
  • Methods working with factories, like {@link #decode(String)}.
  • *
  • Methods providing informations, like {@link #isHorizontalCRS(CoordinateReferenceSystem)}.
  • *
  • Methods performing coordinate transformations, like {@link #transform(CoordinateOperation,Envelope)} * Note that many of those methods are also defined in the {@link Envelopes} class.
  • *
* * @author Martin Desruisseaux (IRD, Geomatys) * @author Jody Garnett (Refractions) * @author Andrea Aime (TOPP) * @version 3.19 * * @see IdentifiedObjects * @see Envelopes * * @since 2.1 * @module */ public final class CRS extends Static { /** * The CRS factory to use for parsing WKT. Will be fetched when first needed * are stored for avoiding indirect synchronization lock in {@link #parseWKT}. */ private static volatile CRSFactory crsFactory; /** * A factory for CRS creation as specified by the authority, which may have * (latitude, longitude) axis order. Will be created * only when first needed. */ private static volatile CRSAuthorityFactory standardFactory; /** * A factory for CRS creation with (longitude, latitude) axis order. * Will be created only when first needed. */ private static volatile CRSAuthorityFactory xyFactory; /** * A factory for default (non-lenient) operations. */ private static volatile CoordinateOperationFactory strictFactory; /** * A factory for default lenient operations. */ private static volatile CoordinateOperationFactory lenientFactory; /** * The default value for {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER}, * or {@code null} if not yet determined. */ private static volatile Boolean defaultOrder; /** * The default value for {@link Hints#LENIENT_DATUM_SHIFT}, * or {@code null} if not yet determined. */ private static volatile Boolean defaultLenient; /** * Registers a listener automatically invoked when the system-wide configuration changed. */ static { Factories.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { synchronized (CRS.class) { crsFactory = null; standardFactory = null; xyFactory = null; strictFactory = null; lenientFactory = null; defaultOrder = null; defaultLenient = null; } } }); } /** * Do not allow instantiation of this class. */ private CRS() { } ////////////////////////////////////////////////////////////// //// //// //// FACTORIES, CRS CREATION AND INSPECTION //// //// //// ////////////////////////////////////////////////////////////// /** * Returns the CRS factory. This is used mostly for WKT parsing. */ private static CRSFactory getCRSFactory() { CRSFactory factory = crsFactory; if (factory == null) { synchronized (CRS.class) { // Double-checked locking - was a deprecated practice before Java 5. // Is okay since Java 5 provided that the variable is volatile. factory = crsFactory; if (factory == null) { crsFactory = factory = FactoryFinder.getCRSFactory(null); } } } return factory; } /** * Returns the CRS authority factory used by the {@link #decode(String,boolean) decode} methods. * This factory {@linkplain org.geotoolkit.referencing.factory.CachingAuthorityFactory uses a cache}, * scans over {@linkplain org.geotoolkit.referencing.factory.AllAuthoritiesFactory all factories} and * uses additional factories as {@linkplain org.geotoolkit.referencing.factory.FallbackAuthorityFactory * fallbacks} if there is more than one {@linkplain AuthorityFactoryFinder#getCRSAuthorityFactories * registered factory} for the same authority. *

* This factory can be used as a kind of system-wide factory for all authorities. * However for more determinist behavior, consider using a more specific factory (as returned * by {@link AuthorityFactoryFinder#getCRSAuthorityFactory}) when the authority is known. * * @param longitudeFirst {@code true} if axis order should be forced to * (longitude, latitude), {@code false} if no order should be * forced (i.e. the standard specified by the authority is respected), or {@code null} * for the {@linkplain Hints#getSystemDefault system default}. * @return The CRS authority factory. * @throws FactoryRegistryException if the factory can't be created. * * @see Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER * * @category factory * @since 2.3 */ public static CRSAuthorityFactory getAuthorityFactory(Boolean longitudeFirst) throws FactoryRegistryException { // No need to synchronize; this is not a big deal if 'defaultOrder' is computed twice. if (longitudeFirst == null) { longitudeFirst = defaultOrder; if (longitudeFirst == null) { longitudeFirst = Boolean.TRUE.equals(Hints.getSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER)); defaultOrder = longitudeFirst; } } CRSAuthorityFactory factory = (longitudeFirst) ? xyFactory : standardFactory; if (factory == null) synchronized (CRS.class) { // Double-checked locking - was a deprecated practice before Java 5. // Is okay since Java 5 provided that the variables are volatile. factory = (longitudeFirst) ? xyFactory : standardFactory; if (factory == null) try { factory = DefaultAuthorityFactory.create(longitudeFirst); if (longitudeFirst) { xyFactory = factory; } else { standardFactory = factory; } } catch (NoSuchElementException exception) { // No factory registered in FactoryFinder. throw new FactoryNotFoundException(null, exception); } } return factory; } /** * Returns the coordinate operation factory used by * {@link #findMathTransform(CoordinateReferenceSystem, CoordinateReferenceSystem) * findMathTransform} convenience methods. * * @param lenient {@code true} if the coordinate operations should be created * even when there is no information available for a datum shift. * @return The coordinate operation factory used for finding math transform in this class. * * @see Hints#LENIENT_DATUM_SHIFT * * @category factory * @since 2.4 */ public static CoordinateOperationFactory getCoordinateOperationFactory(final boolean lenient) { CoordinateOperationFactory factory = (lenient) ? lenientFactory : strictFactory; if (factory == null) synchronized (CRS.class) { // Double-checked locking - was a deprecated practice before Java 5. // Is okay since Java 5 provided that the variables are volatile. factory = (lenient) ? lenientFactory : strictFactory; if (factory == null) { final Hints hints = new Hints(); // Get the system-width default hints. if (lenient) { hints.put(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE); } factory = FactoryFinder.getCoordinateOperationFactory(hints); if (lenient) { lenientFactory = factory; } else { strictFactory = factory; } } } return factory; } /** * Returns the version number of the specified authority database, or {@code null} if * not available. * * @param authority The authority name (typically {@code "EPSG"}). * @return The version number of the authority database, or {@code null} if unknown. * @throws FactoryRegistryException if no {@link CRSAuthorityFactory} implementation * was found for the specified authority. * * @see Hints#VERSION * * @category factory * @since 2.4 */ public static Version getVersion(final String authority) throws FactoryRegistryException { ensureNonNull("authority", authority); Object candidate = AuthorityFactoryFinder.getCRSAuthorityFactory(authority, null); final Set guard = new HashSet(); while (candidate instanceof Factory) { final Factory factory = (Factory) candidate; if (!guard.add(factory)) { break; // Safety against never-ending recursivity. } final Map hints = factory.getImplementationHints(); final Object version = hints.get(Hints.VERSION); if (version instanceof Version) { return (Version) version; } candidate = hints.get(Hints.CRS_AUTHORITY_FACTORY); } return null; } /** * Gets the list of the codes that are supported by the given authority. For example * {@code getSupportedCodes("EPSG")} may returns {@code "EPSG:2000"}, {@code "EPSG:2001"}, * {@code "EPSG:2002"}, etc. It may also returns {@code "2000"}, {@code "2001"}, * {@code "2002"}, etc. without the {@code "EPSG:"} prefix. Whatever the authority * name is prefixed or not is factory implementation dependent. *

* If there is more than one factory for the given authority, then this method merges the * code set of all of them. If a factory fails to provide a set of supported code, then * this particular factory is ignored. Please be aware of the following potential issues: *

*

    *
  • If there is more than one EPSG databases (for example an Access and a PostgreSQL ones), * then this method will connect to all of them even if their content are identical.
  • * *
  • If two factories format their codes differently (e.g. {@code "4326"} and * {@code "EPSG:4326"}), then the returned set will contain a lot of synonymous * codes.
  • * *
  • For any code c in the returned set, there is no warranty that * {@linkplain #decode decode}(c) will use the same authority * factory than the one that formatted c.
  • * *
  • This method doesn't report connection problems since it doesn't throw any exception. * {@link FactoryException}s are logged as warnings and otherwise ignored.
  • *
*

* If a more determinist behavior is wanted, consider the code below instead. * The following code exploit only one factory, the "preferred" one. * * {@preformat java * factory = AuthorityFactoryFinder.getCRSAuthorityFactory(authority, null); * Set codes = factory.getAuthorityCodes(CoordinateReferenceSystem.class); * String code = ... // Choose a code here. * CoordinateReferenceSystem crs = factory.createCoordinateReferenceSystem(code); * } * * @param authority The authority name (for example {@code "EPSG"}). * @return The set of supported codes. May be empty, but never null. * * @see AuthorityFactory#getAuthorityCodes(Class) * @see List of authority codes * * @category factory */ public static Set getSupportedCodes(final String authority) { ensureNonNull("authority", authority); return DefaultAuthorityFactory.getSupportedCodes(authority); } /** * Returns the set of the authority identifiers supported by registered authority factories. * This method search only for {@linkplain CRSAuthorityFactory CRS authority factories}. * * @param returnAliases If {@code true}, the set will contain all identifiers for each * authority. If {@code false}, only the first one * @return The set of supported authorities. May be empty, but never null. * * @category factory * @since 2.3.1 */ public static Set getSupportedAuthorities(final boolean returnAliases) { return DefaultAuthorityFactory.getSupportedAuthorities(returnAliases); } /** * Returns a Coordinate Reference System for the specified code. * Note that the code needs to mention the authority. Examples: *

*

    *
  • {@code EPSG:4326}
  • *
  • {@code AUTO:42001,9001,0,30}
  • *
*

* If there is more than one factory implementation for the same authority, then all additional * factories are {@linkplain org.geotoolkit.referencing.factory.FallbackAuthorityFactory fallbacks} * to be used only when the first acceptable factory failed to create the requested CRS object. * * {@section Common codes} * A few commonly used codes are: *

*

    *
  • Geographic CRS: *
      *
    • WGS 84 (2D only): EPSG:4326
    • *
    • WGS 84 with ellipsoidal height: EPSG:4979
    • *
  • *
  • Simple projected CRS: *
      *
    • Mercator: 3395
    • *
  • *
  • Universal Transverse Mercator (UTM) projections: *
      *
    • WGS 84 (northern hemisphere): EPSG:32600 + zone
    • *
    • WGS 84 (southern hemisphere): EPSG:32700 + zone
    • *
    • WGS 72 (northern hemisphere): EPSG:32200 + zone
    • *
    • WGS 72 (southern hemisphere): EPSG:32300 + zone
    • *
    • NAD 83 (northern hemisphere): EPSG:26900 + zone (zone 1 to 23 only)
    • *
    • NAD 27 (northern hemisphere): EPSG:26700 + zone (zone 1 to 22 only)
    • *
  • *
* * {@section Caching} * CRS objects created by previous calls to this method are * {@linkplain org.geotoolkit.referencing.factory.CachingAuthorityFactory cached} * using {@linkplain java.lang.ref.WeakReference weak references}. Subsequent calls to this * method with the same authority code should be fast, unless the CRS object has been garbage * collected. * * @param code The Coordinate Reference System authority code. * @return The Coordinate Reference System for the provided code. * @throws NoSuchAuthorityCodeException If the code could not be understood. * @throws FactoryException if the CRS creation failed for an other reason. * * @see #getSupportedCodes(String) * @see org.geotoolkit.measure.Units#valueOfEPSG(int) * @see List of authority codes * * @category factory */ public static CoordinateReferenceSystem decode(final String code) throws NoSuchAuthorityCodeException, FactoryException { ensureNonNull("code", code); return getAuthorityFactory(null).createCoordinateReferenceSystem(code); } /** * Returns a Coordinate Reference System for the specified code, maybe forcing the axis order * to (longitude, latitude). The {@code code} argument value is parsed * as in {@linkplain #decode(String) decode}(code). The {@code longitudeFirst} * argument is the value to be given to the {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER * FORCE_LONGITUDE_FIRST_AXIS_ORDER} hint. *

* Example: by default, {@code CRS.decode("EPSG:4326")} returns a Geographic CRS with * (latitude, longitude) axis order, while {@code CRS.decode("EPSG:4326", true)} * returns the same CRS except for axis order, which is (longitude, latitude). * * @param code The Coordinate Reference System authority code. * @param longitudeFirst {@code true} if axis order should be forced to * (longitude, latitude), {@code false} if no order should * be forced (i.e. the standard specified by the authority is respected). * @return The Coordinate Reference System for the provided code. * @throws NoSuchAuthorityCodeException If the code could not be understood. * @throws FactoryException if the CRS creation failed for an other reason. * * @see Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER * @see org.geotoolkit.referencing.factory.epsg.LongitudeFirstEpsgFactory * @see List of authority codes * * @category factory * @since 2.3 */ public static CoordinateReferenceSystem decode(String code, final boolean longitudeFirst) throws NoSuchAuthorityCodeException, FactoryException { ensureNonNull("code", code); return getAuthorityFactory(longitudeFirst).createCoordinateReferenceSystem(code); } /** * Parses a * Well * Known Text (WKT) into a CRS object. This convenience method is a * shorthand for the following: * * {@preformat java * FactoryFinder.getCRSFactory(null).createFromWKT(wkt); * } * * @param wkt The WKT string to parse. * @return The parsed coordinate reference system. * @throws FactoryException if the given WKT can't be parsed. * * @see Envelopes#parseWKT(String) * @see CoordinateReferenceSystem#toWKT() * * @category factory */ public static CoordinateReferenceSystem parseWKT(final String wkt) throws FactoryException { ensureNonNull("wkt", wkt); return getCRSFactory().createFromWKT(wkt); } /** * Returns the domain of validity for the specified coordinate reference system, * or {@code null} if unknown. The returned envelope is expressed in terms of the * specified CRS. *

* This method looks in two places: *

*

    *
  • First, it checks the {@linkplain CoordinateReferenceSystem#getDomainOfValidity domain * of validity} associated with the given CRS. Only {@linkplain GeographicExtent * geographic extents} of kind {@linkplain BoundingPolygon bounding polygon} are * taken in account.
  • *
  • If the above step does not found found any bounding polygon, then the * {@linkplain #getGeographicBoundingBox geographic bounding boxes} are * used as a fallback.
  • *
*

* Note that this method is also accessible from the {@link Envelopes} class. * * @param crs The coordinate reference system, or {@code null}. * @return The envelope in terms of the specified CRS, or {@code null} if none. * * @see #getGeographicBoundingBox(CoordinateReferenceSystem) * @see Envelopes#getDomainOfValidity(CoordinateReferenceSystem) * @see GeneralEnvelope#reduceToDomain(boolean) * * @category information * @since 2.2 */ public static Envelope getEnvelope(final CoordinateReferenceSystem crs) { Envelope envelope = null; GeneralEnvelope merged = null; if (crs != null) { final Extent domainOfValidity = crs.getDomainOfValidity(); if (domainOfValidity != null) { for (final GeographicExtent extent : domainOfValidity.getGeographicElements()) { if (Boolean.FALSE.equals(extent.getInclusion())) { continue; } } } } /* * If no envelope was found, uses the geographic bounding box as a fallback. We will * need to transform it from WGS84 to the supplied CRS. This step was not required in * the previous block because the later selected only envelopes in the right CRS. */ if (envelope == null) { final GeographicBoundingBox bounds = getGeographicBoundingBox(crs); if (bounds != null && !Boolean.FALSE.equals(bounds.getInclusion())) { envelope = merged = new GeneralEnvelope( new double[] {bounds.getWestBoundLongitude(), bounds.getSouthBoundLatitude()}, new double[] {bounds.getEastBoundLongitude(), bounds.getNorthBoundLatitude()}); /* * We do not assign WGS84 unconditionally to the geographic bounding box, because * it is not defined to be on a particular datum; it is only approximative bounds. * We try to get the GeographicCRS from the user-supplied CRS and fallback on WGS * 84 only if we found none. */ final SingleCRS targetCRS = getHorizontalCRS(crs); final GeographicCRS sourceCRS = CRSUtilities.getStandardGeographicCRS2D(targetCRS); merged.setCoordinateReferenceSystem(sourceCRS); try { envelope = transform(envelope, targetCRS); } catch (TransformException exception) { /* * The envelope is probably outside the range of validity for this CRS. * It should not occurs, since the envelope is supposed to describe the * CRS area of validity. Logs a warning and returns null, since it is a * legal return value according this method contract. */ envelope = null; unexpectedException("getEnvelope", exception); } /* * If transform(...) created a new envelope, its CRS is already targetCRS so it * doesn't matter if 'merged' is not anymore the right instance. If 'transform' * returned the envelope unchanged, the 'merged' reference still valid and we * want to ensure that it have the user-supplied CRS. */ merged.setCoordinateReferenceSystem(targetCRS); } } return envelope; } /** * Returns the valid geographic area for the specified coordinate reference system, * or {@code null} if unknown. * * This method fetches the {@linkplain CoordinateReferenceSystem#getDomainOfValidity domain * of validity} associated with the given CRS. Only {@linkplain GeographicExtent geographic * extents} of kind {@linkplain GeographicBoundingBox geographic bounding box} are taken in * account. * * @param crs The coordinate reference system, or {@code null}. * @return The geographic area, or {@code null} if none. * * @see #getEnvelope(CoordinateReferenceSystem) * * @category information * @since 2.3 */ public static GeographicBoundingBox getGeographicBoundingBox(final CoordinateReferenceSystem crs) { GeographicBoundingBox bounds = null; DefaultGeographicBoundingBox merged = null; if (crs != null) { final Extent domainOfValidity = crs.getDomainOfValidity(); if (domainOfValidity != null) { for (final GeographicExtent extent : domainOfValidity.getGeographicElements()) { if (extent instanceof GeographicBoundingBox) { final GeographicBoundingBox candidate = (GeographicBoundingBox) extent; if (bounds == null) { bounds = candidate; } else { if (merged == null) { bounds = merged = new DefaultGeographicBoundingBox(bounds); } merged.add(candidate); } } } } } return bounds; } /** * Returns {@code true} if the given CRS is horizontal. This method is provided because there is * a direct way to determine if a CRS is vertical or temporal, but no direct way to determine if * it is horizontal. So this method complements the check for spatio-temporal components as below: *

*

    *
  • {@code if (crs instanceof TemporalCRS)} determines if the CRS is for the temporal component.
  • *
  • {@code if (crs instanceof VerticalCRS)} determines if the CRS is for the vertical component.
  • *
  • {@code if (CRS.isHorizontalCRS(crs))} determines if the CRS is for the horizontal component.
  • *
*

* This method considers a CRS as horizontal if it is two-dimensional and comply * with one of the following conditions: *

*

    *
  • It is an instance of {@link GeographicCRS}.
  • *
  • It is an instance of {@link ProjectedCRS} (actually this is not explicitly * checked, since this condition is a special case of the condition below).
  • *
  • It is an instance of {@link GeneralDerivedCRS} based on a horizontal CRS * and using a {@link GeodeticDatum}.
  • *
*

* The last condition ({@code GeneralDerivedCRS} based on a horizontal CRS) allows for example * to express the coordinates of a projected CRS (which use a Cartesian coordinate system) in * a {@linkplain org.opengis.referencing.cs.PolarCS polar coordinate system} and still consider * the result as horizontal. However this assumes that the axes of the derived CRS are coplanar * with the axes of the base CRS. This is not always true since a derived CRS could be created * for an inclined plane, for example a plane fitting the slope of a mountain. ISO 19111 does * not specify how to handle this case. In the Geotk implementation, we suggest to define a new * {@linkplain Datum datum} for inclined plane which is not a geodetic datum. * * @param crs The coordinate reference system, or {@code null}. * @return {@code true} if the given CRS is non-null and comply with one of the above * conditions, or {@code false} otherwise. * * @see #getHorizontalCRS(CoordinateReferenceSystem) * * @category information * @since 3.05 */ public static boolean isHorizontalCRS(CoordinateReferenceSystem crs) { if (crs instanceof SingleCRS) { final int dimension = crs.getCoordinateSystem().getDimension(); if (dimension == 2) { final Datum datum = ((SingleCRS) crs).getDatum(); if (datum instanceof GeodeticDatum) { while (crs instanceof GeneralDerivedCRS) { crs = ((GeneralDerivedCRS) crs).getBaseCRS(); } return (crs instanceof GeographicCRS); } } } return false; } /** * Returns the first horizontal coordinate reference system found in the given CRS, * or {@code null} if there is none. A horizontal CRS is usually a two-dimensional * {@linkplain GeographicCRS geographic} or {@linkplain ProjectedCRS projected} CRS. * See the {@link #isHorizontalCRS(CoordinateReferenceSystem) isHorizontalCRS} method for * a more accurate description about the conditions for a CRS to be considered horizontal. * * @param crs The coordinate reference system, or {@code null}. * @return The horizontal CRS, or {@code null} if none. * * @category information * @since 2.4 */ public static SingleCRS getHorizontalCRS(final CoordinateReferenceSystem crs) { if (crs instanceof SingleCRS) { final CoordinateSystem cs = crs.getCoordinateSystem(); final int dimension = cs.getDimension(); if (dimension == 2) { /* * For two-dimensional CRS, returns the CRS directly if it is either a * GeographicCRS, or any kind of derived CRS having a GeographicCRS as * its base and a geodetic datum. */ final Datum datum = ((SingleCRS) crs).getDatum(); if (datum instanceof GeodeticDatum) { CoordinateReferenceSystem base = crs; while (base instanceof GeneralDerivedCRS) { base = ((GeneralDerivedCRS) base).getBaseCRS(); } // No need to test for ProjectedCRS, since the code above unwrap it. if (base instanceof GeographicCRS) { assert isHorizontalCRS(crs) : crs; return (SingleCRS) crs; // Really returns 'crs', not 'base'. } } } else if (dimension >= 3 && crs instanceof GeographicCRS) { /* * For three-dimensional Geographic CRS, extracts the axis having a direction * like "North", "North-East", "East", etc. If we find exactly two of them, * we can build a new GeographicCRS using them. */ CoordinateSystemAxis axis0 = null, axis1 = null; int count = 0; for (int i=0; i properties = CRSUtilities.changeDimensionInName(cs, "3D", "2D"); EllipsoidalCS horizontalCS; try { horizontalCS = FactoryFinder.getCSFactory(null). createEllipsoidalCS(properties, axis0, axis1); } catch (FactoryException e) { Logging.recoverableException(CRS.class, "getHorizontalCRS", e); horizontalCS = new DefaultEllipsoidalCS(properties, axis0, axis1); } properties = CRSUtilities.changeDimensionInName(crs, "3D", "2D"); GeographicCRS horizontalCRS; try { horizontalCRS = getCRSFactory().createGeographicCRS(properties, datum, horizontalCS); } catch (FactoryException e) { Logging.recoverableException(CRS.class, "getHorizontalCRS", e); horizontalCRS = new DefaultGeographicCRS(properties, datum, horizontalCS); } assert isHorizontalCRS(horizontalCRS) : horizontalCRS; return horizontalCRS; } } } if (crs instanceof CompoundCRS) { final CompoundCRS cp = (CompoundCRS) crs; for (final CoordinateReferenceSystem c : cp.getComponents()) { final SingleCRS candidate = getHorizontalCRS(c); if (candidate != null) { assert isHorizontalCRS(candidate) : candidate; return candidate; } } } return null; } /** * Returns the first projected coordinate reference system found in a the given CRS, * or {@code null} if there is none. * * @param crs The coordinate reference system, or {@code null}. * @return The projected CRS, or {@code null} if none. * * @category information * @since 2.4 */ public static ProjectedCRS getProjectedCRS(final CoordinateReferenceSystem crs) { if (crs instanceof ProjectedCRS) { return (ProjectedCRS) crs; } if (crs instanceof CompoundCRS) { final CompoundCRS cp = (CompoundCRS) crs; for (final CoordinateReferenceSystem c : cp.getComponents()) { final ProjectedCRS candidate = getProjectedCRS(c); if (candidate != null) { return candidate; } } } return null; } /** * Returns the first vertical coordinate reference system found in a the given CRS, * or {@code null} if there is none. * * @param crs The coordinate reference system, or {@code null}. * @return The vertical CRS, or {@code null} if none. * * @category information * @since 2.4 */ public static VerticalCRS getVerticalCRS(final CoordinateReferenceSystem crs) { if (crs instanceof VerticalCRS) { return (VerticalCRS) crs; } if (crs instanceof CompoundCRS) { final CompoundCRS cp = (CompoundCRS) crs; for (final CoordinateReferenceSystem c : cp.getComponents()) { final VerticalCRS candidate = getVerticalCRS(c); if (candidate != null) { return candidate; } } } if (crs instanceof GeographicCRS) { final CoordinateSystem cs = crs.getCoordinateSystem(); if (cs.getDimension() >= 3) { assert CRSUtilities.dimensionColinearWith(cs, DefaultCoordinateSystemAxis.ELLIPSOIDAL_HEIGHT) >= 0 : cs; return DefaultVerticalCRS.ELLIPSOIDAL_HEIGHT; } } return null; } /** * Returns the first temporal coordinate reference system found in the given CRS, * or {@code null} if there is none. * * @param crs The coordinate reference system, or {@code null}. * @return The temporal CRS, or {@code null} if none. * * @category information * @since 2.4 */ public static TemporalCRS getTemporalCRS(final CoordinateReferenceSystem crs) { if (crs instanceof TemporalCRS) { return (TemporalCRS) crs; } if (crs instanceof CompoundCRS) { final CompoundCRS cp = (CompoundCRS) crs; for (final CoordinateReferenceSystem c : cp.getComponents()) { final TemporalCRS candidate = getTemporalCRS(c); if (candidate != null) { return candidate; } } } return null; } /** * Returns the first compound CRS which contains only the given components, in any order. * First, this method gets the {@link SingleCRS} components of the given compound CRS. If * all those components are {@linkplain #equalsIgnoreMetadata equal, ignoring metadata} * and order, to the {@code SingleCRS} components given to this method, then the given * {@code CompoundCRS} is returned. Otherwise if the given {@code CompoundCRS} contains * nested {@code CompoundCRS}, then those nested CRS are inspected recursively by the same * algorithm. Otherwise, this method returns {@code null}. *

* This method is useful for extracting metadata about the 3D spatial CRS part in a 4D * spatio-temporal CRS. For example given the following CRS: * * {@preformat wkt * COMPD_CS["Mercator + height + time", * COMPD_CS["Mercator + height", * PROJCS["Mercator", ...etc...] * VERT_CS["Ellipsoidal height", ...etc...]] * TemporalCRS["Modified Julian", ...etc...]] * } * * Then the following code will returns the nested {@code COMPD_CS["Mercator + height"]} * without prior knowledge of the CRS component order (the time CRS could be first, and * the vertical CRS could be before the horizontal one): * * {@preformat java * CompoundCRS crs = ...; * SingleCRS horizontalCRS = getHorizontalCRS(crs); * VerticalCRS verticalCRS = getVerticalCRS(crs); * if (horizontalCRS != null && verticalCRS != null) { * CompoundCRS spatialCRS = getCompoundCRS(crs, horizontalCRS, verticalCRS); * if (spatialCRS != null) { * // ... * } * } * } * * @param crs The compound CRS to compare with the given component CRS, or {@code null}. * @param components The CRS which must be components of the returned CRS. * @return A CRS which contains the given components, or {@code null} if none. * * @see DefaultCompoundCRS#getSingleCRS() * * @since 3.16 */ public static CompoundCRS getCompoundCRS(final CompoundCRS crs, final SingleCRS... components) { final List actualComponents = DefaultCompoundCRS.getSingleCRS(crs); if (actualComponents.size() == components.length) { int firstValid = 0; final SingleCRS[] toSearch = components.clone(); compare: for (final SingleCRS component : actualComponents) { for (int i=firstValid; i *

    *
  • If the given {@code crs} is {@code null}, then this method returns {@code null}.
  • *
  • Otherwise if {@code lower} is 0 and {@code upper} if the number of CRS dimensions, * then this method returns the given CRS unchanged.
  • *
  • Otherwise if the given CRS is an instance of {@link CompoundCRS}, then this method * searches for a {@linkplain CompoundCRS#getComponents() component} where: *
      *
    • The {@linkplain CoordinateSystem#getDimension() number of dimensions} is * equals to {@code upper - lower};
    • *
    • The sum of the number of dimensions of all previous CRS is equals to * {@code lower}.
    • *
    * If such component is found, then it is returned.
  • *
  • Otherwise (i.e. no component match), this method returns {@code null}.
  • *
*

* This method does not attempt to build new CRS from the components. * For example it does not attempt to create a 3D geographic CRS from a 2D one + a vertical * component. If such functionality is desired, consider using the utility methods in * {@link org.geotoolkit.referencing.factory.ReferencingFactoryContainer} instead. * * @param crs The coordinate reference system to decompose, or {@code null}. * @param lower The first dimension to keep, inclusive. * @param upper The last dimension to keep, exclusive. * @return The sub-coordinate system, or {@code null} if the given {@code crs} was {@code null} * or can't be decomposed for dimensions in the range {@code [lower..upper]}. * @throws IndexOutOfBoundsException If the given index are out of bounds. * * @see org.geotoolkit.referencing.factory.ReferencingFactoryContainer#separate(CoordinateReferenceSystem, int[]) * * @since 3.16 */ public static CoordinateReferenceSystem getSubCRS(CoordinateReferenceSystem crs, int lower, int upper) { if (crs != null) { int dimension = crs.getCoordinateSystem().getDimension(); if (lower < 0 || lower > upper || upper > dimension) { throw new IndexOutOfBoundsException(Errors.format( Errors.Keys.INDEX_OUT_OF_BOUNDS_$1, lower < 0 ? lower : upper)); } while (lower != 0 || upper != dimension) { final List c = CRSUtilities.getComponents(crs); if (c == null) { return null; } for (final Iterator it=c.iterator(); it.hasNext();) { crs = it.next(); dimension = crs.getCoordinateSystem().getDimension(); if (lower < dimension) { break; } lower -= dimension; upper -= dimension; } } } return crs; } /** * Returns the datum of the specified CRS, or {@code null} if none. * This method processes as below: *

*

    *
  • If the given CRS is an instance of {@link SingleCRS}, then this method returns * crs.{@linkplain SingleCRS#getDatum() getDatum()}.
  • *
  • Otherwise if the given CRS is an instance of {@link CompoundCRS}, then: *
      *
    • If all components have the same datum, then that datum is returned.
    • *
    • Otherwise if the CRS contains only a geodetic datum with a vertical datum * of type ellipsoidal height (no other type accepted), then the * geodetic datum is returned.
    • *
  • *
  • Otherwise this method returns {@code null}.
  • *
* * @param crs The coordinate reference system for which to get the datum. May be {@code null}. * @return The datum in the given CRS, or {@code null} if none. * * @see #getEllipsoid(CoordinateReferenceSystem) * * @category information * @since 3.16 */ public static Datum getDatum(final CoordinateReferenceSystem crs) { return CRSUtilities.getDatum(crs); } /** * Returns the first ellipsoid found in a coordinate reference system, * or {@code null} if there is none. More specifically: *

*

    *
  • If the given CRS is an instance of {@link SingleCRS} and its datum is a * {@link GeodeticDatum}, then this method returns the datum ellipsoid.
  • *
  • Otherwise if the given CRS is an instance of {@link CompoundCRS}, then this method * invokes itself recursively for each component until a geodetic datum is found.
  • *
  • Otherwise this method returns {@code null}.
  • *
*

* Note that this method does not check if there is more than one ellipsoid * (it should never be the case). * * @param crs The coordinate reference system, or {@code null}. * @return The ellipsoid, or {@code null} if none. * * @see #getDatum(CoordinateReferenceSystem) * * @category information * @since 2.4 */ public static Ellipsoid getEllipsoid(final CoordinateReferenceSystem crs) { if (crs instanceof SingleCRS) { final Datum datum = ((SingleCRS) crs).getDatum(); if (datum instanceof GeodeticDatum) { return ((GeodeticDatum) datum).getEllipsoid(); } } if (crs instanceof CompoundCRS) { for (final CoordinateReferenceSystem c : ((CompoundCRS) crs).getComponents()) { final Ellipsoid candidate = getEllipsoid(c); if (candidate != null) { return candidate; } } } return null; } ///////////////////////////////////////////////// //// //// //// COORDINATE OPERATIONS //// //// //// ///////////////////////////////////////////////// /** * Compares the specified objects for equality, ignoring metadata. If this method returns * {@code true}, then: * *

    *
  • If the two given objects are {@link MathTransform} instances, then transforming * a set of coordinate values using one transform will produce the same results than * transforming the same coordinates with the other transform.

  • * *
  • If the two given objects are {@link CoordinateReferenceSystem} instances, * then a call to {@linkplain #findMathTransform(CoordinateReferenceSystem, * CoordinateReferenceSystem) findMathTransform}(crs1, crs2) will return * an identity transform.

  • *
* * If a more lenient comparison - allowing slight differences in numerical values - is wanted, * then {@link #equalsApproximatively(Object, Object)} can be used instead. * * {@section Implementation note} * This is a convenience method for the following method call: * * {@preformat java * return Utilities.deepEquals(object1, object2, ComparisonMode.IGNORE_METADATA); * } * * @param object1 The first object to compare (may be null). * @param object2 The second object to compare (may be null). * @return {@code true} if both objects are equal, ignoring metadata. * * @see Utilities#deepEquals(Object, Object, ComparisonMode) * @see ComparisonMode#IGNORE_METADATA * * @category information * @since 2.2 */ public static boolean equalsIgnoreMetadata(final Object object1, final Object object2) { return Utilities.deepEquals(object1, object2, ComparisonMode.IGNORE_METADATA); } /** * Compares the specified objects for equality, ignoring metadata and slight differences * in numerical values. If this method returns {@code true}, then: * *
    *
  • If the two given objects are {@link MathTransform} instances, then transforming a * set of coordinate values using one transform will produce approximatively * the same results than transforming the same coordinates with the other transform.

  • * *
  • If the two given objects are {@link CoordinateReferenceSystem} instances, * then a call to {@linkplain #findMathTransform(CoordinateReferenceSystem, * CoordinateReferenceSystem) findMathTransform}(crs1, crs2) will return * a transform close to the identity transform.

  • *
* * {@section Implementation note} * This is a convenience method for the following method call: * * {@preformat java * return Utilities.deepEquals(object1, object2, ComparisonMode.APPROXIMATIVE); * } * * @param object1 The first object to compare (may be null). * @param object2 The second object to compare (may be null). * @return {@code true} if both objects are approximatively equal. * * @see Utilities#deepEquals(Object, Object, ComparisonMode) * @see ComparisonMode#APPROXIMATIVE * * @category information * @since 3.18 */ public static boolean equalsApproximatively(final Object object1, final Object object2) { return Utilities.deepEquals(object1, object2, ComparisonMode.APPROXIMATIVE); } /** * Grabs a transform between two Coordinate Reference Systems. This convenience method is a * shorthand for the following: * * {@preformat java * CoordinateOperationFactory factory = FactoryFinder.getCoordinateOperationFactory(null); * CoordinateOperation operation = factory.createOperation(sourceCRS, targetCRS); * MathTransform transform = operation.getMathTransform(); * } * * Note that some metadata like {@linkplain CoordinateOperation#getCoordinateOperationAccuracy * coordinate operation accuracy} are lost by this method. If those metadata are wanted, use the * {@linkplain CoordinateOperationFactory coordinate operation factory} directly. *

* Sample use: * * {@preformat java * CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:42102"); * CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:4326"); * MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS); * } * * @param sourceCRS The source CRS. * @param targetCRS The target CRS. * @return The math transform from {@code sourceCRS} to {@code targetCRS}. * @throws FactoryException If no math transform can be created for the specified source and * target CRS. * * @see CoordinateOperationFactory#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem) * * @category transform */ public static MathTransform findMathTransform(final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS) throws FactoryException { // No need to synchronize; this is not a big deal if 'defaultLenient' is computed twice. Boolean lenient = defaultLenient; if (lenient == null) { defaultLenient = lenient = Boolean.TRUE.equals( Hints.getSystemDefault(Hints.LENIENT_DATUM_SHIFT)); } return findMathTransform(sourceCRS, targetCRS, lenient); } /** * Grab a transform between two Coordinate Reference Systems. This method is similar to * {@linkplain #findMathTransform(CoordinateReferenceSystem, CoordinateReferenceSystem) * findMathTransform}(sourceCRS, targetCRS), except that it specifies whatever this * method should tolerate lenient datum shift. If the {@code lenient} argument * is {@code true}, then this method will not throw a "Bursa-Wolf parameters required" * exception during datum shifts if the Bursa-Wolf parameters are not specified. * Instead it will assume a no datum shift. * * @param sourceCRS The source CRS. * @param targetCRS The target CRS. * @param lenient {@code true} if the math transform should be created even when there is * no information available for a datum shift. if this argument is not specified, * then the default value is determined from the {@linkplain Hints#getSystemDefault * system default}. * @return The math transform from {@code sourceCRS} to {@code targetCRS}. * @throws FactoryException If no math transform can be created for the specified source and * target CRS. * * @see Hints#LENIENT_DATUM_SHIFT * @see CoordinateOperationFactory#createOperation(CoordinateReferenceSystem, CoordinateReferenceSystem) * * @category transform */ public static MathTransform findMathTransform(final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS, boolean lenient) throws FactoryException { if (equalsIgnoreMetadata(sourceCRS, targetCRS)) { // Slight optimization in order to avoid the overhead of loading the full referencing engine. return IdentityTransform.create(sourceCRS.getCoordinateSystem().getDimension()); } ensureNonNull("sourceCRS", sourceCRS); ensureNonNull("targetCRS", targetCRS); CoordinateOperationFactory operationFactory = getCoordinateOperationFactory(lenient); return operationFactory.createOperation(sourceCRS, targetCRS).getMathTransform(); } // Note: the above 4 transform methods simply delegate their work to the Envelopes class. // We keep those methods mostly for historical reasons. Some Geotk code still reference // those methods instead than Envelopes. We do that when the CRS class is used anyway so // there is no advantage to reference one more class. /** * Transforms the given envelope to the specified CRS. If the given envelope is null, or the * {@linkplain Envelope#getCoordinateReferenceSystem envelope CRS} is null, or the given * target CRS is null, or the transform {@linkplain MathTransform#isIdentity is identity}, * then the envelope is returned unchanged. Otherwise a new transformed envelope is returned. *

* See {@link Envelopes#transform(Envelope, CoordinateReferenceSystem)} for more information. * This method delegates its work to the above-cited {@code Envelopes} class and is defined * in this {@code CRS} class only for convenience. * * @param envelope The envelope to transform (may be {@code null}). * @param targetCRS The target CRS (may be {@code null}). * @return A new transformed envelope, or directly {@code envelope} * if no transformation was required. * @throws TransformException If a transformation was required and failed. * * @category transform * @since 2.5 */ // See the above comment about why some Geotk code still reference this method. public static Envelope transform(Envelope envelope, final CoordinateReferenceSystem targetCRS) throws TransformException { return Envelopes.transform(envelope, targetCRS); } /** * Transforms an envelope using the given {@linkplain MathTransform math transform}. * The transformation is only approximative: the returned envelope may be bigger than * necessary, or smaller than required if the bounding box contains a pole. *

* See {@link Envelopes#transform(MathTransform, Envelope)} for more information. * This method delegates its work to the above-cited {@code Envelopes} class and * is defined in this {@code CRS} class only for convenience. * * @param transform The transform to use. * @param envelope Envelope to transform, or {@code null}. This envelope will not be modified. * @return The transformed envelope, or {@code null} if {@code envelope} was null. * @throws TransformException if a transform failed. * * @category transform * @since 2.4 */ // See the above comment about why some Geotk code still reference this method. public static GeneralEnvelope transform(final MathTransform transform, final Envelope envelope) throws TransformException { return Envelopes.transform(transform, envelope); } /** * Transforms an envelope using the given {@linkplain CoordinateOperation coordinate operation}. * The transformation is only approximative: the returned envelope may be bigger than the * smallest possible bounding box, but should not be smaller in most cases. *

* See {@link Envelopes#transform(CoordinateOperation, Envelope)} for more information. * This method delegates its work to the above-cited {@code Envelopes} class and is defined * in this {@code CRS} class only for convenience. * * @param operation The operation to use. * @param envelope Envelope to transform, or {@code null}. This envelope will not be modified. * @return The transformed envelope, or {@code null} if {@code envelope} was null. * @throws TransformException if a transform failed. * * @category transform * @since 2.4 */ // See the above comment about why some Geotk code still reference this method. public static GeneralEnvelope transform(final CoordinateOperation operation, Envelope envelope) throws TransformException { return Envelopes.transform(operation, envelope); } /** * Transforms a rectangular envelope using the given {@linkplain MathTransform math transform}. * The transformation is only approximative: the returned envelope may be bigger than * necessary, or smaller than required if the bounding box contains a pole. *

* See {@link Envelopes#transform(MathTransform2D, Rectangle2D, Rectangle2D)} for more * information. This method delegates its work to the above-cited {@code Envelopes} class * and is defined in this {@code CRS} class only for convenience. * * @param transform The transform to use. Source and target dimension must be 2. * @param envelope The rectangle to transform (may be {@code null}). * @param destination The destination rectangle (may be {@code envelope}). * If {@code null}, a new rectangle will be created and returned. * @return {@code destination}, or a new rectangle if {@code destination} was non-null * and {@code envelope} was null. * @throws TransformException if a transform failed. * * @category transform * @since 2.4 */ // See the above comment about why some Geotk code still reference this method. public static Rectangle2D transform(final MathTransform2D transform, final Rectangle2D envelope, Rectangle2D destination) throws TransformException { return Envelopes.transform(transform, envelope, destination); } /** * Transforms a rectangular envelope using the given {@linkplain CoordinateOperation coordinate * operation}. The transformation is only approximative: the returned envelope may be bigger * than the smallest possible bounding box, but should not be smaller in most cases. *

* See {@link Envelopes#transform(CoordinateOperation, Rectangle2D, Rectangle2D)} for more * information. This method delegates its work to the above-cited {@code Envelopes} class * and is defined in this {@code CRS} class only for convenience. * * @param operation The operation to use. Source and target dimension must be 2. * @param envelope The rectangle to transform (may be {@code null}). * @param destination The destination rectangle (may be {@code envelope}). * If {@code null}, a new rectangle will be created and returned. * @return {@code destination}, or a new rectangle if {@code destination} was non-null * and {@code envelope} was null. * @throws TransformException if a transform failed. * * @category transform * @since 2.4 */ // See the above comment about why some Geotk code still reference this method. public static Rectangle2D transform(final CoordinateOperation operation, final Rectangle2D envelope, Rectangle2D destination) throws TransformException { return Envelopes.transform(operation, envelope, destination); } /** * Transforms the given relative distance using the given transform. A relative distance * vector is transformed without applying the translation components. However it needs to * be computed at a particular location, given by the {@code origin} parameter in units * of the source CRS. * * @param transform The transformation to apply. * @param origin The position where to compute the delta transform in the source CRS. * @param vector The distance vector to be delta transformed. * @return The result of the delta transformation. * @throws TransformException if the transformation failed. * * @see AffineTransform#deltaTransform(Point2D, Point2D) * * @since 3.10 (derived from 2.3) */ public static double[] deltaTransform(final MathTransform transform, final DirectPosition origin, final double... vector) throws TransformException { ensureNonNull("transform", transform); final int sourceDim = transform.getSourceDimensions(); final int targetDim = transform.getTargetDimensions(); final double[] result = new double[targetDim]; if (vector.length != sourceDim) { throw new IllegalArgumentException(Errors.format(Errors.Keys.MISMATCHED_DIMENSION_$3, "vector", vector.length, sourceDim)); } if (transform instanceof AffineTransform) { ((AffineTransform) transform).deltaTransform(vector, 0, result, 0, 1); } else { /* * If the optimized case in the previous "if" statement can't be used, * use a more generic (but more costly) algorithm. */ final double[] coordinates = new double[2 * Math.max(sourceDim, targetDim)]; for (int i=0; imust have a reasonable fallback (otherwise it * should propagate the exception). */ static void unexpectedException(final String methodName, final Exception exception) { Logging.unexpectedException(CRS.class, methodName, exception); } /** * Resets some aspects of the referencing system. The aspects to be reset are specified by * a space or comma delimited string, which may include any of the following elements: *

*

    *
  • {@code "plugins"} for {@linkplain AuthorityFactoryFinder#scanForPlugins searching * the classpath for new plugins}.
  • *
  • {@code "warnings"} for {@linkplain UnitaryProjection#resetWarnings re-enabling the * warnings to be issued when a coordinate is out of geographic valid area}.
  • *
* * @param aspects The aspects to reset, or {@code "all"} for all of them. * Unknown aspects are silently ignored. * * @since 2.5 */ public static synchronized void reset(final String aspects) { ensureNonNull("aspects", aspects); final StringTokenizer tokens = new StringTokenizer(aspects, ", \t\n\r\f"); while (tokens.hasMoreTokens()) { final String aspect = tokens.nextToken().trim(); final boolean all = aspect.equalsIgnoreCase("all"); if (all || aspect.equalsIgnoreCase("plugins")) { AuthorityFactoryFinder.scanForPlugins(); standardFactory = null; xyFactory = null; strictFactory = null; lenientFactory = null; } if (all || aspect.equalsIgnoreCase("warnings")) { UnitaryProjection.resetWarnings(); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy