sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1785905 [3/5] - in /sis/trunk: ./ application/sis-console/src/main/java/org/apache/sis/console/ core/ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ core/sis-metadat...
Date Tue, 07 Mar 2017 23:37:50 GMT
Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -19,26 +19,29 @@ package org.apache.sis.referencing;
 import java.util.Map;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import javax.measure.Unit;
 import javax.measure.quantity.Time;
+import org.opengis.metadata.Identifier;
 import org.opengis.util.FactoryException;
 import org.opengis.util.InternationalString;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.VerticalCRS;
 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.SingleCRS;
 import org.opengis.referencing.cs.TimeCS;
 import org.opengis.referencing.cs.VerticalCS;
 import org.opengis.referencing.cs.CartesianCS;
 import org.opengis.referencing.cs.SphericalCS;
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.datum.GeodeticDatum;
 import org.opengis.referencing.datum.PrimeMeridian;
@@ -66,9 +69,11 @@ import org.apache.sis.internal.system.Mo
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.util.Constants;
 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.util.Exceptions;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Units;
@@ -147,8 +152,8 @@ public enum CommonCRS {
      *   <tr><th>UTM zones:</th>               <td>1 to 60 in North and South hemispheres</td></tr>
      * </table></blockquote>
      */
-    WGS84((short) 4326, (short) 4979, (short) 4978, (short) 6326, (short) 7030,     // Geodetic info
-          (short) 32600, (short) 32700, (byte) 1, (byte) 60),                       // UTM info
+    WGS84((short) 4326, (short) 4979, (short) 4978, (short) 6326, (short) 7030,             // Geodetic info
+          (short) 5041, (short) 5042, (short) 32600, (short) 32700, (byte) 1, (byte) 60),   // UPS and UTM info
 
     /**
      * World Geodetic System 1972.
@@ -165,8 +170,8 @@ public enum CommonCRS {
      *   <tr><th>UTM zones:</th>               <td>1 to 60 in North and South hemispheres</td></tr>
      * </table></blockquote>
      */
-    WGS72((short) 4322, (short) 4985, (short) 4984, (short) 6322, (short) 7043,     // Geodetic info
-          (short) 32200, (short) 32300, (byte) 1, (byte) 60),                       // UTM info
+    WGS72((short) 4322, (short) 4985, (short) 4984, (short) 6322, (short) 7043,             // Geodetic info
+          (short) 0, (short) 0, (short) 32200, (short) 32300, (byte) 1, (byte) 60),         // UPS and UTM info
 
     /**
      * North American Datum 1983.
@@ -191,8 +196,8 @@ public enum CommonCRS {
      * The <cite>Web Map Server</cite> {@code "CRS:83"} authority code uses the NAD83 datum,
      * while the {@code "IGNF:MILLER"} authority code uses the GRS80 datum.</div>
      */
-    NAD83((short) 4269, (short) 0, (short) 0, (short) 6269, (short) 7019,           // Geodetic info
-          (short) 26900, (short) 0, (byte) 1, (byte) 23),                           // UTM info
+    NAD83((short) 4269, (short) 0, (short) 0, (short) 6269, (short) 7019,                   // Geodetic info
+          (short) 0, (short) 0, (short) 26900, (short) 0, (byte) 1, (byte) 23),             // UPS and UTM info
 
     /**
      * North American Datum 1927.
@@ -209,8 +214,8 @@ public enum CommonCRS {
      *   <tr><th>UTM zones:</th>               <td>1 to 22 in the North hemisphere</td></tr>
      * </table></blockquote>
      */
-    NAD27((short) 4267, (short) 0, (short) 0, (short) 6267, (short) 7008,           // Geodetic info
-          (short) 26700, (short) 0, (byte) 1, (byte) 22),                           // UTM info
+    NAD27((short) 4267, (short) 0, (short) 0, (short) 6267, (short) 7008,                   // Geodetic info
+          (short) 0, (short) 0, (short) 26700, (short) 0, (byte) 1, (byte) 22),             // UPS and UTM info
 
     /**
      * European Terrestrial Reference System 1989.
@@ -234,8 +239,8 @@ public enum CommonCRS {
      * The <cite>Web Map Server</cite> {@code "CRS:83"} authority code uses the NAD83 datum,
      * while the {@code "IGNF:MILLER"} authority code uses the GRS80 datum.</div>
      */
-    ETRS89((short) 4258, (short) 4937, (short) 4936, (short) 6258, (short) 7019,    // Geodetic info
-           (short) 25800, (short) 0, (byte) 28, (byte) 37),                         // UTM info
+    ETRS89((short) 4258, (short) 4937, (short) 4936, (short) 6258, (short) 7019,            // Geodetic info
+           (short) 0, (short) 0, (short) 25800, (short) 0, (byte) 28, (byte) 37),           // UPS and UTM info
 
     /**
      * European Datum 1950.
@@ -252,8 +257,8 @@ public enum CommonCRS {
      *   <tr><th>UTM zones:</th>               <td>28 to 38 in the North hemisphere</td></tr>
      * </table></blockquote>
      */
-    ED50((short) 4230, (short) 0, (short) 0, (short) 6230, (short) 7022,            // Geodetic info
-           (short) 23000, (short) 0, (byte) 28, (byte) 38),                         // UTM info
+    ED50((short) 4230, (short) 0, (short) 0, (short) 6230, (short) 7022,                    // Geodetic info
+         (short) 0, (short) 0, (short) 23000, (short) 0, (byte) 28, (byte) 38),             // UPS and UTM info
 
     /**
      * Unspecified datum based upon the GRS 1980 Authalic Sphere. Spheres use a simpler algorithm for
@@ -271,8 +276,8 @@ public enum CommonCRS {
      *
      * @see org.apache.sis.referencing.datum.DefaultEllipsoid#getAuthalicRadius()
      */
-    SPHERE((short) 4047, (short) 0, (short) 0, (short) 6047, (short) 7048,          // Geodetic info
-           (short) 0, (short) 0, (byte) 0, (byte) 0);                               // UTM info
+    SPHERE((short) 4047, (short) 0, (short) 0, (short) 6047, (short) 7048,                  // Geodetic info
+           (short) 0, (short) 0, (short) 0, (short) 0, (byte) 0, (byte) 0);                 // UPS and UTM info
 
     /**
      * The enum for the default CRS.
@@ -314,6 +319,11 @@ public enum CommonCRS {
     final short ellipsoid;
 
     /**
+     * EPSG codes of Universal Polar Stereographic projections, North and South cases.
+     */
+    final short northUPS, southUPS;
+
+    /**
      * EPSG codes of pseudo "UTM zone zero" (North case and South case), or 0 if none.
      */
     final short northUTM, southUTM;
@@ -359,12 +369,21 @@ public enum CommonCRS {
     private transient volatile GeocentricCRS cachedSpherical;
 
     /**
-     * The Universal Transverse Mercator projections, created when first needed.
-     * All accesses to this map shall be synchronized on {@code cachedUTM}.
+     * The Universal Transverse Mercator (UTM) or Universal Polar Stereographic (UPS) projections,
+     * created when first needed. The UPS projections are arbitrarily given zone numbers
+     * {@value #POLAR} and -{@value #POLAR} for North and South poles respectively.
+     *
+     * <p>All accesses to this map shall be synchronized on {@code cachedProjections}.</p>
      *
-     * @see #UTM(double, double)
+     * @see #universal(double, double)
      */
-    private final Map<Integer,ProjectedCRS> cachedUTM;
+    private final Map<Integer,ProjectedCRS> cachedProjections;
+
+    /**
+     * The special zone number used as key in {@link #cachedProjections} for polar stereographic projections.
+     * Must be outside the range of UTM zone numbers.
+     */
+    private static final int POLAR = 90;
 
     /**
      * Creates a new constant for the given EPSG or SIS codes.
@@ -376,18 +395,20 @@ public enum CommonCRS {
      * @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,
-            final short northUTM, final short southUTM, final byte firstZone, final byte lastZone)
+            final short northUPS, final short southUPS, 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.northUPS   = northUPS;
+        this.southUPS   = southUPS;
         this.northUTM   = northUTM;
         this.southUTM   = southUTM;
         this.firstZone  = firstZone;
         this.lastZone   = lastZone;
-        cachedUTM = new HashMap<>();
+        cachedProjections = new HashMap<>();
     }
 
     /**
@@ -407,15 +428,81 @@ 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'.
+    @SuppressWarnings("NestedSynchronizedStatement")    // Safe because cachedProjections never call any method of 'this'.
     synchronized void clear() {
         cached           = null;
         cachedGeo3D      = null;
         cachedNormalized = null;
         cachedGeocentric = null;
-        synchronized (cachedUTM) {
-            cachedUTM.clear();
+        synchronized (cachedProjections) {
+            cachedProjections.clear();
+        }
+    }
+
+    /**
+     * Returns the {@code CommonCRS} enumeration value for the datum of the given CRS.
+     * The given CRS shall comply to the following conditions
+     * (otherwise an {@link IllegalArgumentException} is thrown):
+     *
+     * <ul>
+     *   <li>The {@code crs} is either an instance of {@link SingleCRS},
+     *       or an instance of {@link org.opengis.referencing.crs.CompoundCRS}
+     *       with an {@linkplain CRS#getHorizontalComponent horizontal component}.</li>
+     *   <li>The {@code crs} or the horizontal component of {@code crs} is associated to a {@link GeodeticDatum}.</li>
+     *   <li>The geodetic datum either<ul>
+     *     <li>has the same EPSG code than one of the {@code CommonCRS} enumeration values, or</li>
+     *     <li>has no EPSG code but is {@linkplain Utilities#equalsIgnoreMetadata equal, ignoring metadata},
+     *       to the {@link #datum()} value of one of the {@code CommonCRS} enumeration values.</li>
+     *   </ul></li>
+     * </ul>
+     *
+     * This method is useful for easier creation of various coordinate reference systems through the
+     * {@link #geographic()}, {@link #geocentric()} or other convenience methods when the set of datums
+     * supported by {@code CommonCRS} is known to be sufficient.
+     *
+     * @param  crs  the coordinate reference system for which to get a {@code CommonCRS} value.
+     * @return the {@code CommonCRS} value for the geodetic datum of the given CRS.
+     * @throws IllegalArgumentException if no {@code CommonCRS} value can be found for the given CRS.
+     *
+     * @see #datum()
+     * @since 0.8
+     */
+    public static CommonCRS forDatum(final CoordinateReferenceSystem crs) {
+        final SingleCRS single;
+        if (crs instanceof SingleCRS) {
+            single = (SingleCRS) crs;
+        } else {
+            single = CRS.getHorizontalComponent(crs);
+            if (single == null) {
+                throw new IllegalArgumentException(Resources.format(
+                        Resources.Keys.NonHorizontalCRS_1, IdentifiedObjects.getName(crs, null)));
+            }
         }
+        final Datum datum = single.getDatum();
+        if (datum instanceof GeodeticDatum) {
+            /*
+             * First, try to search using only the EPSG code. This approach avoid initializing unneeded
+             * geodetic objects (such initializations are costly if they require connection to the EPSG
+             * database).
+             */
+            int epsg = 0;
+            final Identifier identifier = IdentifiedObjects.getIdentifier(datum, Citations.EPSG);
+            if (identifier != null) {
+                final String code = identifier.getCode();
+                if (code != null) try {
+                    epsg = Integer.parseInt(code);
+                } catch (NumberFormatException e) {
+                    Logging.recoverableException(Logging.getLogger(Modules.REFERENCING), CommonCRS.class, "forDatum", e);
+                }
+            }
+            for (final CommonCRS c : values()) {
+                if ((epsg != 0) ? c.datum == epsg : Utilities.equalsIgnoreMetadata(c.datum(), datum)) {
+                    return c;
+                }
+            }
+        }
+        throw new IllegalArgumentException(Errors.format(
+                Errors.Keys.UnsupportedDatum_1, IdentifiedObjects.getName(datum, null)));
     }
 
     /**
@@ -727,6 +814,7 @@ public enum CommonCRS {
      *
      * @return the geodetic datum associated to this enum.
      *
+     * @see #forDatum(CoordinateReferenceSystem)
      * @see org.apache.sis.referencing.datum.DefaultGeodeticDatum
      */
     public GeodeticDatum datum() {
@@ -889,90 +977,175 @@ public enum CommonCRS {
     /**
      * 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.
+     *
+     * @param  latitude  a latitude in the desired UTM projection zone.
+     * @param  longitude a longitude in the desired UTM projection zone.
+     * @return a Universal Transverse Mercator projection for the zone containing the given point.
+     *
+     * @since 0.7
+     *
+     * @deprecated Generalized by {@link #universal(double, double)},
+     *             which can also return a UPS projection when appropriate.
+     */
+    @Deprecated
+    public ProjectedCRS UTM(final double latitude, final double longitude) {
+        return universal(Math.signum(latitude), longitude);
+    }
+
+    /**
+     * Returns a Universal Transverse Mercator (UTM) or a Universal Polar Stereographic (UPS) 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:
      *
-     * <ul>
-     *   <li>The sign of the <var>latitude</var> 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.</li>
-     *   <li>The value of the <var>longitude</var> 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.</li>
+     * <ul class="verbose">
+     *   <li>If the <var>latitude</var> argument is less than 80°S or equal or greater than 84°N,
+     *       then a <cite>Universal Polar Stereographic</cite> projection is created.</li>
+     *   <li>Otherwise a <cite>Universal Transverse Mercator</cite> projection is created as below:
+     *     <ul class="verbose">
+     *       <li>The sign of the <var>latitude</var> 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 the special cases documented below
+     *           and for ensuring that the latitude is inside the [-90 … 90]° range.</li>
+     *       <li>The value of the <var>longitude</var> 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.</li>
+     *       <li>Calculation of UTM zone involves two special cases (if those special cases are not desired,
+     *           they can be avoided by making sure that the given latitude is below 56°N):
+     *         <ul>
+     *           <li>Between 56°N and 64°N, zone 32 is widened to 9° (at the expense of zone 31)
+     *               to accommodate southwest Norway.</li>
+     *           <li>Between 72°N and 84°N, zones 33 and 35 are widened to 12° to accommodate Svalbard.
+     *               To compensate for these 12° wide zones, zones 31 and 37 are widened to 9° and
+     *               zones 32, 34, and 36 are eliminated.</li>
+     *         </ul>
+     *       </li>
+     *     </ul>
+     *   </li>
      * </ul>
      *
-     * <div class="note"><b>Warning:</b>
-     * 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.</div>
+     * <div class="note"><b>Tip:</b>
+     * for "straight" UTM zone calculation without any special case (neither Norway, Svalbard or Universal Polar
+     * Stereographic projection), one can replace the {@code latitude} argument by {@code Math.signum(latitude)}.
+     * For using a specific zone number, one can additionally replace the {@code longitude} argument by
+     * {@code zone * 6 - 183}.</div>
      *
      * The map projection uses the following parameters:
      *
-     * <blockquote><table class="sis">
-     *   <caption>Universal Transverse Mercator (UTM) parameters</caption>
-     *   <tr><th>Parameter name</th>                 <th>Value</th></tr>
-     *   <tr><td>Latitude of natural origin</td>     <td>0°</td></tr>
-     *   <tr><td>Longitude of natural origin</td>    <td>Central meridian of the UTM zone containing the given longitude</td></tr>
-     *   <tr><td>Scale factor at natural origin</td> <td>0.9996</td></tr>
-     *   <tr><td>False easting</td>                  <td>500000 metres</td></tr>
-     *   <tr><td>False northing</td>                 <td>0 (North hemisphere) or 10000000 (South hemisphere) metres</td></tr>
-     * </table></blockquote>
+     * <table class="sis">
+     *   <caption>Universal Transverse Mercator (UTM) and Universal Polar Stereographic (UPS) projection parameters</caption>
+     *   <tr>
+     *     <th>Parameter name</th>
+     *     <th>UTM parameter value</th>
+     *     <th>UPS parameter value</th>
+     *   </tr><tr>
+     *     <td>Latitude of natural origin</td>
+     *     <td>0°</td>
+     *     <td>90°N or 90°S depending on the sign of given latitude</td>
+     *   </tr><tr>
+     *     <td>Longitude of natural origin</td>
+     *     <td>Central meridian of the UTM zone containing the given longitude</td>
+     *     <td>0°</td>
+     *   </tr><tr>
+     *     <td>Scale factor at natural origin</td>
+     *     <td>0.9996</td>
+     *     <td>0.994</td>
+     *   </tr><tr>
+     *     <td>False easting</td>
+     *     <td>500 000 metres</td>
+     *     <td>2 000 000 metres</td>
+     *   </tr><tr>
+     *     <td>False northing</td>
+     *     <td>0 (North hemisphere) or 10 000 000 (South hemisphere) metres</td>
+     *     <td>2 000 000 metres</td>
+     *   </tr>
+     * </table>
      *
      * The coordinate system axes are (Easting, Northing) in metres.
      *
-     * @param  latitude  a latitude in the desired UTM projection zone.
-     * @param  longitude a longitude in the desired UTM projection zone.
-     * @return a Universal Transverse Mercator projection for the zone containing the given point.
+     * <div class="note"><b>Warning:</b>
+     * 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.</div>
      *
-     * @since 0.7
+     * @param  latitude  a latitude in the desired UTM or UPS projection zone.
+     * @param  longitude a longitude in the desired UTM or UPS projection zone.
+     * @return a Universal Transverse Mercator or Polar Stereographic projection for the zone containing the given point.
+     *
+     * @since 0.8
      */
-    public ProjectedCRS UTM(final double latitude, final double longitude) {
+    public ProjectedCRS universal(final double latitude, final double longitude) {
         ArgumentChecks.ensureBetween("latitude",   Latitude.MIN_VALUE,     Latitude.MAX_VALUE,     latitude);
         ArgumentChecks.ensureBetween("longitude", -Formulas.LONGITUDE_MAX, Formulas.LONGITUDE_MAX, longitude);
         final boolean isSouth = MathFunctions.isNegative(latitude);
-        final int zone = TransverseMercator.Zoner.UTM.zone(longitude);
+        final boolean isUTM   = latitude >= TransverseMercator.Zoner.SOUTH_BOUNDS
+                             && latitude <  TransverseMercator.Zoner.NORTH_BOUNDS;
+        final int zone = isUTM ? TransverseMercator.Zoner.UTM.zone(latitude, longitude) : POLAR;
         final Integer key = isSouth ? -zone : zone;
         ProjectedCRS crs;
-        synchronized (cachedUTM) {
-            crs = cachedUTM.get(key);
+        synchronized (cachedProjections) {
+            crs = cachedProjections.get(key);
         }
         if (crs == null) {
+            /*
+             * Requested CRS has not been previously created, or the cache has been cleared.
+             * Before to create the CRS explicitely, try to get it from the EPSG database.
+             * Using the EPSG geodetic dataset when possible gives us more information,
+             * like the aliases and area of validity.
+             */
             int code = 0;
-            if (zone >= firstZone && zone <= lastZone) {
+            if (!isUTM) {
+                code = (isSouth ? southUPS : northUPS) & 0xFFFF;
+            } else if (zone >= firstZone && zone <= lastZone) {
                 code = (isSouth ? southUTM : northUTM) & 0xFFFF;
-                if (code != 0) {
-                    code += zone;
-                    final GeodeticAuthorityFactory factory = factory();
-                    if (factory != null) try {
-                        return factory.createProjectedCRS(String.valueOf(code));
-                    } catch (FactoryException e) {
-                        failure(this, "UTM", e, code);
-                    }
+            }
+            if (code != 0) {
+                if (isUTM) code += zone;
+                final GeodeticAuthorityFactory factory = factory();
+                if (factory != null) try {
+                    return factory.createProjectedCRS(String.valueOf(code));
+                } catch (FactoryException e) {
+                    failure(this, "universal", e, code);
                 }
             }
             /*
-             * All constants defined in this enumeration use the same coordinate system, EPSG:4400.
-             * We will arbitrarily create this CS only for a frequently created CRS, and share that
-             * CS instance for all other constants.
+             * At this point we couldn't use the EPSG dataset; we have to create the CRS ourselves.
+             * All constants defined in this enumeration use the same coordinate system (EPSG:4400)
+             * except for the polar regions. We will arbitrarily create the CS only for a frequently
+             * used datum, then share that CS instance for all other constants.
              */
             CartesianCS cs = null;
-            synchronized (DEFAULT.cachedUTM) {
-                final Iterator<ProjectedCRS> it = DEFAULT.cachedUTM.values().iterator();
-                if (it.hasNext()) {
-                    cs = it.next().getCoordinateSystem();
+            if (isUTM) {
+                synchronized (DEFAULT.cachedProjections) {
+                    for (final Map.Entry<Integer,ProjectedCRS> entry : DEFAULT.cachedProjections.entrySet()) {
+                        if (Math.abs(entry.getKey()) != POLAR) {
+                            cs = entry.getValue().getCoordinateSystem();
+                            break;
+                        }
+                    }
                 }
             }
+            /*
+             * If we didn't found a Coordinate System for EPSG:4400, or if the CS that we needed was
+             * for another EPSG code (polar cases), delegate to the WGS84 datum or create the CS now.
+             *
+             *   EPSG:4400 — Cartesian 2D CS. Axes: easting, northing (E,N). Orientations: east, north. UoM: m.
+             *   EPSG:1026 — Cartesian 2D CS for UPS north. Axes: E,N. Orientations: E along 90°E meridian, N along 180°E meridian. UoM: m.
+             *   EPSG:1027 — Cartesian 2D CS for UPS south. Axes: E,N. Orientations: E along 90°E, N along 0°E meridians. UoM: m.
+             */
             if (cs == null) {
                 if (this != DEFAULT) {
-                    cs = DEFAULT.UTM(latitude, longitude).getCoordinateSystem();
+                    cs = DEFAULT.universal(latitude, longitude).getCoordinateSystem();
                 } else {
-                    cs = (CartesianCS) StandardDefinitions.createCoordinateSystem(Constants.EPSG_PROJECTED_CS);
+                    cs = (CartesianCS) StandardDefinitions.createCoordinateSystem(
+                            isUTM ? Constants.EPSG_PROJECTED_CS : isSouth ? (short) 1027 : (short) 1026);
                 }
             }
-            crs = StandardDefinitions.createUTM(code, geographic(), latitude, longitude, cs);
+            crs = StandardDefinitions.createUniversal(code, geographic(), isUTM, latitude, longitude, cs);
             final ProjectedCRS other;
-            synchronized (cachedUTM) {
-                other = JDK8.putIfAbsent(cachedUTM, key, crs);
+            synchronized (cachedProjections) {
+                other = JDK8.putIfAbsent(cachedProjections, key, crs);
             }
             if (other != null) {
                 return other;

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -45,6 +45,7 @@ import org.apache.sis.util.resources.Voc
 import org.apache.sis.util.iso.DefaultNameSpace;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Debug;
+import org.apache.sis.measure.Latitude;
 
 
 /**
@@ -94,8 +95,8 @@ final class EPSGFactoryFallback extends
     }
 
     /**
-     * Returns the EPSG authority with only a modification in the title of emphasing that this is a subset
-     * of EPSG dataset.
+     * Returns the EPSG authority with only a modification in the title
+     * for emphasing that this is a subset of EPSG dataset.
      */
     @Override
     public synchronized Citation getAuthority() {
@@ -138,10 +139,14 @@ final class EPSGFactoryFallback extends
                 add(codes, crs.geographic);
                 add(codes, crs.geo3D);
             }
-            if (projected && (crs.northUTM != 0 || crs.southUTM != 0)) {
-                for (int zone = crs.firstZone; zone <= crs.lastZone; zone++) {
-                    if (crs.northUTM != 0) codes.add(Integer.toString(crs.northUTM + zone));
-                    if (crs.southUTM != 0) codes.add(Integer.toString(crs.southUTM + zone));
+            if (projected) {
+                add(codes, crs.northUPS);
+                add(codes, crs.southUPS);
+                if (crs.northUTM != 0 || crs.southUTM != 0) {
+                    for (int zone = crs.firstZone; zone <= crs.lastZone; zone++) {
+                        if (crs.northUTM != 0) codes.add(Integer.toString(crs.northUTM + zone));
+                        if (crs.southUTM != 0) codes.add(Integer.toString(crs.southUTM + zone));
+                    }
                 }
             }
         }
@@ -246,13 +251,19 @@ final class EPSGFactoryFallback extends
                     final double latitude;
                     int zone;
                     if (crs.northUTM != 0 && (zone = n - crs.northUTM) >= crs.firstZone && zone <= crs.lastZone) {
-                        latitude = +1;
+                        latitude = +1;          // Any north latitude below 56°N (because of Norway exception) is okay
                     } else if (crs.southUTM != 0 && (zone = n - crs.southUTM) >= crs.firstZone && zone <= crs.lastZone) {
-                        latitude = -1;
+                        latitude = -1;          // Any south latitude above 80°S (because of UPS south case) is okay.
+                    } else if (n == crs.northUPS) {
+                        latitude = Latitude.MAX_VALUE;
+                        zone     = 30;                  // Any random UTM zone is okay.
+                    } else if (n == crs.southUPS) {
+                        latitude = Latitude.MIN_VALUE;
+                        zone     = 30;                  // Any random UTM zone is okay.
                     } else {
                         continue;
                     }
-                    return crs.UTM(latitude, TransverseMercator.Zoner.UTM.centralMeridian(zone));
+                    return crs.universal(latitude, TransverseMercator.Zoner.UTM.centralMeridian(zone));
                 }
             }
             if ((kind & (DATUM | CRS)) != 0) {

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/StandardDefinitions.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/StandardDefinitions.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/StandardDefinitions.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/StandardDefinitions.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -41,12 +41,14 @@ import org.opengis.util.NoSuchIdentifier
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.metadata.AxisNames;
 import org.apache.sis.internal.referencing.provider.TransverseMercator;
+import org.apache.sis.internal.referencing.provider.PolarStereographicA;
 import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.datum.DefaultEllipsoid;
 import org.apache.sis.referencing.datum.DefaultPrimeMeridian;
 import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
 import org.apache.sis.referencing.datum.DefaultVerticalDatum;
+import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.cs.DefaultVerticalCS;
 import org.apache.sis.referencing.cs.DefaultCartesianCS;
 import org.apache.sis.referencing.cs.DefaultSphericalCS;
@@ -72,6 +74,7 @@ import static org.apache.sis.internal.me
 /**
  * Definitions of referencing objects identified by the {@link CommonCRS} enumeration values.
  * This class is used only as a fallback if the objects can not be fetched from the EPSG database.
+ * This class should not be loaded when a connection to an EPSG geodetic dataset is available.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4
@@ -107,7 +110,7 @@ final class StandardDefinitions {
             map.put(IDENTIFIERS_KEY, new NamedIdentifier(Citations.EPSG, String.valueOf(code)));
         }
         map.put(NAME_KEY, new NamedIdentifier(Citations.EPSG, name));
-        map.put(ALIAS_KEY, alias); // May be null, which is okay.
+        map.put(ALIAS_KEY, alias);                                      // May be null, which is okay.
         if (world) {
             map.put(DOMAIN_OF_VALIDITY_KEY, Extents.WORLD);
         }
@@ -126,29 +129,31 @@ final class StandardDefinitions {
     }
 
     /**
-     * Returns the operation method for Transverse Mercator projection using the SIS factory implementation.
-     * This method restricts the factory to SIS implementation instead than arbitrary factory in order to meet
-     * the contract saying that {@link CommonCRS} methods should never fail.
+     * Creates a Universal Transverse Mercator (UTM) or a Universal Polar Stereographic (UPS) projected CRS
+     * using the Apache SIS factory implementation. This method restricts the factory to SIS implementation
+     * instead than arbitrary factory in order to meet the contract saying that {@link CommonCRS} methods
+     * should never fail.
      *
      * @param code       the EPSG code, or 0 if none.
      * @param baseCRS    the geographic CRS on which the projected CRS is based.
-     * @param latitude   a latitude in the zone of the desired projection, to be snapped to 0°.
+     * @param isUTM      {@code true} for UTM or {@code false} for UPS. Note: redundant with the given latitude.
+     * @param latitude   a latitude in the zone of the desired projection, to be snapped to 0°, 90°S or 90°N.
      * @param longitude  a longitude in the zone of the desired projection, to be snapped to UTM central meridian.
      * @param derivedCS  the projected coordinate system.
      */
-    static ProjectedCRS createUTM(final int code, final GeographicCRS baseCRS,
+    static ProjectedCRS createUniversal(final int code, final GeographicCRS baseCRS, final boolean isUTM,
             final double latitude, final double longitude, final CartesianCS derivedCS)
     {
         final OperationMethod method;
         try {
-            method = DefaultFactories.forBuildin(MathTransformFactory.class,
-                                          DefaultMathTransformFactory.class)
-                    .getOperationMethod(TransverseMercator.NAME);
+            method = DefaultFactories.forBuildin(MathTransformFactory.class, DefaultMathTransformFactory.class)
+                            .getOperationMethod(isUTM ? TransverseMercator.NAME : PolarStereographicA.NAME);
         } catch (NoSuchIdentifierException e) {
-            throw new IllegalStateException(e);     // Should not happen with SIS implementation.
+            throw new IllegalStateException(e);                     // Should not happen with SIS implementation.
         }
         final ParameterValueGroup parameters = method.getParameters().createValue();
-        String name = TransverseMercator.Zoner.UTM.setParameters(parameters, true, latitude, longitude);
+        String name = isUTM ? TransverseMercator.Zoner.UTM.setParameters(parameters, latitude, longitude)
+                            : PolarStereographicA.setParameters(parameters, latitude >= 0);
         final DefaultConversion conversion = new DefaultConversion(properties(0, name, null, false), method, null, parameters);
 
         name = baseCRS.getName().getCode() + " / " + name;
@@ -165,17 +170,17 @@ final class StandardDefinitions {
      */
     static GeographicCRS createGeographicCRS(final short code, final GeodeticDatum datum, final EllipsoidalCS cs) {
         final String name;
-        String alias = null;
-        String scope = null;
+        String  alias = null;
+        String  scope = null;
         boolean world = false;
         switch (code) {
-            case 4326: name = "WGS 84"; world = true; scope = "Horizontal component of 3D system."; break;
-            case 4322: name = "WGS 72"; world = true; break;
-            case 4258: name = "ETRS89"; alias = "ETRS89-GRS80"; break;
-            case 4269: name = "NAD83";  break;
-            case 4267: name = "NAD27";  break;
-            case 4230: name = "ED50";   break;
-            case 4047: name = "Unspecified datum based upon the GRS 1980 Authalic Sphere"; world = true; break;
+            case 4326: name = "WGS 84"; world = true;           scope = "Horizontal component of 3D system."; break;
+            case 4322: name = "WGS 72"; world = true;           scope = "Horizontal component of 3D system."; break;
+            case 4258: name = "ETRS89"; alias = "ETRS89-GRS80"; scope = "Horizontal component of 3D system."; break;
+            case 4269: name = "NAD83";                          scope = "Geodetic survey.";                   break;
+            case 4267: name = "NAD27";                          scope = "Geodetic survey.";                   break;
+            case 4230: name = "ED50";                           scope = "Geodetic survey.";                   break;
+            case 4047: name = "Unspecified datum based upon the GRS 1980 Authalic Sphere"; world = true;      break;
             default:   throw new AssertionError(code);
         }
         final Map<String, Object> properties = properties(code, name, alias, world);
@@ -313,16 +318,17 @@ final class StandardDefinitions {
     @SuppressWarnings("fallthrough")
     static CoordinateSystem createCoordinateSystem(final short code) {
         final String name;
-        final int dim;                  // Number of dimension.
+        int type = 0;                   // 0= Cartesian (default), 1= Spherical, 2= Ellipsoidal
+        int dim = 2;                    // Number of dimension, default to 2.
         short axisCode;                 // Code of first axis + dim (or code after the last axis).
-        boolean isCartesian = false;
-        boolean isSpherical = false;
         switch (code) {
-            case 6422: name = "Ellipsoidal 2D"; dim = 2; axisCode = 108; break;
-            case 6423: name = "Ellipsoidal 3D"; dim = 3; axisCode = 111; break;
-            case 6404: name = "Spherical";      dim = 3; axisCode =  63; isSpherical = true; break;
-            case 6500: name = "Earth centred";  dim = 3; axisCode = 118; isCartesian = true; break;
-            case 4400: name = "Cartesian 2D";   dim = 2; axisCode =   3; isCartesian = true; break;
+            case 6422: name = "Ellipsoidal 2D"; type = 2;          axisCode =  108; break;
+            case 6423: name = "Ellipsoidal 3D"; type = 2; dim = 3; axisCode =  111; break;
+            case 6404: name = "Spherical";      type = 1; dim = 3; axisCode =   63; break;
+            case 6500: name = "Earth centred";            dim = 3; axisCode =  118; break;
+            case 4400: name = "Cartesian 2D";                      axisCode =    3; break;
+            case 1026: name = "Cartesian 2D for UPS north";        axisCode = 1067; break;
+            case 1027: name = "Cartesian 2D for UPS south";        axisCode = 1059; break;
             default:   throw new AssertionError(code);
         }
         final Map<String,?> properties = properties(code, name, null, false);
@@ -334,20 +340,13 @@ final class StandardDefinitions {
             case 1:  xAxis = createAxis(--axisCode);
             case 0:  break;
         }
-        if (isCartesian) {
-            if (zAxis != null) {
-                return new DefaultCartesianCS(properties, xAxis, yAxis, zAxis);
-            } else {
-                return new DefaultCartesianCS(properties, xAxis, yAxis);
-            }
-        } else if (isSpherical) {
-            return new DefaultSphericalCS(properties, xAxis, yAxis, zAxis);
-        } else {
-            if (zAxis != null) {
-                return new DefaultEllipsoidalCS(properties, xAxis, yAxis, zAxis);
-            } else {
-                return new DefaultEllipsoidalCS(properties, xAxis, yAxis);
-            }
+        switch (type) {
+            default: throw new AssertionError(type);
+            case 0:  return (zAxis != null) ? new DefaultCartesianCS  (properties, xAxis, yAxis, zAxis)
+                                            : new DefaultCartesianCS  (properties, xAxis, yAxis);
+            case 1:  return                   new DefaultSphericalCS  (properties, xAxis, yAxis, zAxis);
+            case 2:  return (zAxis != null) ? new DefaultEllipsoidalCS(properties, xAxis, yAxis, zAxis)
+                                            : new DefaultEllipsoidalCS(properties, xAxis, yAxis);
         }
     }
 
@@ -367,12 +366,10 @@ final class StandardDefinitions {
         switch (code) {
             case 1:    name = "Easting";
                        abrv = "E";
-                       unit = Units.METRE;
                        dir  = AxisDirection.EAST;
                        break;
             case 2:    name = "Northing";
                        abrv = "N";
-                       unit = Units.METRE;
                        dir  = AxisDirection.NORTH;
                        break;
             case 60:   name = "Spherical latitude";
@@ -393,7 +390,6 @@ final class StandardDefinitions {
                        break;
             case 62:   name = "Geocentric radius";
                        abrv = "R";                          // See HardCodedAxes.GEOCENTRIC_RADIUS in tests.
-                       unit = Units.METRE;
                        dir  = AxisDirection.UP;
                        rm   = RangeMeaning.EXACT;
                        min  = 0;
@@ -440,6 +436,23 @@ final class StandardDefinitions {
                        abrv = "Z";
                        dir  = AxisDirection.GEOCENTRIC_Z;
                        break;
+            case 1057: // Actually no axis allocated by EPSG here, but createCoordinateSystem(1027) needs this number.
+            case 1056: name = "Easting";
+                       abrv = "E";
+                       dir  = CoordinateSystems.directionAlongMeridian(AxisDirection.NORTH, 90);
+                       break;
+            case 1058: name = "Northing";
+                       abrv = "N";
+                       dir  = CoordinateSystems.directionAlongMeridian(AxisDirection.NORTH, 0);
+                       break;
+            case 1065: name = "Easting";
+                       abrv = "E";
+                       dir  = CoordinateSystems.directionAlongMeridian(AxisDirection.SOUTH, 90);
+                       break;
+            case 1066: name = "Northing";
+                       abrv = "N";
+                       dir  = CoordinateSystems.directionAlongMeridian(AxisDirection.SOUTH, 180);
+                       break;
             default:   throw new AssertionError(code);
         }
         final Map<String,Object> properties = properties(code, name, null, false);

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -169,7 +169,7 @@ public class AbstractCRS extends Abstrac
      *   </tr>
      * </table>
      *
-     * @param properties  The properties to be given to the coordinate reference system.
+     * @param properties  the properties to be given to the coordinate reference system.
      * @param cs          the coordinate system.
      */
     public AbstractCRS(final Map<String,?> properties, final CoordinateSystem cs) {

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/DefaultCoordinateSystemAxis.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -19,6 +19,7 @@ package org.apache.sis.referencing.cs;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.Objects;
 import javax.measure.Unit;
 import javax.measure.quantity.Angle;
 import javax.measure.UnitConverter;
@@ -45,6 +46,7 @@ import org.apache.sis.referencing.Identi
 import org.apache.sis.measure.Longitude;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Units;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.jaxb.Context;
@@ -69,9 +71,6 @@ import static org.apache.sis.util.collec
  */
 import static org.apache.sis.internal.referencing.NilReferencingObject.UNNAMED;
 
-// Branch-dependent imports
-import java.util.Objects;
-
 
 /**
  * Coordinate system axis name, direction, unit and range of values.
@@ -567,12 +566,13 @@ public class DefaultCoordinateSystemAxis
      * i.e. it is caller responsibility to determine if range shall be considered as metadata.
      *
      * @param  that  the axis to compare with this axis.
+     * @param  mode  whether the unit comparison is approximative or exact.
      * @param  cr    {@code true} for comparing also the range minimum and maximum values.
      * @return {@code true} if unit, direction and optionally range extremum are equal.
      */
-    private boolean equalsIgnoreMetadata(final CoordinateSystemAxis that, final boolean cr) {
-        return Objects.equals(getUnit(),      that.getUnit()) &&
-               Objects.equals(getDirection(), that.getDirection()) &&
+    private boolean equalsIgnoreMetadata(final CoordinateSystemAxis that, final ComparisonMode mode, final boolean cr) {
+        return Objects.equals(getDirection(), that.getDirection()) &&
+               Utilities.deepEquals(getUnit(), that.getUnit(), mode) &&
                (!cr || (doubleToLongBits(getMinimumValue()) == doubleToLongBits(that.getMinimumValue()) &&
                         doubleToLongBits(getMaximumValue()) == doubleToLongBits(that.getMaximumValue())));
     }
@@ -618,7 +618,7 @@ public class DefaultCoordinateSystemAxis
             }
             case BY_CONTRACT: {
                 final CoordinateSystemAxis that = (CoordinateSystemAxis) object;
-                return equalsIgnoreMetadata(that, true) &&
+                return equalsIgnoreMetadata(that, mode, true) &&
                        Objects.equals(getAbbreviation(), that.getAbbreviation()) &&
                        Objects.equals(getRangeMeaning(), that.getRangeMeaning());
             }
@@ -629,8 +629,8 @@ public class DefaultCoordinateSystemAxis
          * coordinate operation may shift some ordinate values (typically ±360° on longitudes).
          */
         final CoordinateSystemAxis that = (CoordinateSystemAxis) object;
-        if (!equalsIgnoreMetadata(that, RangeMeaning.WRAPAROUND.equals(this.getRangeMeaning()) &&
-                                        RangeMeaning.WRAPAROUND.equals(that.getRangeMeaning())))
+        if (!equalsIgnoreMetadata(that, mode, RangeMeaning.WRAPAROUND.equals(this.getRangeMeaning()) &&
+                                              RangeMeaning.WRAPAROUND.equals(that.getRangeMeaning())))
         {
             return false;
         }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultEllipsoid.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.datum;
 
 import java.util.Map;
+import java.util.Objects;
 import javax.measure.Unit;
 import javax.measure.quantity.Length;
 import javax.measure.UnitConverter;
@@ -42,6 +43,7 @@ import org.apache.sis.referencing.Abstra
 import org.apache.sis.io.wkt.Formatter;
 import org.apache.sis.io.wkt.Convention;
 import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.measure.Units;
 
 import static java.lang.Math.*;
@@ -49,9 +51,6 @@ import static java.lang.Double.*;
 import static org.apache.sis.util.ArgumentChecks.ensureStrictlyPositive;
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 
-// Branch-dependent imports
-import java.util.Objects;
-
 
 /**
  * Geometric figure that can be used to describe the approximate shape of the earth.
@@ -717,7 +716,7 @@ public class DefaultEllipsoid extends Ab
                  */
                 final Ellipsoid that = (Ellipsoid) object;
                 final Unit<Length> unit = getAxisUnit();  // In case the user override this method.
-                if (!Objects.equals(unit, that.getAxisUnit())) {
+                if (!Utilities.deepEquals(unit, that.getAxisUnit(), mode)) {
                     return false;
                 }
                 final UnitConverter c = mode.isApproximative() ? unit.getConverterTo(Units.METRE) : null;

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -43,6 +43,7 @@ import org.opengis.referencing.cs.Cartes
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.datum.DatumFactory;
 import org.opengis.referencing.datum.EngineeringDatum;
+import org.apache.sis.internal.referencing.provider.TransverseMercator.Zoner;
 import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.metadata.iso.citation.Citations;
@@ -60,8 +61,6 @@ import org.apache.sis.util.resources.Err
 import org.apache.sis.util.iso.DefaultNameSpace;
 import org.apache.sis.util.iso.SimpleInternationalString;
 
-import static org.apache.sis.internal.referencing.provider.TransverseMercator.Zoner.UTM;
-
 
 /**
  * Creates coordinate reference systems in the "{@code OGC}", "{@code CRS}" or {@code "AUTO(2)"} namespaces.
@@ -177,14 +176,18 @@ import static org.apache.sis.internal.re
  *
  * <div class="note"><b>Examples:</b>
  * {@code "AUTO2:42001,1,-100,45"} identifies a Universal Transverse Mercator (UTM) projection
- * for a zone containing the point at (45°N, 100°W).</div>
+ * for a zone containing the point at (45°N, 100°W) with axes in metres.</div>
  *
  * Codes in the {@code "AUTO"} namespace are the same than codes in the {@code "AUTO2"} namespace, except that
  * the {@linkplain org.apache.sis.measure.Units#valueOfEPSG(int) EPSG code} of the desired unit of measurement
  * was used instead than the unit factor.
  * The {@code "AUTO"} namespace was defined in the <cite>Web Map Service</cite> (WMS) 1.1.1 specification
  * while the {@code "AUTO2"} namespace is defined in WMS 1.3.0.
- * In Apache SIS implementation, the unit parameter (either as factor or as EPSG code) is optional and default to metres.
+ * In Apache SIS implementation, the unit parameter (either as factor or as EPSG code) is optional and defaults to metres.
+ *
+ * <p>In the {@code AUTO(2):42001} case, the UTM zone is calculated as specified in WMS 1.3 specification,
+ * i.e. <strong>without</strong> taking in account the Norway and Svalbard special cases and without
+ * switching to polar stereographic projections for high latitudes.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.7
@@ -587,7 +590,7 @@ public class CommonAuthorityFactory exte
              * 42005: WGS 84 / Auto Mollweide         —   defined by "Central_Meridian" only.
              */
             case 42001: isUTM  = true; break;
-            case 42002: isUTM  = (latitude == 0) && (UTM.centralMeridian(UTM.zone(longitude)) == longitude); break;
+            case 42002: isUTM  = (latitude == 0) && (Zoner.UTM.centralMeridian(Zoner.UTM.zone(0, longitude)) == longitude); break;
             case 42003: method = "Orthographic";       param = Constants.LATITUDE_OF_ORIGIN;  break;
             case 42004: method = "Equirectangular";    param = Constants.STANDARD_PARALLEL_1; break;
             case 42005: method = "Mollweide";                                                 break;
@@ -596,14 +599,17 @@ public class CommonAuthorityFactory exte
         /*
          * For the (Universal) Transverse Mercator case (AUTO:42001 and 42002), we delegate to the CommonCRS
          * enumeration if possible because CommonCRS will itself delegate to the EPSG factory if possible.
+         * The Math.signum(latitude) instruction is for preventing "AUTO:42001" to handle the UTM special cases
+         * (Norway and Svalbard) or to switch on the Universal Polar Stereographic projection for high latitudes,
+         * because the WMS specification does not said that we should.
          */
         final CommonCRS datum = CommonCRS.WGS84;
-        final GeographicCRS baseCRS;
-        final ProjectedCRS crs;
-        CartesianCS cs;
+        final GeographicCRS baseCRS;                // To be set, directly or indirectly, to WGS84.geographic().
+        final ProjectedCRS crs;                     // Temporary UTM projection, for extracting other properties.
+        CartesianCS cs;                             // Coordinate system with (E,N) axes in metres.
         try {
             if (isUTM != null && isUTM) {
-                crs = datum.UTM(latitude, longitude);
+                crs = datum.universal(Math.signum(latitude), longitude);
                 if (factor == (isLegacy ? Constants.EPSG_METRE : 1)) {
                     return crs;
                 }
@@ -612,7 +618,7 @@ public class CommonAuthorityFactory exte
             } else {
                 cs = projectedCS;
                 if (cs == null) {
-                    crs = datum.UTM(latitude, longitude);
+                    crs = datum.universal(Math.signum(latitude), longitude);
                     projectedCS = cs = crs.getCoordinateSystem();
                     baseCRS = crs.getBaseCRS();
                 } else {
@@ -640,10 +646,10 @@ public class CommonAuthorityFactory exte
              */
             final GeodeticObjectBuilder builder = new GeodeticObjectBuilder();
             if (isUTM != null) {
-                if (isUTM) {
+                if (isUTM && crs != null) {
                     builder.addName(crs.getName());
                 } // else default to the conversion name, which is "Transverse Mercator".
-                builder.setTransverseMercator(isUTM, latitude, longitude);
+                builder.setTransverseMercator(isUTM ? Zoner.UTM : Zoner.ANY, latitude, longitude);
             } else {
                 builder.setConversionMethod(method)
                        .addName(PROJECTION_NAMES[projection - FIRST_PROJECTION_CODE])

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/package-info.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/package-info.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/package-info.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -23,7 +23,13 @@
  * <p>The most commonly used kinds of Reference Systems in Apache SIS are the <cite>Coordinate Reference Systems</cite>
  * (CRS), which handle coordinates of arbitrary dimensions. The SIS implementations can handle 2D and 3D coordinates,
  * as well as 4D, 5D, <i>etc</i>. An other less-frequently used kind of Reference System uses labels instead, as in
- * postal address. This package is the root for both kinds, with an emphasis on the one for coordinates.</p>
+ * postal address. This package is the root for both kinds, with an emphasis on the one for coordinates.
+ * The two kinds of referencing system are implemented in the following packages:</p>
+ * <ul>
+ *   <li>{@link org.apache.sis.referencing.crs} for <cite>referencing by coordinates</cite> (ISO 19111)</li>
+ *   <li>{@link org.apache.sis.referencing.gazetteer} for <cite>referencing by geographic identifiers</cite>
+ *       (ISO 19112), together with the linking from geographic identifiers to coordinates.</li>
+ * </ul>
  *
  * <div class="section">Fetching geodetic object instances</div>
  * Geodetic objects can be instantiated either

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -90,7 +90,7 @@ public abstract strictfp class Transform
      */
     @Test
     public final void testTransform() throws FactoryException, TransformException {
-        final ProjectedCRS    targetCRS  = CommonCRS.WGS84.UTM(10, -123.5);
+        final ProjectedCRS    targetCRS  = CommonCRS.WGS84.universal(10, -123.5);
         final GeographicCRS   sourceCRS  = targetCRS.getBaseCRS();
         final Conversion      conversion = targetCRS.getConversionFromBase();
         final MathTransform2D transform  = (MathTransform2D) conversion.getMathTransform();
@@ -199,7 +199,7 @@ public abstract strictfp class Transform
     @Test
     @DependsOnMethod("testTransform")
     public final void testTransformNotOverPole() throws FactoryException, TransformException {
-        final ProjectedCRS  sourceCRS  = CommonCRS.WGS84.UTM(10, -3.5);
+        final ProjectedCRS  sourceCRS  = CommonCRS.WGS84.universal(10, -3.5);
         final GeographicCRS targetCRS  = sourceCRS.getBaseCRS();
         final Conversion    conversion = inverse(sourceCRS.getConversionFromBase());
         final G rectangle = createFromExtremums(sourceCRS, 199980, 4490220, 309780, 4600020);

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ServicesForMetadataTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ServicesForMetadataTest.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ServicesForMetadataTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ServicesForMetadataTest.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -50,7 +50,7 @@ import static org.apache.sis.test.TestUt
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
- * @version 0.7
+ * @version 0.8
  * @module
  */
 @DependsOn({
@@ -213,4 +213,14 @@ public final strictfp class ServicesForM
                 ((CoordinateReferenceSystem) components[1]).getCoordinateSystem(),
                 AxisDirection.UP, AxisDirection.NORTH, AxisDirection.EAST);
     }
+
+    /**
+     * Tests {@link org.apache.sis.metadata.iso.extent.Extents#centroid(GeographicBoundingBox)}.
+     *
+     * @since 0.8
+     */
+    @Test
+    public void testGeographicBoundingBoxCentroid() {
+        org.apache.sis.metadata.iso.extent.ExtentsTest.testCentroid();
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/TransverseMercatorTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/TransverseMercatorTest.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/TransverseMercatorTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/TransverseMercatorTest.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -37,13 +37,18 @@ import static org.junit.Assert.*;
  */
 public final strictfp class TransverseMercatorTest extends TestCase {
     /**
-     * Tests {@link TransverseMercator.Zoner#zone(double)}.
+     * Tests {@link TransverseMercator.Zoner#zone(double)},
+     * including the special cases for Norway and Svalbard.
      */
     @Test
     public void testZone() {
-        assertEquals(1,  TransverseMercator.Zoner.UTM.zone(-180));
-        assertEquals(10, TransverseMercator.Zoner.UTM.zone(-123));
-        assertEquals(60, TransverseMercator.Zoner.UTM.zone(179.9));
+        assertEquals("180°E",        1, TransverseMercator.Zoner.UTM.zone( 0, -180  ));
+        assertEquals("123°E",       10, TransverseMercator.Zoner.UTM.zone( 0, -123  ));
+        assertEquals("179.9°W",     60, TransverseMercator.Zoner.UTM.zone( 0,  179.9));
+        assertEquals( "4°E band T", 31, TransverseMercator.Zoner.UTM.zone(45,    4  ));
+        assertEquals( "4°E band V", 32, TransverseMercator.Zoner.UTM.zone(56,    4  ));
+        assertEquals("20°E band W", 34, TransverseMercator.Zoner.UTM.zone(71,   20  ));
+        assertEquals("20°E band X", 33, TransverseMercator.Zoner.UTM.zone(72,   20  ));
     }
 
     /**
@@ -57,7 +62,7 @@ public final strictfp class TransverseMe
     }
 
     /**
-     * Tests {@link TransverseMercator.Zoner#setParameters(ParameterValueGroup, boolean, double, double)}
+     * Tests {@link TransverseMercator.Zoner#setParameters(ParameterValueGroup, double, double)}
      * followed by {@link TransverseMercator.Zoner#zone(ParameterValueGroup)}.
      */
     @Test
@@ -67,17 +72,17 @@ public final strictfp class TransverseMe
     })
     public void testSetParameters() {
         final ParameterValueGroup p = TransverseMercator.PARAMETERS.createValue();
-        assertEquals("UTM zone 10N", TransverseMercator.Zoner.UTM.setParameters(p, true, 0, -122));
+        assertEquals("UTM zone 10N", TransverseMercator.Zoner.UTM.setParameters(p, 0, -122));
         assertEquals(Constants.CENTRAL_MERIDIAN, -123, p.parameter(Constants.CENTRAL_MERIDIAN).doubleValue(), STRICT);
         assertEquals(Constants.FALSE_NORTHING, 0, p.parameter(Constants.FALSE_NORTHING).doubleValue(), STRICT);
         assertEquals("UTM.zone(parameters)", 10, TransverseMercator.Zoner.UTM.zone(p));
 
-        assertEquals("Transverse Mercator", TransverseMercator.Zoner.UTM.setParameters(p, false, 0, -122));
+        assertEquals("Transverse Mercator", TransverseMercator.Zoner.ANY.setParameters(p, 0, -122));
         assertEquals(Constants.CENTRAL_MERIDIAN, -122, p.parameter(Constants.CENTRAL_MERIDIAN).doubleValue(), STRICT);
         assertEquals(Constants.FALSE_NORTHING, 0, p.parameter(Constants.FALSE_NORTHING).doubleValue(), STRICT);
         assertEquals("UTM.zone(parameters)", 0, TransverseMercator.Zoner.UTM.zone(p));
 
-        assertEquals("UTM zone 10S", TransverseMercator.Zoner.UTM.setParameters(p, false, -0.0, -123));
+        assertEquals("UTM zone 10S", TransverseMercator.Zoner.ANY.setParameters(p, -0.0, -123));
         assertEquals(Constants.CENTRAL_MERIDIAN, -123, p.parameter(Constants.CENTRAL_MERIDIAN).doubleValue(), STRICT);
         assertEquals(Constants.FALSE_NORTHING, 10000000, p.parameter(Constants.FALSE_NORTHING).doubleValue(), STRICT);
         assertEquals("UTM.zone(parameters)", -10, TransverseMercator.Zoner.UTM.zone(p));

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -75,28 +75,30 @@ public final strictfp class CRSTest exte
      *
      * @throws FactoryException if a CRS can not be constructed.
      *
-     * @see CommonCRSTest#testForCode()
+     * @see EPSGFactoryFallbackTest#testCreateCRS()
      */
     @Test
     public void testForEpsgCode() throws FactoryException {
-        verifyForCode(CommonCRS.WGS84 .geographic(),   "EPSG:4326");
-        verifyForCode(CommonCRS.WGS84 .geographic(),   "urn:ogc:def:crs:EPSG::4326");
-        verifyForCode(CommonCRS.WGS84 .geographic(),   "urn:x-ogc:def:crs:EPSG::4326");
-        verifyForCode(CommonCRS.WGS84 .geographic(),   "http://www.opengis.net/gml/srs/epsg.xml#4326");
-        verifyForCode(CommonCRS.WGS72 .geographic(),   "EPSG:4322");
-        verifyForCode(CommonCRS.SPHERE.geographic(),   "EPSG:4047");
-        verifyForCode(CommonCRS.NAD83 .geographic(),   "EPSG:4269");
-        verifyForCode(CommonCRS.NAD27 .geographic(),   "EPSG:4267");
-        verifyForCode(CommonCRS.ETRS89.geographic(),   "EPSG:4258");
-        verifyForCode(CommonCRS.ED50  .geographic(),   "EPSG:4230");
-        verifyForCode(CommonCRS.WGS84 .geocentric(),   "EPSG:4978");
-        verifyForCode(CommonCRS.WGS72 .geocentric(),   "EPSG:4984");
-        verifyForCode(CommonCRS.ETRS89.geocentric(),   "EPSG:4936");
-        verifyForCode(CommonCRS.WGS84 .geographic3D(), "EPSG:4979");
-        verifyForCode(CommonCRS.WGS72 .geographic3D(), "EPSG:4985");
-        verifyForCode(CommonCRS.ETRS89.geographic3D(), "EPSG:4937");
+        verifyForCode(CommonCRS.WGS84 .geographic(),           "EPSG:4326");
+        verifyForCode(CommonCRS.WGS84 .geographic(),           "urn:ogc:def:crs:EPSG::4326");
+        verifyForCode(CommonCRS.WGS84 .geographic(),           "urn:x-ogc:def:crs:EPSG::4326");
+        verifyForCode(CommonCRS.WGS84 .geographic(),           "http://www.opengis.net/gml/srs/epsg.xml#4326");
+        verifyForCode(CommonCRS.WGS72 .geographic(),           "EPSG:4322");
+        verifyForCode(CommonCRS.SPHERE.geographic(),           "EPSG:4047");
+        verifyForCode(CommonCRS.NAD83 .geographic(),           "EPSG:4269");
+        verifyForCode(CommonCRS.NAD27 .geographic(),           "EPSG:4267");
+        verifyForCode(CommonCRS.ETRS89.geographic(),           "EPSG:4258");
+        verifyForCode(CommonCRS.ED50  .geographic(),           "EPSG:4230");
+        verifyForCode(CommonCRS.WGS84 .geocentric(),           "EPSG:4978");
+        verifyForCode(CommonCRS.WGS72 .geocentric(),           "EPSG:4984");
+        verifyForCode(CommonCRS.ETRS89.geocentric(),           "EPSG:4936");
+        verifyForCode(CommonCRS.WGS84 .geographic3D(),         "EPSG:4979");
+        verifyForCode(CommonCRS.WGS72 .geographic3D(),         "EPSG:4985");
+        verifyForCode(CommonCRS.ETRS89.geographic3D(),         "EPSG:4937");
+        verifyForCode(CommonCRS.WGS84 .universal(88, 120),     "EPSG:5041");
+        verifyForCode(CommonCRS.WGS84 .universal(-40, 2),      "EPSG:32731");
         verifyForCode(CommonCRS.Vertical.MEAN_SEA_LEVEL.crs(), "EPSG:5714");
-        verifyForCode(CommonCRS.Vertical.DEPTH.crs(), "EPSG:5715");
+        verifyForCode(CommonCRS.Vertical.DEPTH.crs(),          "EPSG:5715");
     }
 
     /**
@@ -104,7 +106,7 @@ public final strictfp class CRSTest exte
      *
      * @throws FactoryException if a CRS can not be constructed.
      *
-     * @see CommonCRSTest#testForCode()
+     * @see EPSGFactoryFallbackTest#testCreateCRS()
      */
     @Test
     @DependsOnMethod("testForEpsgCode")

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -79,6 +79,8 @@ public final strictfp class CommonCRSTes
             assertNoCodeCollision(codes, crs, crs.geographic);
             assertNoCodeCollision(codes, crs, crs.geocentric);
             assertNoCodeCollision(codes, crs, crs.geo3D);
+            assertNoCodeCollision(codes, crs, crs.northUPS);
+            assertNoCodeCollision(codes, crs, crs.southUPS);
             for (int zone = crs.firstZone; zone <= crs.lastZone; zone++) {
                 if (crs.northUTM != 0) assertNoCodeCollision(codes, crs, crs.northUTM + zone);
                 if (crs.southUTM != 0) assertNoCodeCollision(codes, crs, crs.southUTM + zone);
@@ -276,19 +278,54 @@ public final strictfp class CommonCRSTes
     }
 
     /**
-     * Tests {@link CommonCRS#UTM(double, double)}.
+     * Tests {@link CommonCRS#universal(double, double)} with Universal Transverse Mercator (UTM) projections.
      *
      * @since 0.7
      */
     @Test
     @DependsOnMethod("testGeographic")
     public void testUTM() {
-        final ProjectedCRS crs = CommonCRS.WGS72.UTM(-45, -122);
+        final ProjectedCRS crs = CommonCRS.WGS72.universal(-45, -122);
         assertEquals("name", "WGS 72 / UTM zone 10S", crs.getName().getCode());
         final ParameterValueGroup pg = crs.getConversionFromBase().getParameterValues();
-        assertEquals(Constants.LATITUDE_OF_ORIGIN, -123, pg.parameter(Constants.CENTRAL_MERIDIAN).doubleValue(), STRICT);
-        assertEquals(Constants.FALSE_NORTHING, 10000000, pg.parameter(Constants.FALSE_NORTHING).doubleValue(),   STRICT);
-        assertSame("Expected a cached instance.", crs, CommonCRS.WGS72.UTM(-45, -122));
-        assertNotSame("Expected a new instance.", crs, CommonCRS.WGS72.UTM(+45, -122));
+        assertEquals(Constants.LATITUDE_OF_ORIGIN,    0, pg.parameter(Constants.LATITUDE_OF_ORIGIN).doubleValue(), STRICT);
+        assertEquals(Constants.CENTRAL_MERIDIAN,   -123, pg.parameter(Constants.CENTRAL_MERIDIAN)  .doubleValue(), STRICT);
+        assertEquals(Constants.SCALE_FACTOR,     0.9996, pg.parameter(Constants.SCALE_FACTOR)      .doubleValue(), STRICT);
+        assertEquals(Constants.FALSE_EASTING,    500000, pg.parameter(Constants.FALSE_EASTING)     .doubleValue(), STRICT);
+        assertEquals(Constants.FALSE_NORTHING, 10000000, pg.parameter(Constants.FALSE_NORTHING)    .doubleValue(), STRICT);
+        assertSame("Expected a cached instance.", crs, CommonCRS.WGS72.universal(-45, -122));
+        assertNotSame("Expected a new instance.", crs, CommonCRS.WGS72.universal(+45, -122));
+    }
+
+    /**
+     * Tests {@link CommonCRS#universal(double, double)} with Universal Polar Stereographic (UPS) projections.
+     *
+     * @since 0.8
+     */
+    @Test
+    @DependsOnMethod("testGeographic")
+    public void testUPS() {
+        final ProjectedCRS crs = CommonCRS.WGS72.universal(-85, -122);
+        assertEquals("name", "WGS 72 / Universal Polar Stereographic South", crs.getName().getCode());
+        final ParameterValueGroup pg = crs.getConversionFromBase().getParameterValues();
+        assertEquals(Constants.LATITUDE_OF_ORIGIN, -90, pg.parameter(Constants.LATITUDE_OF_ORIGIN).doubleValue(), STRICT);
+        assertEquals(Constants.CENTRAL_MERIDIAN,     0, pg.parameter(Constants.CENTRAL_MERIDIAN)  .doubleValue(), STRICT);
+        assertEquals(Constants.SCALE_FACTOR,     0.994, pg.parameter(Constants.SCALE_FACTOR)      .doubleValue(), STRICT);
+        assertEquals(Constants.FALSE_EASTING,  2000000, pg.parameter(Constants.FALSE_EASTING)     .doubleValue(), STRICT);
+        assertEquals(Constants.FALSE_NORTHING, 2000000, pg.parameter(Constants.FALSE_NORTHING)    .doubleValue(), STRICT);
+        assertSame("Expected a cached instance.", crs, CommonCRS.WGS72.universal(-85, -122));
+        assertNotSame("Expected a new instance.", crs, CommonCRS.WGS72.universal(+85, -122));
+    }
+
+    /**
+     * Tests {@link CommonCRS#forDatum(CoordinateReferenceSystem)}.
+     *
+     * @sinc 0.8
+     */
+    @Test
+    @DependsOnMethod("testGeographic")
+    public void testForDatum() {
+        assertSame("WGS84", CommonCRS.WGS84, CommonCRS.forDatum(CommonCRS.WGS84.geographic()));
+        assertSame("WGS72", CommonCRS.WGS72, CommonCRS.forDatum(CommonCRS.WGS72.geographic()));
     }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/EPSGFactoryFallbackTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/EPSGFactoryFallbackTest.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/EPSGFactoryFallbackTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/EPSGFactoryFallbackTest.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -19,11 +19,13 @@ package org.apache.sis.referencing;
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Set;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.VerticalCRS;
 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.datum.Datum;
 import org.opengis.referencing.datum.Ellipsoid;
@@ -74,6 +76,12 @@ public final strictfp class EPSGFactoryF
                 EPSGFactoryFallback.INSTANCE.getAuthorityCodes(GeographicCRS.class));
         assertSetEquals(Arrays.asList("5714", "5715", "5703"),
                 EPSGFactoryFallback.INSTANCE.getAuthorityCodes(VerticalCRS.class));
+        /*
+         * There is two many ProjectedCRS codes for enumerating all of them, so test only a sampling.
+         */
+        final Set<String> codes = EPSGFactoryFallback.INSTANCE.getAuthorityCodes(ProjectedCRS.class);
+        assertTrue(codes.containsAll(Arrays.asList("5041", "5042", "32601", "32660", "32701", "32760")));
+        assertTrue(Collections.disjoint(codes, Arrays.asList("7030", "6326", "4326", "4978", "32600", "32700", "5714")));
     }
 
     /**
@@ -141,6 +149,8 @@ public final strictfp class EPSGFactoryF
         verifyCreateCRS(CommonCRS.WGS84 .geographic3D(),          "4979");
         verifyCreateCRS(CommonCRS.WGS72 .geographic3D(),          "4985");
         verifyCreateCRS(CommonCRS.ETRS89.geographic3D(),          "4937");
+        verifyCreateCRS(CommonCRS.WGS84 .universal(-88, 120),     "5042");
+        verifyCreateCRS(CommonCRS.WGS84 .universal( 40, 14),     "32633");
         verifyCreateCRS(CommonCRS.Vertical.MEAN_SEA_LEVEL.crs(),  "5714");
         verifyCreateCRS(CommonCRS.Vertical.DEPTH.crs(),           "5715");
     }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/StandardDefinitionsTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/StandardDefinitionsTest.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/StandardDefinitionsTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/StandardDefinitionsTest.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -71,18 +71,42 @@ public final strictfp class StandardDefi
     }
 
     /**
-     * Tests {@link StandardDefinitions#createUTM(int, GeographicCRS, double, boolean, CartesianCS)}.
+     * Tests {@link StandardDefinitions#createUniversal(int, GeographicCRS, boolean, double, double, CartesianCS)}
+     * for a Universal Transverse Mercator (UTM) projection.
      *
      * @since 0.7
      */
     @Test
     @DependsOnMethod("testCreateGeographicCRS")
     public void testCreateUTM() {
-        final ProjectedCRS crs = StandardDefinitions.createUTM(32610, HardCodedCRS.WGS84, 15, -122, HardCodedCS.PROJECTED);
+        final ProjectedCRS crs = StandardDefinitions.createUniversal(32610, HardCodedCRS.WGS84, true, 15, -122, HardCodedCS.PROJECTED);
         assertEquals("name", "WGS 84 / UTM zone 10N", crs.getName().getCode());
         final ParameterValueGroup pg = crs.getConversionFromBase().getParameterValues();
-        assertEquals(Constants.LATITUDE_OF_ORIGIN, -123, pg.parameter(Constants.CENTRAL_MERIDIAN).doubleValue(), STRICT);
-        assertEquals(Constants.FALSE_NORTHING,        0, pg.parameter(Constants.FALSE_NORTHING).doubleValue(),   STRICT);
+        assertEquals(Constants.LATITUDE_OF_ORIGIN,    0, pg.parameter(Constants.LATITUDE_OF_ORIGIN).doubleValue(), STRICT);
+        assertEquals(Constants.CENTRAL_MERIDIAN,   -123, pg.parameter(Constants.CENTRAL_MERIDIAN)  .doubleValue(), STRICT);
+        assertEquals(Constants.SCALE_FACTOR,     0.9996, pg.parameter(Constants.SCALE_FACTOR)      .doubleValue(), STRICT);
+        assertEquals(Constants.FALSE_EASTING,    500000, pg.parameter(Constants.FALSE_EASTING)     .doubleValue(), STRICT);
+        assertEquals(Constants.FALSE_NORTHING,        0, pg.parameter(Constants.FALSE_NORTHING)    .doubleValue(), STRICT);
+    }
+
+    /**
+     * Tests {@link StandardDefinitions#createUniversal(int, GeographicCRS, boolean, double, double, CartesianCS)}
+     * for a Universal Polar Stereographic (UPS) projection. This test cheats a little bit on the coordinate system
+     * by laziness; we are more interested in the projection parameters.
+     *
+     * @since 0.8
+     */
+    @Test
+    @DependsOnMethod("testCreateGeographicCRS")
+    public void testCreateUPS() {
+        final ProjectedCRS crs = StandardDefinitions.createUniversal(5041, HardCodedCRS.WGS84, false, 90, -122, HardCodedCS.PROJECTED);
+        assertEquals("name", "WGS 84 / Universal Polar Stereographic North", crs.getName().getCode());
+        final ParameterValueGroup pg = crs.getConversionFromBase().getParameterValues();
+        assertEquals(Constants.LATITUDE_OF_ORIGIN,  90, pg.parameter(Constants.LATITUDE_OF_ORIGIN).doubleValue(), STRICT);
+        assertEquals(Constants.CENTRAL_MERIDIAN,     0, pg.parameter(Constants.CENTRAL_MERIDIAN)  .doubleValue(), STRICT);
+        assertEquals(Constants.SCALE_FACTOR,     0.994, pg.parameter(Constants.SCALE_FACTOR)      .doubleValue(), STRICT);
+        assertEquals(Constants.FALSE_EASTING,  2000000, pg.parameter(Constants.FALSE_EASTING)     .doubleValue(), STRICT);
+        assertEquals(Constants.FALSE_NORTHING, 2000000, pg.parameter(Constants.FALSE_NORTHING)    .doubleValue(), STRICT);
     }
 
     /**

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -208,7 +208,7 @@ public final strictfp class EPSGInstalle
              * This implies the creation of a coordinate operation.
              */
             final ProjectedCRS p = factory.createProjectedCRS("EPSG:32215");
-            assertTrue(Utilities.deepEquals(CommonCRS.WGS72.UTM(1, -93), p, ComparisonMode.DEBUG));
+            assertTrue(Utilities.deepEquals(CommonCRS.WGS72.universal(1, -93), p, ComparisonMode.DEBUG));
             /*
              * Get the authority codes. We choose a type that implies an SQL statement
              * with both "DEPRECATED" and "SHOW_CRS" conditions in their "WHERE" clause.

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/CoordinateOperationFinderTest.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -175,7 +175,7 @@ public final strictfp class CoordinateOp
         testIdentityTransform(CommonCRS.WGS84.geographic3D());
         testIdentityTransform(CommonCRS.WGS84.geocentric());
         testIdentityTransform(CommonCRS.WGS84.spherical());
-        testIdentityTransform(CommonCRS.WGS84.UTM(0, 0));
+        testIdentityTransform(CommonCRS.WGS84.universal(0, 0));
         testIdentityTransform(CommonCRS.Vertical.DEPTH.crs());
         testIdentityTransform(CommonCRS.Temporal.JULIAN.crs());
     }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CoordinateDomain.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CoordinateDomain.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CoordinateDomain.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CoordinateDomain.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -37,6 +37,12 @@ import org.apache.sis.referencing.datum.
  * @module
  */
 public strictfp class CoordinateDomain {
+    /*
+     * Note: this class is not declared as an enum yet because moving ARTICLE_CIRCLE and HEIGHT constants
+     *       after the enum declarations causes an "illegal forward reference" compiler error with JDK 8.
+     */
+
+
     /**
      * Latitude of the Article circle, which is 66°33′45.7″ as of March 30, 2015
      * Note that this value fluctuate by 2° over 40,000-year periods.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/JDK8.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/JDK8.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/JDK8.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/JDK8.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -82,6 +82,17 @@ public final class JDK8 {
         return buffer.toString();
     }
 
+    /**
+     * Returns {@code true} if the given value is neither NaN or infinite.
+     *
+     * @param  value  the value to test.
+     * @return whether the given value is finite.
+     *
+     * @since 0.8
+     */
+    public static boolean isFinite(final double value) {
+        return !Double.isNaN(value) && !Double.isInfinite(value);
+    }
 
     /**
      * Compares two numbers as unsigned long.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Modules.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -55,6 +55,11 @@ public final class Modules {
     /**
      * The {@value} module name.
      */
+    public static final String REFERENCING_BY_IDENTIFIERS = "org.apache.sis.referencing.gazetteer";
+
+    /**
+     * The {@value} module name.
+     */
     public static final String STORAGE = "org.apache.sis.storage";
 
     /**

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/OptionalDependency.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/OptionalDependency.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/OptionalDependency.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/OptionalDependency.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -46,6 +46,7 @@ public abstract class OptionalDependency
      * @param dependency  the Maven artifact name (<strong>not</strong> a name from the {@link Modules} class)
      *        of the optional module on which the {@code module} depend.
      */
+    @SuppressWarnings("ThisEscapedInObjectConstruction")
     protected OptionalDependency(final String module, final String dependency) {
         super(module);
         this.dependency = dependency;

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java?rev=1785905&r1=1785904&r2=1785905&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java [UTF-8] Tue Mar  7 23:37:49 2017
@@ -91,7 +91,7 @@ public class Cloner {
         try {
             if (valueType != type) {
                 method = valueType.getMethod("clone", (Class<?>[]) null);
-                type = valueType; // Set only if the above line succeed.
+                type = valueType;                                           // Set only if the above line succeed.
                 /*
                  * If the class implementing the 'clone()' method is not public, we may not be able to access that
                  * method even if it is public. Try to make the method accessible. If we fail for security reason,



Mime
View raw message