+ * + * + * + * + * + * + * + *
Transverse Mercator parameters
Parameter name Value
Latitude of natural origin Given latitude, or 0° if UTM projection
Longitude of natural origin Given longitude, optionally snapped to a UTM central meridian
Scale factor at natural origin 0.9996
False easting 500000 metres
False northing 0 (North hemisphere) or 10000000 (South hemisphere) metres
+ * + * @param group The parameters for which to set the values. + * @param isUTM {@code true} for Universal Transverse Mercator (UTM) projection. + * @param latitude The latitude in the center of the desired projection. + * @param longitude The longitude in the center of the desired projection. + * @return A name like "Transverse Mercator" or "UTM zone 10N", + * depending on the arguments given to this method. + * + * @since 0.7 + */ + public static String setParameters(final ParameterValueGroup group, + final boolean isUTM, double latitude, double longitude) + { + final boolean isSouth = MathFunctions.isNegative(latitude); + int zone = zone(longitude); + if (isUTM) { + latitude = 0; + longitude = centralMeridian(zone); + } else if (longitude != centralMeridian(zone)) { + zone = 0; + } + String name = NAME; + if (zone != 0) { + name = "UTM zone " + zone + (isSouth ? 'S' : 'N'); + } + group.parameter(Constants.LATITUDE_OF_ORIGIN).setValue(latitude, NonSI.DEGREE_ANGLE); + group.parameter(Constants.CENTRAL_MERIDIAN) .setValue(longitude, NonSI.DEGREE_ANGLE); + group.parameter(Constants.SCALE_FACTOR) .setValue(0.9996, Unit.ONE); + group.parameter(Constants.FALSE_EASTING) .setValue(500000, SI.METRE); + group.parameter(Constants.FALSE_NORTHING) .setValue(isSouth ? 10000000 : 0, SI.METRE); + return name; + } + + /** + * Computes the UTM zone from a meridian in the zone. + * + * @param longitude A meridian inside the desired zone, in degrees relative to Greenwich. + * Positive longitudes are toward east, and negative longitudes toward west. + * @return The UTM zone number numbered from 1 to 60 inclusive, or 0 if the given central meridian was NaN. + * + * @since 0.7 + */ + public static int zone(double longitude) { + /* + * Casts to int are equivalent to Math.floor(double) for positive values, which is guaranteed + * to be the case here since we normalize the central meridian to the [MIN_VALUE … MAX_VALUE] range. + */ + double z = (longitude - Longitude.MIN_VALUE) / ZONE_WIDTH; // Zone number with fractional part. + z -= Math.floor(z / ((Longitude.MAX_VALUE - Longitude.MIN_VALUE) / ZONE_WIDTH)) // Roll in the [0 … 60) range. + * ((Longitude.MAX_VALUE - Longitude.MIN_VALUE) / ZONE_WIDTH); + return (int) (z + 1); // Cast only after addition in order to handle NaN as documented. + } + + /** + * Computes the central meridian of a given UTM zone. + * + * @param zone The UTM zone as a number in the [1 … 60] range. + * @return The central meridian of the given UTM zone. + * + * @since 0.7 + */ + public static double centralMeridian(final int zone) { + return (zone - 0.5) * ZONE_WIDTH + Longitude.MIN_VALUE; + } } Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java?rev=1724476&r1=1724475&r2=1724476&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] Wed Jan 13 18:31:16 2016 @@ -18,6 +18,8 @@ package org.apache.sis.referencing; import java.util.Map; import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; import javax.measure.unit.SI; import javax.measure.unit.NonSI; import javax.measure.unit.Unit; @@ -32,7 +34,9 @@ import org.opengis.referencing.crs.Verti import org.opengis.referencing.crs.TemporalCRS; import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.crs.GeocentricCRS; +import org.opengis.referencing.crs.ProjectedCRS; import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.crs.CRSAuthorityFactory; import org.opengis.referencing.cs.TimeCS; import org.opengis.referencing.cs.VerticalCS; import org.opengis.referencing.cs.CartesianCS; @@ -45,7 +49,6 @@ import org.opengis.referencing.datum.Ver import org.opengis.referencing.datum.VerticalDatumType; import org.opengis.referencing.datum.TemporalDatum; import org.opengis.referencing.datum.DatumAuthorityFactory; -import org.opengis.referencing.crs.CRSAuthorityFactory; import org.apache.sis.referencing.datum.DefaultVerticalDatum; import org.apache.sis.referencing.datum.DefaultTemporalDatum; import org.apache.sis.referencing.cs.AxesConvention; @@ -56,12 +59,17 @@ import org.apache.sis.referencing.crs.De import org.apache.sis.referencing.crs.DefaultVerticalCRS; import org.apache.sis.referencing.crs.DefaultGeographicCRS; import org.apache.sis.referencing.crs.DefaultGeocentricCRS; +import org.apache.sis.internal.referencing.provider.TransverseMercator; +import org.apache.sis.internal.referencing.Formulas; import org.apache.sis.internal.system.SystemListener; import org.apache.sis.internal.system.Modules; import org.apache.sis.internal.system.Loggers; import org.apache.sis.util.resources.Vocabulary; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.logging.Logging; +import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.math.MathFunctions; +import org.apache.sis.measure.Latitude; import org.apache.sis.measure.Units; import static java.util.Collections.singletonMap; @@ -72,6 +80,9 @@ import static org.apache.sis.internal.ut import static org.apache.sis.internal.util.Constants.CRS83; import static org.apache.sis.internal.util.Constants.CRS84; +// Branch-dependent imports +import org.apache.sis.internal.jdk8.JDK8; + /** * Frequently-used geodetic CRS and datum that are guaranteed to be available in SIS. @@ -115,7 +126,7 @@ import static org.apache.sis.internal.ut * * @author Martin Desruisseaux (Geomatys) * @since 0.4 - * @version 0.5 + * @version 0.7 * @module * * @see org.apache.sis.referencing.factory.CommonAuthorityFactory @@ -136,9 +147,11 @@ public enum CommonCRS { * Semi-minor axis length: 6356752 (approximative) * Inverse flattening: 298.257223563 (definitive) * Ellipsoid axes unit: {@link SI#METRE} + * UTM zones: 1 to 60 in North and South hemispheres * */ - WGS84((short) 4326, (short) 4979, (short) 4978, (short) 6326, (short) 7030), + WGS84((short) 4326, (short) 4979, (short) 4978, (short) 6326, (short) 7030, // Geodetic info + (short) 32600, (short) 32700, (byte) 1, (byte) 60), // UTM info /** * World Geodetic System 1972. @@ -152,9 +165,11 @@ public enum CommonCRS { * Semi-minor axis length: 6356751 (approximative) * Inverse flattening: 298.26 (definitive) * Ellipsoid axes unit: {@link SI#METRE} + * UTM zones: 1 to 60 in North and South hemispheres * */ - WGS72((short) 4322, (short) 4985, (short) 4984, (short) 6322, (short) 7043), + WGS72((short) 4322, (short) 4985, (short) 4984, (short) 6322, (short) 7043, // Geodetic info + (short) 32200, (short) 32300, (byte) 1, (byte) 60), // UTM info /** * North American Datum 1983. @@ -171,6 +186,7 @@ public enum CommonCRS { * Semi-minor axis length: 6356752 (approximative) * Inverse flattening: 298.257222101 (definitive) * Ellipsoid axes unit: {@link SI#METRE} + * UTM zones: 1 to 23 in the North hemisphere * * *
Note: @@ -178,7 +194,8 @@ public enum CommonCRS { * The Web Map Server {@code "CRS:83"} authority code uses the NAD83 datum, * while the {@code "IGNF:MILLER"} authority code uses the GRS80 datum.
*/ - NAD83((short) 4269, (short) 0, (short) 0, (short) 6269, (short) 7019), + NAD83((short) 4269, (short) 0, (short) 0, (short) 6269, (short) 7019, // Geodetic info + (short) 26900, (short) 0, (byte) 1, (byte) 23), // UTM info /** * North American Datum 1927. @@ -192,9 +209,11 @@ public enum CommonCRS { * Semi-major axis length: 6378206.4 * Semi-minor axis length: 6356583.8 (definitive) * Ellipsoid axes unit: {@link SI#METRE} + * UTM zones: 1 to 22 in the North hemisphere * */ - NAD27((short) 4267, (short) 0, (short) 0, (short) 6267, (short) 7008), + NAD27((short) 4267, (short) 0, (short) 0, (short) 6267, (short) 7008, // Geodetic info + (short) 26700, (short) 0, (byte) 1, (byte) 22), // UTM info /** * European Terrestrial Reference System 1989. @@ -210,6 +229,7 @@ public enum CommonCRS { * Semi-minor axis length: 6356752 (approximative) * Inverse flattening: 298.257222101 (definitive) * Ellipsoid axes unit: {@link SI#METRE} + * UTM zones: 28 to 38 in the North hemisphere * * *
Note: @@ -217,7 +237,8 @@ public enum CommonCRS { * The Web Map Server {@code "CRS:83"} authority code uses the NAD83 datum, * while the {@code "IGNF:MILLER"} authority code uses the GRS80 datum.
*/ - ETRS89((short) 4258, (short) 4937, (short) 4936, (short) 6258, (short) 7019), + ETRS89((short) 4258, (short) 4937, (short) 4936, (short) 6258, (short) 7019, // Geodetic info + (short) 25800, (short) 0, (byte) 28, (byte) 38), // UTM info /** * European Datum 1950. @@ -231,9 +252,11 @@ public enum CommonCRS { * Semi-minor axis length: 6356912 (approximative) * Inverse flattening: 297 (definitive) * Ellipsoid axes unit: {@link SI#METRE} + * UTM zones: 28 to 38 in the North hemisphere * */ - ED50((short) 4230, (short) 0, (short) 0, (short) 6230, (short) 7022), + ED50((short) 4230, (short) 0, (short) 0, (short) 6230, (short) 7022, // Geodetic info + (short) 23000, (short) 0, (byte) 28, (byte) 38), // UTM info /** * Unspecified datum based upon the GRS 1980 Authalic Sphere. Spheres use a simpler algorithm for @@ -251,7 +274,8 @@ public enum CommonCRS { * * @see org.apache.sis.referencing.datum.DefaultEllipsoid#getAuthalicRadius() */ - SPHERE((short) 4047, (short) 0, (short) 0, (short) 6047, (short) 7048); + SPHERE((short) 4047, (short) 0, (short) 0, (short) 6047, (short) 7048, // Geodetic info + (short) 0, (short) 0, (byte) 0, (byte) 0); // UTM info /** * The enum for the default CRS. @@ -288,6 +312,16 @@ public enum CommonCRS { final short ellipsoid; /** + * EPSG codes of pseudo "UTM zone zero" (North case and South case), or 0 if none. + */ + final short northUTM, southUTM; + + /** + * Zone number of the first UTM and last UTM zone defined in the EPSG database, inclusive. + */ + final byte firstZone, lastZone; + + /** * The cached object. This is initially {@code null}, then set to various kind of objects depending * on which method has been invoked. The kind of object stored in this field may change during the * application execution. @@ -316,6 +350,14 @@ public enum CommonCRS { private transient volatile GeocentricCRS cachedGeocentric; /** + * The Universal Transverse Mercator projections, created when first needed. + * All accesses to this map shall be synchronized on {@code cachedUTM}. + * + * @see #UTM(double, double) + */ + private final Map cachedUTM; + + /** * Creates a new constant for the given EPSG or SIS codes. * * @param geographic The EPSG code for the two-dimensional geographic CRS. @@ -324,14 +366,19 @@ public enum CommonCRS { * @param datum The EPSG code for the datum. * @param ellipsoid The EPSG code for the ellipsoid. */ - private CommonCRS(final short geographic, final short geo3D, final short geocentric, - final short datum, final short ellipsoid) + private CommonCRS(final short geographic, final short geo3D, final short geocentric, final short datum, final short ellipsoid, + final short northUTM, final short southUTM, final byte firstZone, final byte lastZone) { this.geographic = geographic; this.geocentric = geocentric; this.geo3D = geo3D; this.datum = datum; this.ellipsoid = ellipsoid; + this.northUTM = northUTM; + this.southUTM = southUTM; + this.firstZone = firstZone; + this.lastZone = lastZone; + cachedUTM = new HashMap<>(); } /** @@ -351,11 +398,15 @@ public enum CommonCRS { /** * Invoked by when the cache needs to be cleared after a classpath change. */ + @SuppressWarnings("NestedSynchronizedStatement") // Safe because cachedUTM never call any method of 'this'. synchronized void clear() { cached = null; cachedGeo3D = null; cachedNormalized = null; cachedGeocentric = null; + synchronized (cachedUTM) { + cachedUTM.clear(); + } } /** @@ -667,7 +718,7 @@ public enum CommonCRS { object = ellipsoid(cached); if (object == null) { if (this == NAD83) { - object = ETRS89.ellipsoid(); // Share the same instance for NAD83 and ETRS89. + object = ETRS89.ellipsoid(); // Share the same instance for NAD83 and ETRS89. } else { final DatumAuthorityFactory factory = datumFactory(); if (factory != null) try { @@ -707,7 +758,7 @@ public enum CommonCRS { object = primeMeridian(cached); if (object == null) { if (this != DEFAULT) { - object = DEFAULT.primeMeridian(); // Share the same instance for all constants. + object = DEFAULT.primeMeridian(); // Share the same instance for all constants. } else { final DatumAuthorityFactory factory = datumFactory(); if (factory != null) try { @@ -767,6 +818,130 @@ public enum CommonCRS { return (datum != null) ? datum.getPrimeMeridian() : null; } + /* + * NOTE ABOUT MAP PROJECTION CONVENIENCE METHODS: + * There is no convenience method for projections other than UTM because this enumeration is not a + * factory for arbitrary CRS (the UTM projection has the advantage of being constrained to zones). + * World-wide projections like "WGS 84 / World Mercator" are not handled neither because they make + * sense only for some datum like WGS84 or WGS72. Application to more regional datum like NAD27 or + * ED50 would be more questionable. + */ + + /** + * Returns a Universal Transverse Mercator (UTM) projection for the zone containing the given point. + * There is a total of 120 UTM zones, with 60 zones in the North hemisphere and 60 zones in the South hemisphere. + * The projection zone is determined from the arguments as below: + * + *
+ *
• The sign of the latitude argument determines the hemisphere: + * North for positive latitudes (including positive zero) or + * South for negative latitudes (including negative zero). + * The latitude magnitude is ignored, except for ensuring that the latitude is inside the [-90 … 90]° range.
• + *
• The value of the longitude argument determines the 6°-width zone, + * numbered from 1 for the zone starting at 180°W up to 60 for the zone finishing at 180°E. + * Longitudes outside the [-180 … 180]° range will be rolled as needed before to compute the zone.
• + *
+ * + *
Warning: + * be aware of parameter order! For this method, latitude is first. + * This order is for consistency with the non-normalized {@linkplain #geographic() geographic} CRS + * of all items in this {@code CommonCRS} enumeration.
+ * + * The map projection uses the following parameters: + * + *
+ * + * + * + * + * + * + * + *
Universal Transverse Mercator (UTM) parameters
Parameter name Value
Latitude of natural origin
Longitude of natural origin Central meridian of the UTM zone containing the given longitude
Scale factor at natural origin 0.9996
False easting 500000 metres
False northing 0 (North hemisphere) or 10000000 (South hemisphere) metres
Recognized Coordinate Reference System codes
NamespaceCodeNameDatum typeAxis directionUnits
* *
Note on codes in CRS namespace
- * The format is usually "CRS:n" where n is a number like 27, 83 or 84. + * The format is usually "{@code CRS:}n" where n is a number like 27, 83 or 84. * However this factory is lenient and allows the {@code CRS} part to be repeated as in {@code "CRS:CRS84"}. * It also accepts {@code "OGC"} as a synonymous of the {@code "CRS"} namespace. * *
Examples: * {@code "CRS:27"}, {@code "CRS:83"}, {@code "CRS:84"}, {@code "CRS:CRS84"}, {@code "OGC:CRS84"}.
* + *
Note on codes in AUTO(2) namespace
+ * The format is usually "{@code AUTO2:}n,factor,λ₀,φ₀" + * where n is a number between 42001 and 42005 inclusive, factor is a conversion + * factor from the CRS units to metres (e.g. 0.3048 for a CRS with axes in feet) and (λ₀,φ₀) + * is the longitude and latitude of a point in the projection centre. + * + *
Examples: + * {@code "AUTO2:42001,1,-100,45"} identifies a Universal Transverse Mercator (UTM) projection + * for a zone containing the point at (45°N, 100°W).

Codes in the {@code "AUTO(2)"} namespace do not need parameters for this method. + * But if parameters are nevertheless specified, then they will be taken in account.

+ * + * + * + * + * + * + * + *
Examples
Argument value Return value
{@code CRS:84} WGS 84
{@code AUTO2:42001} WGS 84 / Auto UTM
{@code AUTO2:42001,1,-100,45} WGS 84 / UTM zone 47N
+ * + * @param code Value in the CRS or AUTO(2) code space. + * @return A description of the object. + * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found. + * @throws FactoryException if an error occurred while fetching the description. + */ + @Override + public InternationalString getDescriptionText(final String code) throws FactoryException { + final int s = skipNamespace(code) & ~LEGACY_MASK; + final String localCode = code.substring(s, CharSequences.skipTrailingWhitespaces(code, s, code.length())); + if (localCode.indexOf(SEPARATOR) < 0) { + /* + * For codes in the "AUTO(2)" namespace without parameters, we can not rely on the default implementation + * since it would fail to create the ProjectedCRS instance. Instead we return a generic description. + * Note that we do not execute this block if parametes were specified. If there is parameters, + * then we instead rely on the default implementation for a more accurate description text. + */ + final int codeValue; + try { + codeValue = Integer.parseInt(localCode); + } catch (NumberFormatException exception) { + throw noSuchAuthorityCode(localCode, code, exception); + } + final int i = codeValue - FIRST_PROJECTION_CODE; + if (i >= 0 && i < PROJECTION_NAMES.length) { + return new SimpleInternationalString(PROJECTION_NAMES[i]); + } + } + return new SimpleInternationalString(createCoordinateReferenceSystem(localCode).getName().getCode()); + } + + /** * Creates an object from the specified code. * The default implementation delegates to {@link #createCoordinateReferenceSystem(String)}. * @@ -216,41 +430,223 @@ public class CommonAuthorityFactory exte /** * Creates a coordinate reference system from the specified code. + * This method performs the following steps: * + *
+ *
1. Skip the {@code "OGC"}, {@code "CRS"}, {@code "AUTO"}, {@code "AUTO1"} or {@code "AUTO2"} namespace + * if present (ignoring case). All other namespaces will cause an exception to be thrown.
2. + *
3. Skip the {@code "CRS"} prefix if present. This additional check is for accepting codes like + * {@code "OGC:CRS84"} (not a valid CRS code, but seen in practice).
4. + *
5. In the remaining text, interpret the integer value as documented in this class javadoc. + * Note that some codes require coma-separated parameters after the integer value.
6. + *