sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1789729 [3/9] - in /sis/branches/JDK9: ./ application/sis-console/ application/sis-console/src/main/java/org/apache/sis/console/ application/sis-console/src/test/java/org/apache/sis/console/ core/ core/sis-feature/src/main/java/org/apache/...
Date Fri, 31 Mar 2017 18:49:18 GMT
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -27,9 +27,9 @@ import org.apache.sis.math.DecimalFuncti
  * increase the precision in the common case where the shifts are specified with no more than
  * 5 digits in base 10 in ASCII files.
  *
- * @param <C> Dimension of the coordinate unit (usually {@link javax.measure.quantity.Angle}).
- * @param <T> Dimension of the translation unit (usually {@link javax.measure.quantity.Angle}
- *            or {@link javax.measure.quantity.Length}).
+ * @param  <C>  dimension of the coordinate unit (usually {@link javax.measure.quantity.Angle}).
+ * @param  <T>  dimension of the translation unit (usually {@link javax.measure.quantity.Angle}
+ *              or {@link javax.measure.quantity.Length}).
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
@@ -123,7 +123,7 @@ final class DatumShiftGridCompressed<C e
      * Returns a new grid with the same geometry than this grid but different data arrays.
      */
     @Override
-    final DatumShiftGridFile<C,T> setData(final Object[] other) {
+    protected final DatumShiftGridFile<C,T> setData(final Object[] other) {
         return new DatumShiftGridCompressed<>(this, averages, (short[][]) other, scale);
     }
 
@@ -140,11 +140,13 @@ final class DatumShiftGridCompressed<C e
     }
 
     /**
-     * Returns direct references (not cloned) to the data arrays.
+     * Returns direct references (not cloned) to the data arrays. This method is for cache management,
+     * {@link #equals(Object)} and {@link #hashCode()} implementations only and should not be invoked
+     * in other context.
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    final Object[] getData() {
+    protected final Object[] getData() {
         return data;
     }
 

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -24,28 +24,35 @@ import javax.measure.Quantity;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.opengis.parameter.GeneralParameterDescriptor;
+import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.util.collection.Cache;
 import org.apache.sis.util.Debug;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.referencing.datum.DatumShiftGrid;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.internal.util.Utilities;
 
 
 /**
  * A datum shift grid loaded from a file.
  * The filename is usually a parameter defined in the EPSG database.
+ * This class should not be in public API because it requires implementation to expose internal mechanic:
  *
- * <p>This class is in internal package (not public API) because it makes the following assumptions:</p>
  * <ul>
- *   <li>Values <var>x₀</var>, <var>y₀</var>, <var>Δx</var> and <var>Δy</var>
- *       given to the constructor are in degrees and needs to be converted to radians.</li>
- *   <li>Single floating-point precision ({@code float)} is sufficient.</li>
- *   <li>Values were defined in base 10, usually in ASCII files. This assumption has an impact on conversions
- *       from {@code float} to {@code double} performed by the {@link #getCellValue(int, int, int)} method.</li>
+ *   <li>Subclasses need to give an access to their internal data (not a copy) through the {@link #getData()}
+ *       and {@link #setData(Object[])} methods. We use that for managing the cache, reducing memory usage by
+ *       sharing data and for {@link #equals(Object)} and {@link #hashCode()} implementations.</li>
+ *   <li>{@link #descriptor}, {@link #gridToTarget()} and {@link #setFileParameters(Parameters)} are convenience
+ *       members for {@link org.apache.sis.referencing.operation.transform.InterpolatedTransform} constructor.
+ *       What they do are closely related to how {@code InterpolatedTransform} works, and trying to document that
+ *       in a public API would probably be too distracting for the users.</li>
  * </ul>
  *
+ * The main concrete subclass is {@link DatumShiftGridFile.Float}.
+ *
  * @param  <C>  dimension of the coordinate unit (usually {@link javax.measure.quantity.Angle}).
  * @param  <T>  dimension of the translation unit (usually {@link javax.measure.quantity.Angle}
  *              or {@link javax.measure.quantity.Length}).
@@ -54,6 +61,8 @@ import org.apache.sis.internal.referenci
  * @since   0.7
  * @version 0.8
  * @module
+ *
+ * @see org.apache.sis.referencing.operation.transform.InterpolatedTransform
  */
 public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> extends DatumShiftGrid<C,T> {
     /**
@@ -91,7 +100,7 @@ public abstract class DatumShiftGridFile
     /**
      * Number of grid cells along the <var>x</var> axis.
      */
-    final int nx;
+    protected final int nx;
 
     /**
      * The best translation accuracy that we can expect from this file.
@@ -101,7 +110,37 @@ public abstract class DatumShiftGridFile
      *
      * @see #getCellPrecision()
      */
-    double accuracy;
+    protected double accuracy;
+
+    /**
+     * Creates a new datum shift grid for the given grid geometry.
+     * The actual offset values need to be provided by subclasses.
+     *
+     * @param  coordinateUnit    the unit of measurement of input values, before conversion to grid indices by {@code coordinateToGrid}.
+     * @param  translationUnit   the unit of measurement of output values.
+     * @param  isCellValueRatio  {@code true} if results of {@link #interpolateInCell interpolateInCell(…)} are divided by grid cell size.
+     * @param  coordinateToGrid  conversion from the "real world" coordinates to grid indices including fractional parts.
+     * @param  nx                number of cells along the <var>x</var> axis in the grid.
+     * @param  ny                number of cells along the <var>y</var> axis in the grid.
+     * @param  descriptor        the parameter descriptor of the provider that created this grid.
+     * @param  files             the file(s) from which the grid has been loaded.
+     *
+     * @since 0.8
+     */
+    protected DatumShiftGridFile(final Unit<C> coordinateUnit,
+                                 final Unit<T> translationUnit,
+                                 final boolean isCellValueRatio,
+                                 final LinearTransform coordinateToGrid,
+                                 final int nx, final int ny,
+                                 final ParameterDescriptorGroup descriptor,
+                                 final Path... files)
+    {
+        super(coordinateUnit, coordinateToGrid, new int[] {nx, ny}, isCellValueRatio, translationUnit);
+        this.descriptor = descriptor;
+        this.files      = files;
+        this.nx         = nx;
+        this.accuracy   = Double.NaN;
+    }
 
     /**
      * Creates a new datum shift grid for the given grid geometry.
@@ -123,15 +162,8 @@ public abstract class DatumShiftGridFile
                        final ParameterDescriptorGroup descriptor,
                        final Path... files) throws NoninvertibleTransformException
     {
-        super(coordinateUnit, new AffineTransform2D(Δx, 0, 0, Δy, x0, y0).inverse(),
-                new int[] {nx, ny}, isCellValueRatio, translationUnit);
-        this.descriptor = descriptor;
-        this.files      = files;
-        this.nx         = nx;
-        this.accuracy   = Double.NaN;
-        if (files.length == 0) {
-            throw new IllegalArgumentException();
-        }
+        this(coordinateUnit, translationUnit, isCellValueRatio,
+                new AffineTransform2D(Δx, 0, 0, Δy, x0, y0).inverse(), nx, ny, descriptor, files);
     }
 
     /**
@@ -139,7 +171,7 @@ public abstract class DatumShiftGridFile
      *
      * @param  other  the other datum shift grid from which to copy the grid geometry.
      */
-    DatumShiftGridFile(final DatumShiftGridFile<C,T> other) {
+    protected DatumShiftGridFile(final DatumShiftGridFile<C,T> other) {
         super(other);
         descriptor = other.descriptor;
         files      = other.files;
@@ -148,21 +180,29 @@ public abstract class DatumShiftGridFile
     }
 
     /**
-     * Suggests a precision for the translation values in this grid.
-     * The default implementation returns a value smaller than the accuracy.
-     *
-     * @return a precision for the translation values in this grid.
+     * Returns {@code this} casted to the given type, after verification that those types are valid.
+     * This method is invoked after {@link NADCON}, {@link NTv2} or other providers got an existing
+     * {@code DatumShiftGridFile} instance from the {@link #CACHE}.
      */
-    @Override
-    public double getCellPrecision() {
-        return accuracy / 10;   // Division by 10 is arbitrary.
+    @SuppressWarnings("unchecked")
+    final <NC extends Quantity<NC>, NT extends Quantity<NT>> DatumShiftGridFile<NC,NT> castTo(
+            final Class<NC> coordinateType, final Class<NT> translationType)
+    {
+        super.getCoordinateUnit() .asType(coordinateType);
+        super.getTranslationUnit().asType(translationType);
+        return (DatumShiftGridFile<NC,NT>) this;
     }
 
     /**
      * If a grid exists in the cache for the same data, returns a new grid sharing the same data arrays.
      * Otherwise returns {@code this}.
+     *
+     * @return a grid using the same data than this grid, or {@code this}.
+     *
+     * @see #getData()
+     * @see #setData(Object[])
      */
-    final DatumShiftGridFile<C,T> useSharedData() {
+    protected final DatumShiftGridFile<C,T> useSharedData() {
         final Object[] data = getData();
         for (final DatumShiftGridFile<?,?> grid : CACHE.values()) {
             final Object[] other = grid.getData();
@@ -179,13 +219,44 @@ public abstract class DatumShiftGridFile
      * the same data than an existing grid. The typical use case is when a filename is different but still
      * reference the same grid (e.g. symbolic link, lower case versus upper case in a case-insensitive file
      * system).
+     *
+     * @param  other  data from another {@code DatumShiftGridFile} that we can share.
+     * @return a new {@code DatumShiftGridFile} using the given data reference.
      */
-    abstract DatumShiftGridFile<C,T> setData(Object[] other);
+    protected abstract DatumShiftGridFile<C,T> setData(Object[] other);
 
     /**
-     * Returns the data for each shift dimensions.
+     * Returns the data for each shift dimensions. This method is for cache management, {@link #equals(Object)}
+     * and {@link #hashCode()} implementations only and should not be invoked in other context.
+     *
+     * @return a direct (not cloned) reference to the internal data array.
      */
-    abstract Object[] getData();
+    protected abstract Object[] getData();
+
+    /**
+     * Suggests a precision for the translation values in this grid.
+     * The default implementation returns a value smaller than the accuracy.
+     *
+     * @return a precision for the translation values in this grid.
+     */
+    @Override
+    public double getCellPrecision() {
+        return accuracy / 10;   // Division by 10 is arbitrary.
+    }
+
+    /**
+     * Returns the transform from grid coordinates to "real world" coordinates after the datum shift has been applied,
+     * or {@code null} for the default. This is usually the inverse of the transform from "real world" coordinates to
+     * grid coordinates before datum shift, since NADCON and NTv2 transformations have source and target coordinates
+     * in the same coordinate system (with axis units in degrees). But this method may be overridden by subclasses that
+     * use {@code DatumShiftGridFile} for other kind of transformations.
+     *
+     * @return the transformation from grid coordinates to "real world" coordinates after datum shift,
+     *         or {@code null} for the default (namely the inverse of the "source to grid" transformation).
+     */
+    public Matrix gridToTarget() {
+        return null;
+    }
 
     /**
      * Sets all parameters for a value of type {@link Path} to the values given to th constructor.
@@ -193,31 +264,21 @@ public abstract class DatumShiftGridFile
      * @param  parameters  the parameter group where to set the values.
      */
     public final void setFileParameters(final Parameters parameters) {
-        int i = 0;  // The 'files' array should always contains at least one element.
-        for (final GeneralParameterDescriptor gd : descriptor.descriptors()) {
-            if (gd instanceof ParameterDescriptor<?>) {
-                final ParameterDescriptor<?> d = (ParameterDescriptor<?>) gd;
-                if (Path.class.isAssignableFrom(d.getValueClass())) {
-                    parameters.getOrCreate(d).setValue(files[i]);
-                    if (++i == files.length) break;
+        if (files.length != 0) {
+            int i = 0;
+            for (final GeneralParameterDescriptor gd : descriptor.descriptors()) {
+                if (gd instanceof ParameterDescriptor<?>) {
+                    final ParameterDescriptor<?> d = (ParameterDescriptor<?>) gd;
+                    if (Path.class.isAssignableFrom(d.getValueClass())) {
+                        parameters.getOrCreate(d).setValue(files[i]);
+                        if (++i == files.length) break;
+                    }
                 }
             }
         }
     }
 
     /**
-     * Returns {@code this} casted to the given type, after verification that those types are valid.
-     */
-    @SuppressWarnings("unchecked")
-    final <NC extends Quantity<NC>, NT extends Quantity<NT>> DatumShiftGridFile<NC,NT> castTo(
-            final Class<NC> coordinateType, final Class<NT> translationType)
-    {
-        super.getCoordinateUnit() .asType(coordinateType);
-        super.getTranslationUnit().asType(translationType);
-        return (DatumShiftGridFile<NC,NT>) this;
-    }
-
-    /**
      * Returns {@code true} if the given object is a grid containing the same data than this grid.
      *
      * @param  other  the other object to compare with this datum shift grid.
@@ -254,7 +315,7 @@ public abstract class DatumShiftGridFile
     @Debug
     @Override
     public String toString() {
-        return "DatumShiftGrid[\"" + files[0].getFileName() + "\"]";
+        return Utilities.toString(getClass(), "file", (files.length != 0) ? files[0] : null);
     }
 
 
@@ -262,6 +323,14 @@ public abstract class DatumShiftGridFile
 
     /**
      * An implementation of {@link DatumShiftGridFile} which stores the offset values in {@code float[]} arrays.
+     * This class is in internal package (not public API) because it makes the following assumptions:
+     * <ul>
+     *   <li>Values <var>x₀</var>, <var>y₀</var>, <var>Δx</var> and <var>Δy</var>
+     *       given to the constructor are in degrees and needs to be converted to radians.</li>
+     *   <li>Single floating-point precision ({@code float)} is sufficient.</li>
+     *   <li>Values were defined in base 10, usually in ASCII files. This assumption has an impact on conversions
+     *       from {@code float} to {@code double} performed by the {@link #getCellValue(int, int, int)} method.</li>
+     * </ul>
      *
      * @author  Martin Desruisseaux (Geomatys)
      * @since   0.7
@@ -313,16 +382,18 @@ public abstract class DatumShiftGridFile
          * Returns a new grid with the same geometry than this grid but different data arrays.
          */
         @Override
-        final DatumShiftGridFile<C,T> setData(final Object[] other) {
+        protected final DatumShiftGridFile<C,T> setData(final Object[] other) {
             return new Float<>(this, (float[][]) other);
         }
 
         /**
-         * Returns direct references (not cloned) to the data arrays.
+         * Returns direct references (not cloned) to the data arrays. This method is for cache management,
+         * {@link #equals(Object)} and {@link #hashCode()} implementations only and should not be invoked
+         * in other context.
          */
         @Override
         @SuppressWarnings("ReturnOfCollectionOrArrayField")
-        final Object[] getData() {
+        protected final Object[] getData() {
             return offsets;
         }
 

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -132,7 +132,7 @@ public abstract class GeocentricAffine e
         RX = createRotation(builder.addIdentifier("8608"), "X-axis rotation", "ex");
         RY = createRotation(builder.addIdentifier("8609"), "Y-axis rotation", "ey");
         RZ = createRotation(builder.addIdentifier("8610"), "Z-axis rotation", "ez");
-        DS = builder.addIdentifier("8611").addName("Scale difference").addName(Citations.OGC, "ppm").create(1, Units.PPM);
+        DS = builder.addIdentifier("8611").addName("Scale difference").addName(Citations.OGC, "ppm").create(0, Units.PPM);
     }
 
     /**

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -17,14 +17,21 @@
 package org.apache.sis.internal.referencing.provider;
 
 import javax.xml.bind.annotation.XmlTransient;
+import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.measure.Latitude;
+import org.apache.sis.measure.Units;
 
 
 /**
  * The provider for <cite>"Polar Stereographic (Variant A)"</cite> projection (EPSG:9810).
+ * Also used for the definition of Universal Polar Stereographic (UPS) projection.
  *
  * @author  Rueben Schulz (UBC)
  * @author  Martin Desruisseaux (Geomatys)
@@ -101,4 +108,64 @@ public final class PolarStereographicA e
     public PolarStereographicA() {
         super(PARAMETERS);
     }
+
+    /**
+     * False Easting and false Northing value used in Universal Polar Stereographic (UPS) projections.
+     * Represented as an integer for the convenience of Military Reference Grid System (MGRS) or other
+     * grid systems.
+     */
+    public static final int UPS_SHIFT = 2000000;
+
+    /**
+     * Sets the parameter values for a Universal Polar Stereographic projection
+     * and returns a suggested conversion name.
+     *
+     * <blockquote><table class="sis">
+     *   <caption>Universal Polar Stereographic parameters</caption>
+     *   <tr><th>Parameter name</th>                 <th>Value</th></tr>
+     *   <tr><td>Latitude of natural origin</td>     <td>90°N or 90°S</td></tr>
+     *   <tr><td>Longitude of natural origin</td>    <td>0°</td></tr>
+     *   <tr><td>Scale factor at natural origin</td> <td>0.994</td></tr>
+     *   <tr><td>False easting</td>                  <td>2000000 metres</td></tr>
+     *   <tr><td>False northing</td>                 <td>2000000 metres</td></tr>
+     * </table></blockquote>
+     *
+     * @param  group  the parameters for which to set the values.
+     * @param  north  {@code true} for North pole, or {@code false} for South pole.
+     * @return a name like <cite>"Universal Polar Stereographic North"</cite>,
+     *         depending on the arguments given to this method.
+     *
+     * @since 0.8
+     */
+    public static String setParameters(final ParameterValueGroup group, final boolean north) {
+        group.parameter(Constants.LATITUDE_OF_ORIGIN).setValue(north ? Latitude.MAX_VALUE : Latitude.MIN_VALUE, Units.DEGREE);
+        group.parameter(Constants.CENTRAL_MERIDIAN)  .setValue(0,         Units.DEGREE);
+        group.parameter(Constants.SCALE_FACTOR)      .setValue(0.994,     Units.UNITY);
+        group.parameter(Constants.FALSE_EASTING)     .setValue(UPS_SHIFT, Units.METRE);
+        group.parameter(Constants.FALSE_NORTHING)    .setValue(UPS_SHIFT, Units.METRE);
+        return "Universal Polar Stereographic " + (north ? "North" : "South");
+    }
+
+    /**
+     * If the given parameter values are those of a Universal Polar Stereographic projection,
+     * returns -1 for South pole or +1 for North pole. Otherwise returns 0. It is caller's
+     * responsibility to verify that the operation method is {@value #NAME}.
+     *
+     * @param  group  the Transverse Mercator projection parameters.
+     * @return +1 if UPS north, -1 if UPS south, or 0 if the given parameters are not for a UPS projection.
+     *
+     * @since 0.8
+     */
+    public static int isUPS(final ParameterValueGroup group) {
+        if (Numerics.epsilonEqual(group.parameter(Constants.SCALE_FACTOR)    .doubleValue(Units.UNITY),     0.994, Numerics.COMPARISON_THRESHOLD) &&
+            Numerics.epsilonEqual(group.parameter(Constants.FALSE_EASTING)   .doubleValue(Units.METRE), UPS_SHIFT, Formulas.LINEAR_TOLERANCE) &&
+            Numerics.epsilonEqual(group.parameter(Constants.FALSE_NORTHING)  .doubleValue(Units.METRE), UPS_SHIFT, Formulas.LINEAR_TOLERANCE) &&
+            Numerics.epsilonEqual(group.parameter(Constants.CENTRAL_MERIDIAN).doubleValue(Units.DEGREE),        0, Formulas.ANGULAR_TOLERANCE))
+        {
+            final double φ = group.parameter(Constants.LATITUDE_OF_ORIGIN).doubleValue(Units.DEGREE);
+            if (Numerics.epsilonEqual(φ, Latitude.MAX_VALUE, Formulas.ANGULAR_TOLERANCE)) return +1;
+            if (Numerics.epsilonEqual(φ, Latitude.MIN_VALUE, Formulas.ANGULAR_TOLERANCE)) return -1;
+        }
+        return 0;
+    }
 }

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -25,7 +25,9 @@ import org.apache.sis.parameter.Paramete
 import org.apache.sis.parameter.ParameterBuilder;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.operation.projection.NormalizedProjection;
+import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.measure.Units;
 
@@ -49,14 +51,6 @@ public final class TransverseMercator ex
     private static final long serialVersionUID = -3386587506686432398L;
 
     /**
-     * Width of a Universal Transverse Mercator (UTM) zone, in degrees.
-     *
-     * @see #zone(double)
-     * @see #centralMeridian(int)
-     */
-    private static final double ZONE_WIDTH = 6;
-
-    /**
      * The {@value} string, which is also the EPSG name for this projection.
      */
     public static final String NAME = "Transverse Mercator";
@@ -138,79 +132,312 @@ public final class TransverseMercator ex
     }
 
     /**
-     * Sets the parameter values for a Transverse Mercator projection and returns a suggested conversion name.
-     *
-     * <blockquote><table class="sis">
-     *   <caption>Transverse Mercator parameters</caption>
-     *   <tr><th>Parameter name</th>                 <th>Value</th></tr>
-     *   <tr><td>Latitude of natural origin</td>     <td>Given latitude, or 0° if UTM projection</td></tr>
-     *   <tr><td>Longitude of natural origin</td>    <td>Given longitude, optionally snapped to a UTM central meridian</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>
+     * Computes zone numbers and central meridian.
      *
-     * @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 <cite>"Transverse Mercator"</cite> or <cite>"UTM zone 10N"</cite>,
-     *         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,  Units.DEGREE);
-        group.parameter(Constants.CENTRAL_MERIDIAN)  .setValue(longitude, Units.DEGREE);
-        group.parameter(Constants.SCALE_FACTOR)      .setValue(0.9996, Units.UNITY);
-        group.parameter(Constants.FALSE_EASTING)     .setValue(500000, Units.METRE);
-        group.parameter(Constants.FALSE_NORTHING)    .setValue(isSouth ? 10000000 : 0, Units.METRE);
-        return name;
-    }
+     * @author  Martin Desruisseaux (Geomatys)
+     * @since   0.8
+     * @version 0.8
+     * @module
+     */
+    public static enum Zoner {
+        /**
+         * Universal Transverse Mercator (UTM) projection zones.
+         * The zone computation includes special cases for Norway and Svalbard.
+         *
+         * <blockquote><table class="sis">
+         *   <caption>Universal Transverse Mercator 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>Given longitude snapped to a UTM central meridian</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>
+         */
+        UTM(Longitude.MIN_VALUE, 6, 0.9996, 500000, 10000000) {
+            /** Computes the zone from a meridian in the zone. */
+            @Override public int zone(final double φ, final double λ) {
+                int zone = super.zone(φ, λ);
+                switch (zone) {
+                    /*
+                     * Between 56° and 64°, zone  32 is widened to 9° at the expense of zone 31 to accommodate Norway.
+                     * Between 72° and 84°, zones 33 and 35 are widened to 12° to accommodate Svalbard. To compensate,
+                     * zones 31 and 37 are widened to 9° and zones 32, 34, and 36 are eliminated.
+                     * In this switch statement, only the zones that are reduced or eliminated needs to appear.
+                     */
+                    case 31: if (isNorway  (φ)) {if (λ >=  3) zone++;             } break;   //  3° is zone 31 central meridian.
+                    case 32: if (isSvalbard(φ)) {if (λ >=  9) zone++; else zone--;} break;   //  9° is zone 32 central meridian.
+                    case 34: if (isSvalbard(φ)) {if (λ >= 21) zone++; else zone--;} break;   // 21° is zone 34 central meridian.
+                    case 36: if (isSvalbard(φ)) {if (λ >= 33) zone++; else zone--;} break;   // 33° is zone 36 central meridian.
+                }
+                return zone;
+            }
+
+            /** Indicates whether the given zone needs to be handled in a special way for the given latitude. */
+            @Override public boolean isSpecialCase(final int zone, final double φ) {
+                if (zone >= 31 && zone <= 37) {
+                    return isSvalbard(φ) || (zone <= 32 && isNorway(φ));
+                }
+                return false;
+            }
+
+            /** Indicates whether the given geographic area intersects the regions that need to be handled in a special way. */
+            @Override public boolean isSpecialCase(final double φmin, final double φmax, final double λmin, final double λmax) {
+                if (φmax >= NORWAY_BOUNDS && φmin < NORTH_BOUNDS) {
+                    return super.zone(0, λmax) >= 31 && super.zone(0, λmin) <= 37;
+                }
+                return false;
+            }
+        },
+
+        /**
+         * Modified Transverse Mercator (MTM) projection zones.
+         * This projection is used in Canada only.
+         *
+         * <blockquote><table class="sis">
+         *   <caption>Modified Transverse Mercator 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>Given longitude snapped to a MTM central meridian</td></tr>
+         *   <tr><td>Scale factor at natural origin</td> <td>0.9999</td></tr>
+         *   <tr><td>False easting</td>                  <td>304800 metres</td></tr>
+         *   <tr><td>False northing</td>                 <td>0 metres</td></tr>
+         * </table></blockquote>
+         */
+        MTM(-51.5, -3, 0.9999, 304800, Double.NaN),
 
-    /**
-     * 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.
-    }
+        /**
+         * Like UTM, but allows <cite>latitude of origin</cite> and <cite>central meridian</cite> to be anywhere.
+         * The given central meridian is not snapped to the UTM zone center and no special case is applied for
+         * Norway or Svalbard.
+         *
+         * <p>This zoner matches the behavior of {@code AUTO(2):42002} authority code specified in the
+         * OGC <cite>Web Map Service</cite> (WMS) specification.</p>
+         */
+        ANY(Longitude.MIN_VALUE, 6, 0.9996, 500000, 10000000);
 
-    /**
-     * 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;
+        /**
+         * Longitude of the beginning of zone 1. This is the westmost longitude if {@link #width} is positive,
+         * or the eastmost longitude if {@code width} is negative.
+         */
+        public final double origin;
+
+        /**
+         * Width of a zone, in degrees of longitude.
+         * Positive if zone numbers are increasing eastward, or negative if increasing westwards.
+         *
+         * @see #zone(double)
+         * @see #centralMeridian(int)
+         */
+        public final double width;
+
+        /**
+         * The scale factor of zoned projections.
+         */
+        public final double scale;
+
+        /**
+         * The false easting of zoned projections, in metres.
+         */
+        public final double easting;
+
+        /**
+         * The false northing in South hemisphere of zoned projection, in metres.
+         */
+        public final double northing;
+
+        /**
+         * Creates a new instance for computing zones using the given parameters.
+         */
+        private Zoner(final double origin, final double width, final double scale, final double easting, final double northing) {
+            this.origin   = origin;
+            this.width    = width;
+            this.scale    = scale;
+            this.easting  = easting;
+            this.northing = northing;
+        }
+
+        /**
+         * Sets the parameter values for a Transverse Mercator projection and returns a suggested conversion name.
+         *
+         * <blockquote><table class="sis">
+         *   <caption>Transverse Mercator parameters</caption>
+         *   <tr><th>Parameter name</th>                 <th>Value</th></tr>
+         *   <tr><td>Latitude of natural origin</td>     <td>Given latitude, or 0° if zoned projection</td></tr>
+         *   <tr><td>Longitude of natural origin</td>    <td>Given longitude, optionally snapped to a zone central meridian</td></tr>
+         *   <tr><td>Scale factor at natural origin</td> <td>0.9996 for UTM or 0.9999 for MTM</td></tr>
+         *   <tr><td>False easting</td>                  <td>500000 metres for UTM or 304800 metres for MTM</td></tr>
+         *   <tr><td>False northing</td>                 <td>0 (North hemisphere) or 10000000 (South hemisphere) metres</td></tr>
+         * </table></blockquote>
+         *
+         * @param  group      the parameters for which to set the values.
+         * @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 <cite>"Transverse Mercator"</cite> or <cite>"UTM zone 10N"</cite>,
+         *         depending on the arguments given to this method.
+         */
+        public final String setParameters(final ParameterValueGroup group, double latitude, double longitude) {
+            final boolean isSouth = MathFunctions.isNegative(latitude);
+            int zone = zone(latitude, longitude);
+            String name;
+            if (this == ANY) {
+                name = "UTM";
+                if (latitude != 0 || longitude != centralMeridian(zone)) {
+                    name = NAME;
+                    zone = 0;
+                }
+            } else {
+                name      = name();
+                latitude  = 0;
+                longitude = centralMeridian(zone);
+            }
+            if (zone != 0) {
+                name = name + " zone " + zone + (isSouth ? 'S' : 'N');
+            }
+            group.parameter(Constants.LATITUDE_OF_ORIGIN).setValue(latitude,  Units.DEGREE);
+            group.parameter(Constants.CENTRAL_MERIDIAN)  .setValue(longitude, Units.DEGREE);
+            group.parameter(Constants.SCALE_FACTOR)      .setValue(scale,     Units.UNITY);
+            group.parameter(Constants.FALSE_EASTING)     .setValue(easting,   Units.METRE);
+            group.parameter(Constants.FALSE_NORTHING)    .setValue(isSouth ? northing : 0, Units.METRE);
+            return name;
+        }
+
+        /**
+         * If the given parameter values are those of a zoned projection, returns the zone number (negative if South).
+         * Otherwise returns 0. It is caller's responsibility to verify that the operation method is {@value #NAME}.
+         *
+         * @param  group  the Transverse Mercator projection parameters.
+         * @return zone number (positive if North, negative if South),
+         *         or 0 if the given parameters are not for a zoned projection.
+         */
+        public final int zone(final ParameterValueGroup group) {
+            if (Numerics.epsilonEqual(group.parameter(Constants.SCALE_FACTOR)      .doubleValue(Units.UNITY), scale,   Numerics.COMPARISON_THRESHOLD) &&
+                Numerics.epsilonEqual(group.parameter(Constants.FALSE_EASTING)     .doubleValue(Units.METRE), easting, Formulas.LINEAR_TOLERANCE) &&
+                Numerics.epsilonEqual(group.parameter(Constants.LATITUDE_OF_ORIGIN).doubleValue(Units.DEGREE),      0, Formulas.ANGULAR_TOLERANCE))
+            {
+                double v = group.parameter(Constants.FALSE_NORTHING).doubleValue(Units.METRE);
+                final boolean isNorth = Numerics.epsilonEqual(v, 0, Formulas.LINEAR_TOLERANCE);
+                if (isNorth || Numerics.epsilonEqual(v, northing, Formulas.LINEAR_TOLERANCE)) {
+                    v = group.parameter(Constants.CENTRAL_MERIDIAN).doubleValue(Units.DEGREE);
+                    int zone = zone(0, v);
+                    if (Numerics.epsilonEqual(centralMeridian(zone), v, Formulas.ANGULAR_TOLERANCE)) {
+                        if (!isNorth) zone = -zone;
+                        return zone;
+                    }
+                }
+            }
+            return 0;
+        }
+
+        /**
+         * Computes the zone from a meridian in the zone.
+         *
+         * @param  φ  a latitude for which to get the zone. Used for taking in account the special cases.
+         * @param  λ  a meridian inside the desired zone, in degrees relative to Greenwich.
+         *            Positive longitudes are toward east, and negative longitudes toward west.
+         * @return the zone number numbered from 1 inclusive, or 0 if the given central meridian was NaN.
+         */
+        public int zone(final double φ, final double λ) {
+            double z = (λ - origin) / width;                                              // Zone number with fractional part.
+            final double count = (Longitude.MAX_VALUE - Longitude.MIN_VALUE) / width;
+            z -= Math.floor(z / count) * count;                                           // Roll in the [0 … 60) range.
+            /*
+             * 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. We cast only after addition in order to handle NaN as documented.
+             */
+            return (int) (z + 1);
+        }
+
+        /**
+         * Returns the number of zones.
+         *
+         * @return number of zones.
+         */
+        public final int zoneCount() {
+            return (int) ((Longitude.MAX_VALUE - Longitude.MIN_VALUE) / width);
+        }
+
+        /**
+         * Computes the central meridian of a given zone.
+         *
+         * @param  zone  the zone as a number starting with 1.
+         * @return the central meridian of the given zone.
+         */
+        public final double centralMeridian(final int zone) {
+            return (zone - 0.5) * width + origin;
+        }
+
+        /**
+         * Indicates whether the given zone needs to be handled in a special way for the given latitude.
+         *
+         * @param  zone  the zone to test if it is a special case.
+         * @param  φ     the latitude for which to test if there is a special case.
+         * @return whether the given zone at the given latitude is a special case.
+         */
+        public boolean isSpecialCase(final int zone, final double φ) {
+            return false;
+        }
+
+        /**
+         * Indicates whether the given geographic area intersects the regions that need to be handled in a special way.
+         *
+         * @param  φmin  southernmost latitude in degrees.
+         * @param  φmax  northernmost latitude in degrees.
+         * @param  λmin  westernmost longitude in degrees.
+         * @param  λmax  easternmost longitude in degrees.
+         * @return whether the given area intersects a region that needs to be handled as a special case.
+         */
+        public boolean isSpecialCase(final double φmin, final double φmax, final double λmin, final double λmax) {
+            return false;
+        }
+
+        /**
+         * First exception in UTM projection, corresponding to latitude band V.
+         * This method is public for {@code MilitaryGridReferenceSystemTest.verifyZonerConsistency()} purpose only.
+         *
+         * @param  φ  the latitude in degrees to test.
+         * @return whether the given latitude is in the Norway latitude band.
+         */
+        public static boolean isNorway(final double φ) {
+            return (φ >= NORWAY_BOUNDS) && (φ < 64);
+        }
+
+        /**
+         * Second exception in UTM projection, corresponding to latitude band X.
+         * This method is public for {@code MilitaryGridReferenceSystemTest.verifyZonerConsistency()} purpose only.
+         *
+         * @param  φ  the latitude in degrees to test.
+         * @return whether the given latitude is in the Svalbard latitude band.
+         */
+        public static boolean isSvalbard(final double φ) {
+            return (φ >= SVALBARD_BOUNDS) && (φ < NORTH_BOUNDS);
+        }
+
+        /**
+         * Southernmost bound of the first latitude band ({@code 'C'}), inclusive.
+         *
+         * @see #NORTH_BOUNDS
+         */
+        public static final double SOUTH_BOUNDS = -80;
+
+        /**
+         * Southernmost bounds (inclusive) of the latitude band that contains Norway ({@code 'V'}).
+         * This is the first latitude band where we may need to handle special cases (Norway and Svalbard).
+         */
+        private static final double NORWAY_BOUNDS = 56;
+
+        /**
+         * Southernmost bounds (inclusive) of the last latitude band, which contains Svalbard.
+         * This latitude band is 12° height instead of 8°.
+         */
+        public static final double SVALBARD_BOUNDS = 72;
+
+        /**
+         * Northernmost bound of the last latitude band ({@code 'X'}), exclusive.
+         *
+         * @see #SOUTH_BOUNDS
+         */
+        public static final double NORTH_BOUNDS = 84;
     }
 }

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -28,6 +28,7 @@ import org.opengis.parameter.ParameterVa
 import org.opengis.parameter.ParameterDescriptor;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Numbers;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.measure.Range;
@@ -67,7 +68,7 @@ import static org.apache.sis.util.Argume
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
  * @since   0.4
- * @version 0.6
+ * @version 0.8
  * @module
  *
  * @see DefaultParameterValue
@@ -468,7 +469,7 @@ public class DefaultParameterDescriptor<
                     final ParameterDescriptor<?> that = (ParameterDescriptor<?>) object;
                     return getValueClass() == that.getValueClass() &&
                            Objects.deepEquals(getDefaultValue(), that.getDefaultValue()) &&
-                           Objects.equals(getUnit(), that.getUnit()) &&
+                           Utilities.deepEquals(getUnit(), that.getUnit(), mode) &&
                            (isHeuristicMatchForName(that.getName().getCode()) ||
                             IdentifiedObjects.isHeuristicMatchForName(that, getName().getCode()));
                 }
@@ -481,7 +482,7 @@ public class DefaultParameterDescriptor<
                            Objects.    equals(getMinimumValue(), that.getMinimumValue()) &&
                            Objects.    equals(getMaximumValue(), that.getMaximumValue()) &&
                            Objects.deepEquals(getDefaultValue(), that.getDefaultValue()) &&
-                           Objects.    equals(getUnit(),         that.getUnit());
+                           Utilities.deepEquals(getUnit(),       that.getUnit(), mode);
                 }
                 case STRICT: {
                     final DefaultParameterDescriptor<?> that = (DefaultParameterDescriptor<?>) object;

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -118,7 +118,7 @@ import static org.apache.sis.util.Utilit
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.7
+ * @version 0.8
  * @module
  *
  * @see DefaultParameterDescriptor
@@ -787,8 +787,8 @@ public class DefaultParameterValue<T> ex
             } else if (object instanceof ParameterValue<?>) {
                 final ParameterValue<?> that = (ParameterValue<?>) object;
                 return deepEquals(getDescriptor(), that.getDescriptor(), mode) &&
-                       deepEquals(getValue(),      that.getValue(), mode) &&
-                       Objects.equals(getUnit(),   that.getUnit());
+                       deepEquals(getValue(),      that.getValue(),      mode) &&
+                       deepEquals(getUnit(),       that.getUnit(),       mode);
             }
         }
         return false;

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -97,9 +97,11 @@ import static org.apache.sis.util.collec
  *   <tr><td><code>{@linkplain IdentifiedObject}[]</code></td><td>Accepted only for {@link ContentLevel#NAME_SUMMARY}.</td></tr>
  * </table>
  *
- * <div class="warning"><b>Limitation:</b>
- * Current implementation supports only formatting, not parsing.
- * </div>
+ * <p><b>Limitations:</b></p>
+ * <ul>
+ *   <li>The current implementation can only format features — parsing is not yet implemented.</li>
+ *   <li>{@code ParameterFormat}, like most {@code java.text.Format} subclasses, is not thread-safe.</li>
+ * </ul>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
@@ -1012,7 +1014,7 @@ public class ParameterFormat extends Tab
     @Override
     public Object parse(final CharSequence text, final ParsePosition pos) throws ParseException {
         throw new ParseException(Errors.getResources(displayLocale)
-                .getString(Errors.Keys.UnsupportedOperation_1, "parse"), 0);
+                .getString(Errors.Keys.UnsupportedOperation_1, "parse"), pos.getIndex());
     }
 
     /**

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing;
 
 import java.util.Map;
+import java.util.Objects;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@@ -34,9 +35,6 @@ import org.apache.sis.internal.metadata.
 import static org.apache.sis.util.Utilities.deepEquals;
 import static org.apache.sis.util.collection.Containers.property;
 
-// Branch-dependent imports
-import java.util.Objects;
-
 
 /**
  * Description of a spatial and temporal reference system used by a dataset.

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -145,23 +145,29 @@ public final class CRS extends Static {
      *
      * <blockquote><table class="sis">
      *   <caption>Minimal set of supported authority codes</caption>
-     *   <tr><th>Code</th>      <th>Enum</th>                            <th>CRS Type</th>      <th>Description</th></tr>
-     *   <tr><td>CRS:27</td>    <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td>    <td>Like EPSG:4267 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
-     *   <tr><td>CRS:83</td>    <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td>    <td>Like EPSG:4269 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
-     *   <tr><td>CRS:84</td>    <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td>    <td>Like EPSG:4326 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
-     *   <tr><td>EPSG:4047</td> <td>{@link CommonCRS#SPHERE SPHERE}</td> <td>Geographic</td>    <td>GRS 1980 Authalic Sphere</td></tr>
-     *   <tr><td>EPSG:4230</td> <td>{@link CommonCRS#ED50   ED50}</td>   <td>Geographic</td>    <td>European Datum 1950</td></tr>
-     *   <tr><td>EPSG:4258</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic</td>    <td>European Terrestrial Reference Frame 1989</td></tr>
-     *   <tr><td>EPSG:4267</td> <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td>    <td>North American Datum 1927</td></tr>
-     *   <tr><td>EPSG:4269</td> <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td>    <td>North American Datum 1983</td></tr>
-     *   <tr><td>EPSG:4322</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geographic</td>    <td>World Geodetic System 1972</td></tr>
-     *   <tr><td>EPSG:4326</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td>    <td>World Geodetic System 1984</td></tr>
-     *   <tr><td>EPSG:4936</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geocentric</td>    <td>European Terrestrial Reference Frame 1989</td></tr>
-     *   <tr><td>EPSG:4937</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic 3D</td> <td>European Terrestrial Reference Frame 1989</td></tr>
-     *   <tr><td>EPSG:4978</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geocentric</td>    <td>World Geodetic System 1984</td></tr>
-     *   <tr><td>EPSG:4979</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic 3D</td> <td>World Geodetic System 1984</td></tr>
-     *   <tr><td>EPSG:4984</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geocentric</td>    <td>World Geodetic System 1972</td></tr>
-     *   <tr><td>EPSG:4985</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geographic 3D</td> <td>World Geodetic System 1972</td></tr>
+     *   <tr><th>Code</th>      <th>Enum</th>                            <th>CRS Type</th>        <th>Description</th></tr>
+     *   <tr><td>CRS:27</td>    <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td>      <td>Like EPSG:4267 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+     *   <tr><td>CRS:83</td>    <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td>      <td>Like EPSG:4269 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+     *   <tr><td>CRS:84</td>    <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td>      <td>Like EPSG:4326 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+     *   <tr><td>EPSG:4047</td> <td>{@link CommonCRS#SPHERE SPHERE}</td> <td>Geographic</td>      <td>GRS 1980 Authalic Sphere</td></tr>
+     *   <tr><td>EPSG:4230</td> <td>{@link CommonCRS#ED50   ED50}</td>   <td>Geographic</td>      <td>European Datum 1950</td></tr>
+     *   <tr><td>EPSG:4258</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic</td>      <td>European Terrestrial Reference Frame 1989</td></tr>
+     *   <tr><td>EPSG:4267</td> <td>{@link CommonCRS#NAD27  NAD27}</td>  <td>Geographic</td>      <td>North American Datum 1927</td></tr>
+     *   <tr><td>EPSG:4269</td> <td>{@link CommonCRS#NAD83  NAD83}</td>  <td>Geographic</td>      <td>North American Datum 1983</td></tr>
+     *   <tr><td>EPSG:4322</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geographic</td>      <td>World Geodetic System 1972</td></tr>
+     *   <tr><td>EPSG:4326</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic</td>      <td>World Geodetic System 1984</td></tr>
+     *   <tr><td>EPSG:4936</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geocentric</td>      <td>European Terrestrial Reference Frame 1989</td></tr>
+     *   <tr><td>EPSG:4937</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic 3D</td>   <td>European Terrestrial Reference Frame 1989</td></tr>
+     *   <tr><td>EPSG:4978</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geocentric</td>      <td>World Geodetic System 1984</td></tr>
+     *   <tr><td>EPSG:4979</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Geographic 3D</td>   <td>World Geodetic System 1984</td></tr>
+     *   <tr><td>EPSG:4984</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geocentric</td>      <td>World Geodetic System 1972</td></tr>
+     *   <tr><td>EPSG:4985</td> <td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Geographic 3D</td>   <td>World Geodetic System 1972</td></tr>
+     *   <tr><td>EPSG:5041</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UPS North (E,N)</td></tr>
+     *   <tr><td>EPSG:5042</td> <td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UPS South (E,N)</td></tr>
+     *   <tr><td>EPSG:322##</td><td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Projected</td>       <td>WGS 72 / UTM zone ##N</td></tr>
+     *   <tr><td>EPSG:323##</td><td>{@link CommonCRS#WGS72  WGS72}</td>  <td>Projected</td>       <td>WGS 72 / UTM zone ##S</td></tr>
+     *   <tr><td>EPSG:326##</td><td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UTM zone ##N</td></tr>
+     *   <tr><td>EPSG:327##</td><td>{@link CommonCRS#WGS84  WGS84}</td>  <td>Projected</td>       <td>WGS 84 / UTM zone ##S</td></tr>
      *   <tr><td>EPSG:5715</td> <td>{@link CommonCRS.Vertical#DEPTH DEPTH}</td> <td>Vertical</td> <td>Mean Sea Level depth</td></tr>
      *   <tr><td>EPSG:5714</td> <td>{@link CommonCRS.Vertical#MEAN_SEA_LEVEL MEAN_SEA_LEVEL}</td> <td>Vertical</td> <td>Mean Sea Level height</td></tr>
      * </table></blockquote>
@@ -960,9 +966,11 @@ check:  while (lower != 0 || upper != di
                     crs = components.get(i);
                     dimension = crs.getCoordinateSystem().getDimension();
                     if (lower < dimension) {
-                        // The requested dimensions may intersect the dimension of this CRS.
-                        // The outer loop will perform the verification, and eventually go
-                        // down again in the tree of sub-components.
+                        /*
+                         * The requested dimensions may intersect the dimension of this CRS.
+                         * The outer loop will perform the verification, and eventually go
+                         * down again in the tree of sub-components.
+                         */
                         continue check;
                     }
                     lower -= dimension;

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] Fri Mar 31 18:49:16 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;
@@ -144,8 +149,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.
@@ -162,8 +167,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.
@@ -188,8 +193,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.
@@ -206,8 +211,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.
@@ -231,8 +236,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.
@@ -249,8 +254,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
@@ -268,8 +273,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.
@@ -311,6 +316,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;
@@ -356,12 +366,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.
@@ -373,18 +392,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<>();
     }
 
     /**
@@ -404,15 +425,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)));
     }
 
     /**
@@ -724,6 +811,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() {
@@ -886,90 +974,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.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 = Short.toUnsignedInt(isSouth ? southUPS : northUPS);
+            } else if (zone >= firstZone && zone <= lastZone) {
                 code = Short.toUnsignedInt(isSouth ? southUTM : northUTM);
-                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 = cachedUTM.putIfAbsent(key, crs);
+            synchronized (cachedProjections) {
+                other = cachedProjections.putIfAbsent(key, crs);
             }
             if (other != null) {
                 return other;



Mime
View raw message