This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 49ceb864506fdf6cb48235375195951b53712f0a
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Apr 27 19:06:55 2022 +0200

    Replace the way variants of map projections are represented, using 
enumeration instead of a single integer.
    This change makes possible to attach more information to the enumeration 
value.
    This is a step for making easier to add support for variants such as 
"Mercator Auxiliary Sphere".
---
 .../referencing/provider/AbstractMercator.java     |  12 +-
 .../internal/referencing/provider/Mercator1SP.java |   9 +-
 .../internal/referencing/provider/Mercator2SP.java |   9 +-
 .../provider/MercatorAuxiliarySphere.java          |  80 +++++++++++++
 .../referencing/provider/MillerCylindrical.java    |   2 +-
 .../referencing/provider/PseudoMercator.java       |   2 +-
 .../operation/projection/AlbersEqualArea.java      |   2 +-
 .../operation/projection/AzimuthalEquidistant.java |   2 +-
 .../operation/projection/CassiniSoldner.java       |  79 ++++++++-----
 .../operation/projection/CylindricalEqualArea.java |  55 ++++++---
 .../operation/projection/Initializer.java          |  42 ++++---
 .../projection/LambertConicConformal.java          |  91 +++++++++------
 .../referencing/operation/projection/Mercator.java | 124 +++++++++++++++------
 .../projection/ModifiedAzimuthalEquidistant.java   |   2 +-
 .../operation/projection/Mollweide.java            |  29 ++++-
 .../operation/projection/NormalizedProjection.java |  50 ++++-----
 .../operation/projection/ObliqueMercator.java      |  80 ++++++++-----
 .../operation/projection/ObliqueStereographic.java |   2 +-
 .../operation/projection/Orthographic.java         |   2 +-
 .../operation/projection/PolarStereographic.java   |  79 ++++++++-----
 .../operation/projection/Polyconic.java            |   2 +-
 .../operation/projection/ProjectionVariant.java    |  58 ++++++++++
 .../operation/projection/SatelliteTracking.java    |   2 +-
 .../operation/projection/Sinusoidal.java           |   2 +-
 .../operation/projection/TransverseMercator.java   |  36 ++++--
 .../operation/projection/ZonedGridSystem.java      |   2 +-
 ...g.opengis.referencing.operation.OperationMethod |   1 +
 .../referencing/provider/ProvidersTest.java        |   1 +
 .../operation/projection/InitializerTest.java      |   2 +-
 .../sis/referencing/operation/projection/NoOp.java |   4 +-
 30 files changed, 614 insertions(+), 249 deletions(-)

diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractMercator.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractMercator.java
index c792f94e74..417dfb6b39 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractMercator.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractMercator.java
@@ -31,7 +31,7 @@ import 
org.apache.sis.referencing.operation.projection.NormalizedProjection;
  * Base class of providers for all Mercator projections, and for Mercator-like 
projections.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.6
+ * @version 1.2
  * @since   0.6
  * @module
  */
@@ -78,15 +78,19 @@ class AbstractMercator extends MapProjection {
 
     /**
      * Returns the given descriptor as an array, excluding the two first 
elements which are assumed
-     * to be the axis lengths.This method assumes that all elements in the 
given list are instances
+     * to be the axis lengths. This method assumes that all elements in the 
given list are instances
      * of {@link ParameterDescriptor}.
      *
+     * @param  descriptors  the descriptors to return as an array.
+     * @param  expansion    number of additional elements in the returned 
array.
+     * @return the given descriptors without the two first elements.
      * @throws ArrayStoreException if a {@code descriptors} element is not an 
instance of {@link ParameterDescriptor}.
      */
     @SuppressWarnings("SuspiciousToArrayCall")
-    static ParameterDescriptor<?>[] toArray(List<GeneralParameterDescriptor> 
descriptors) {
+    static ParameterDescriptor<?>[] toArray(List<GeneralParameterDescriptor> 
descriptors, int expansion) {
         descriptors = descriptors.subList(2, descriptors.size());
-        return descriptors.toArray(new 
ParameterDescriptor<?>[descriptors.size()]);  // Intentional array subtype.
+        // Intentionallly use an array subtype in call to `toArray(…)`.
+        return descriptors.toArray(new 
ParameterDescriptor<?>[descriptors.size() + expansion]);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator1SP.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator1SP.java
index d84fd0e9c4..7d165e8394 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator1SP.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator1SP.java
@@ -30,7 +30,7 @@ import org.apache.sis.metadata.iso.citation.Citations;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Rueben Schulz (UBC)
- * @version 0.6
+ * @version 1.2
  *
  * @see <a 
href="http://geotiff.maptools.org/proj_list/mercator_1sp.html";>GeoTIFF 
parameters for Mercator 1SP</a>
  *
@@ -44,6 +44,11 @@ public final class Mercator1SP extends AbstractMercator {
      */
     private static final long serialVersionUID = -5886510621481710072L;
 
+    /**
+     * The EPSG identifier, to be preferred to the name when available.
+     */
+    public static final String IDENTIFIER = "9804";
+
     /**
      * The operation parameter descriptor for the <cite>Latitude of natural 
origin</cite> (φ₀) parameter value.
      * In theory, this parameter should not be used and its value should be 0 
in all cases.
@@ -126,7 +131,7 @@ public final class Mercator1SP extends AbstractMercator {
                 .addName(Citations.PROJ4,   "k"));
 
         PARAMETERS = builder
-                .addIdentifier(              "9804")                        // 
The ellipsoidal case
+                .addIdentifier(              IDENTIFIER)                    // 
The ellipsoidal case
                 .addName(                    "Mercator (variant A)")        // 
Starting from EPSG version 7.6
                 .addName(                    "Mercator (1SP)")              // 
Prior to EPSG version 7.6
                 .addName(Citations.OGC,      "Mercator_1SP")
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java
index 552953843a..7b64cae079 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java
@@ -31,7 +31,7 @@ import org.apache.sis.metadata.iso.citation.Citations;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Rueben Schulz (UBC)
- * @version 0.6
+ * @version 1.2
  *
  * @see <a 
href="http://geotiff.maptools.org/proj_list/mercator_2sp.html";>GeoTIFF 
parameters for Mercator 2SP</a>
  *
@@ -45,6 +45,11 @@ public final class Mercator2SP extends AbstractMercator {
      */
     private static final long serialVersionUID = 6356028352681135786L;
 
+    /**
+     * The EPSG identifier, to be preferred to the name when available.
+     */
+    public static final String IDENTIFIER = "9805";
+
     /*
      * ACCESS POLICY: Only formal EPSG parameters shall be public.
      * Parameters that we add ourselves should be package-privated.
@@ -126,7 +131,7 @@ public final class Mercator2SP extends AbstractMercator {
                 .setRemarks(remarks).setDeprecated(true));
 
         PARAMETERS = builder
-                .addIdentifier(             "9805")
+                .addIdentifier(             IDENTIFIER)
                 .addName(                   "Mercator (variant B)")     // 
Starting from EPSG version 7.6
                 .addName(                   "Mercator (2SP)")           // 
Prior to EPSG version 7.6
                 .addName(Citations.OGC,     "Mercator_2SP")
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorAuxiliarySphere.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorAuxiliarySphere.java
new file mode 100644
index 0000000000..0dd127e112
--- /dev/null
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorAuxiliarySphere.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.referencing.provider;
+
+import javax.xml.bind.annotation.XmlTransient;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.metadata.iso.citation.Citations;
+
+
+/**
+ * The provider for <cite>"Mercator Auxiliary Sphere"</cite> projection 
(defined by ESRI).
+ * This is often equivalent to {@link PseudoMercator}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+@XmlTransient
+public final class MercatorAuxiliarySphere extends AbstractMercator {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -8000127893422503988L;
+
+    /**
+     * The operation parameter descriptor for the <cite>Auxiliary sphere 
type</cite> parameter value.
+     * Valid values are:
+     *
+     * <ul>
+     *   <li>0 = use semi-major axis or radius of the geographic coordinate 
system.</li>
+     *   <li>1 = use semi-minor axis or radius.</li>
+     *   <li>2 = calculate and use authalic radius.</li>
+     *   <li>3 = use authalic radius and convert geodetic latitudes to 
authalic latitudes.</li>
+     * </ul>
+     *
+     * <!-- Generated by ParameterNameTableGenerator -->
+     * <table class="sis">
+     *   <caption>Parameter names</caption>
+     *   <tr><td> ESRI:    </td><td> Auxiliary_Sphere_Type </td></tr>
+     * </table>
+     */
+    public static final ParameterDescriptor<Integer> AUXILIARY_SPHERE_TYPE;
+
+    /**
+     * The group of all parameters expected by this coordinate operation.
+     */
+    private static final ParameterDescriptorGroup PARAMETERS;
+    static {
+        final ParameterBuilder builder = 
builder().setCodeSpace(Citations.ESRI, "ESRI");
+        AUXILIARY_SPHERE_TYPE = 
builder.addName("Auxiliary_Sphere_Type").createBounded(0, 3, 0);
+
+        final ParameterDescriptor<?>[] descriptors = 
toArray(MercatorSpherical.PARAMETERS.descriptors(), 1);
+        descriptors[descriptors.length - 1] = AUXILIARY_SPHERE_TYPE;
+        PARAMETERS = 
builder.addName("Mercator_Auxiliary_Sphere").createGroupForMapProjection(descriptors);
+    }
+
+    /**
+     * Constructs a new provider.
+     */
+    public MercatorAuxiliarySphere() {
+        super(PARAMETERS);
+    }
+}
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MillerCylindrical.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MillerCylindrical.java
index 5531117b39..3d42617b1a 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MillerCylindrical.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MillerCylindrical.java
@@ -71,7 +71,7 @@ public final class MillerCylindrical extends AbstractMercator 
{
                 .addIdentifier(Citations.GEOTIFF,  "20")
                 .addName      (Citations.PROJ4,    "mill")
                 .addIdentifier(Citations.MAP_INFO, "11")
-                
.createGroupForMapProjection(toArray(MercatorSpherical.PARAMETERS.descriptors()));
+                
.createGroupForMapProjection(toArray(MercatorSpherical.PARAMETERS.descriptors(),
 0));
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PseudoMercator.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PseudoMercator.java
index 46fc169070..56fe1330c7 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PseudoMercator.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PseudoMercator.java
@@ -50,7 +50,7 @@ public final class PseudoMercator extends AbstractMercator {
         PARAMETERS = builder()
                 .addIdentifier(IDENTIFIER)
                 .addName("Popular Visualisation Pseudo Mercator")
-                
.createGroupForMapProjection(toArray(MercatorSpherical.PARAMETERS.descriptors()));
+                
.createGroupForMapProjection(toArray(MercatorSpherical.PARAMETERS.descriptors(),
 0));
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java
index 03e0673d9f..fcfd6f0866 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java
@@ -111,7 +111,7 @@ public class AlbersEqualArea extends EqualAreaProjection {
         roles.put(ParameterRole.FALSE_EASTING,    EASTING_AT_FALSE_ORIGIN);
         roles.put(ParameterRole.FALSE_NORTHING,   NORTHING_AT_FALSE_ORIGIN);
         roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_FALSE_ORIGIN);
-        return new Initializer(method, parameters, roles, STANDARD_VARIANT);
+        return new Initializer(method, parameters, roles, null);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistant.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistant.java
index 397621ab27..e698d8c489 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistant.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistant.java
@@ -95,7 +95,7 @@ public class AzimuthalEquidistant extends 
NormalizedProjection {
         roles.put(ParameterRole.CENTRAL_MERIDIAN,                    
LONGITUDE_OF_ORIGIN);
         roles.put(ParameterRole.FALSE_EASTING,                       
FALSE_EASTING);
         roles.put(ParameterRole.FALSE_NORTHING,                      
FALSE_NORTHING);
-        return new Initializer(method, parameters, roles, STANDARD_VARIANT);
+        return new Initializer(method, parameters, roles, null);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CassiniSoldner.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CassiniSoldner.java
index 7c8b88c3a9..143fe0a8e5 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CassiniSoldner.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CassiniSoldner.java
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.EnumMap;
+import java.util.regex.Pattern;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.operation.MathTransform;
@@ -49,7 +50,7 @@ import static 
org.apache.sis.internal.referencing.provider.CassiniSoldner.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.1
  * @module
  */
@@ -57,7 +58,7 @@ public class CassiniSoldner extends MeridianArcBased {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 8306467537772990906L;
+    private static final long serialVersionUID = 616536461409425434L;
 
     /**
      * The hyperbolic variants of this projection. {@link #VANUA} is the 
special case
@@ -66,7 +67,37 @@ public class CassiniSoldner extends MeridianArcBased {
      *
      * @see #variant
      */
-    private static final byte HYPERBOLIC = 1, VANUA = 2;
+    private enum Variant implements ProjectionVariant {
+        /**
+         * The <cite>"Hyperbolic Cassini-Soldner"</cite> variant, which is 
non-invertible.
+         */
+        HYPERBOLIC(Pattern.compile(".*\\bHyperbolic\\b.*", 
Pattern.CASE_INSENSITIVE), HyperbolicCassiniSoldner.IDENTIFIER),
+
+        /**
+         * The special case of <cite>"Vanua Levu Grid"</cite> at φ₀=16°15′S.
+         * This is the only hyperbolic variant for which inverse projection is 
supported.
+         * This special case is detected by checking the value of the latitude 
of origin.
+         */
+        VANUA(HYPERBOLIC.operationName, HyperbolicCassiniSoldner.IDENTIFIER);
+
+        /** Name pattern for this variant.    */ private final Pattern 
operationName;
+        /** EPSG identifier for this variant. */ private final String  
identifier;
+        /** Creates a new enumeration value.  */
+        private Variant(final Pattern operationName, final String identifier) {
+            this.operationName = operationName;
+            this.identifier    = identifier;
+        }
+
+        /** The expected name pattern of an operation method for this variant. 
*/
+        @Override public Pattern getOperationNamePattern() {
+            return operationName;
+        }
+
+        /** EPSG identifier of an operation method for this variant. */
+        @Override public String getIdentifier() {
+            return identifier;
+        }
+    }
 
     /**
      * The latitude of {@link #VANUA} variant (16°15′S) in radians.
@@ -77,14 +108,14 @@ public class CassiniSoldner extends MeridianArcBased {
      * The type of Cassini-Soldner projection. Possible values are:
      *
      * <ul>
-     *   <li>{@link #STANDARD_VARIANT} if this projection is the standard 
variant.</li>
-     *   <li>{@link #HYPERBOLIC} if this projection is the "Hyperbolic 
Cassini-Soldner" case.</li>
-     *   <li>{@link #VANUA} if this projection is the "Hyperbolic 
Cassini-Soldner" case at φ₀=16°15′S.</li>
+     *   <li>{@code null} if this projection is the standard variant.</li>
+     *   <li>{@link Variant#HYPERBOLIC} if this projection is the "Hyperbolic 
Cassini-Soldner" case.</li>
+     *   <li>{@link Variant#VANUA} if this projection is the "Hyperbolic 
Cassini-Soldner" case at φ₀=16°15′S.</li>
      * </ul>
      *
      * Other cases may be added in the future.
      */
-    private final byte variant;
+    private final Variant variant;
 
     /**
      * Meridional distance <var>M</var> from equator to latitude of origin φ₀.
@@ -94,17 +125,6 @@ public class CassiniSoldner extends MeridianArcBased {
      */
     private final double M0;
 
-    /**
-     * Returns the variant of the projection based on the name and identifier 
of the given operation method.
-     * See {@link #variant} for the list of possible values.
-     */
-    private static byte getVariant(final OperationMethod method) {
-        if (identMatch(method, "(?i).*\\bHyperbolic\\b.*", 
HyperbolicCassiniSoldner.IDENTIFIER)) {
-            return HYPERBOLIC;
-        }
-        return STANDARD_VARIANT;
-    }
-
     /**
      * Creates a Cassini-Soldner projection from the given parameters.
      * The {@code method} argument can be the description of one of the 
following:
@@ -127,12 +147,13 @@ public class CassiniSoldner extends MeridianArcBased {
      */
     @Workaround(library="JDK", version="1.7")
     private static Initializer initializer(final OperationMethod method, final 
Parameters parameters) {
+        final Variant variant = variant(method, new Variant[] 
{Variant.HYPERBOLIC}, null);
         final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new 
EnumMap<>(ParameterRole.class);
         roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_ORIGIN);
         roles.put(ParameterRole.SCALE_FACTOR,     SCALE_FACTOR);
         roles.put(ParameterRole.FALSE_EASTING,    FALSE_EASTING);
         roles.put(ParameterRole.FALSE_NORTHING,   FALSE_NORTHING);
-        return new Initializer(method, parameters, roles, getVariant(method));
+        return new Initializer(method, parameters, roles, variant);
     }
 
     /**
@@ -142,14 +163,14 @@ public class CassiniSoldner extends MeridianArcBased {
         super(initializer);
         final double φ0 = 
toRadians(initializer.getAndStore(LATITUDE_OF_ORIGIN));
         M0 = distance(φ0, sin(φ0), cos(φ0));
-        if (initializer.variant == STANDARD_VARIANT) {
+        if (initializer.variant == null) {
             final MatrixSIS denormalize = 
getContextualParameters().getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
             denormalize.convertBefore(1, null, -distance(φ0, sin(φ0), 
cos(φ0)));
         } else if (abs(φ0 - VANUA_LATITUDE) <= ANGULAR_TOLERANCE) {
-            variant = VANUA;
+            variant = Variant.VANUA;
             return;
         }
-        variant = initializer.variant;
+        variant = (Variant) initializer.variant;
     }
 
     /**
@@ -167,7 +188,7 @@ public class CassiniSoldner extends MeridianArcBased {
      */
     @Override
     final String[] getInternalParameterNames() {
-        if (variant != STANDARD_VARIANT) {
+        if (variant != null) {
             return new String[] {"M₀"};
         } else {
             return super.getInternalParameterNames();
@@ -180,7 +201,7 @@ public class CassiniSoldner extends MeridianArcBased {
      */
     @Override
     final double[] getInternalParameterValues() {
-        if (variant != STANDARD_VARIANT) {
+        if (variant != null) {
             return new double[] {M0};
         } else {
             return super.getInternalParameterValues();
@@ -201,7 +222,7 @@ public class CassiniSoldner extends MeridianArcBased {
     @Override
     public MathTransform createMapProjection(final MathTransformFactory 
factory) throws FactoryException {
         CassiniSoldner kernel = this;
-        if (eccentricity == 0 && variant == STANDARD_VARIANT) {
+        if (eccentricity == 0 && variant == null) {
             kernel = new Spherical(this);
         }
         return context.completeTransform(factory, kernel);
@@ -256,7 +277,7 @@ public class CassiniSoldner extends MeridianArcBased {
         if (dstPts != null) {
             dstPts[dstOff  ] = (A - T*A3/6 + Q*T*(A3*A2) / 120) / rν;
             dstPts[dstOff+1] = distance(φ, sinφ, cosφ) + tanφ*A2*(0.5 + S/4) / 
rν;
-            if (variant != STANDARD_VARIANT) {
+            if (variant != null) {
                 /*
                  * Offset: X³ / (6ρν)    where    ρ = (1 – ℯ²)⋅ν³
                  *       = X³ / (6⋅(1 – ℯ²)⋅ν⁴)
@@ -268,7 +289,7 @@ public class CassiniSoldner extends MeridianArcBased {
         if (!derivate) {
             return null;
         }
-        if (variant != STANDARD_VARIANT) {
+        if (variant != null) {
             throw new 
ProjectionException(Resources.format(Resources.Keys.CanNotComputeDerivative));
         }
         /*
@@ -306,8 +327,8 @@ public class CassiniSoldner extends MeridianArcBased {
     {
         double x = srcPts[srcOff  ];
         double y = srcPts[srcOff+1];
-        if (variant != STANDARD_VARIANT) {
-            if (variant != VANUA) {
+        if (variant != null) {
+            if (variant != Variant.VANUA) {
                 throw new 
ProjectionException(Errors.format(Errors.Keys.UnsupportedArgumentValue_1, 
"φ₀≠16°15′S"));
             }
             /*
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalEqualArea.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalEqualArea.java
index 81bcd0bb2c..5d5c9ef337 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalEqualArea.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalEqualArea.java
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.EnumMap;
+import java.util.regex.Pattern;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransformFactory;
@@ -58,7 +59,7 @@ import static 
org.apache.sis.internal.referencing.provider.LambertCylindricalEqu
  * However this projection may be useful for computing areas.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.8
  * @module
  */
@@ -66,34 +67,55 @@ public class CylindricalEqualArea extends 
EqualAreaProjection {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 8840395516658904421L;
+    private static final long serialVersionUID = 5659955047326708663L;
 
     /**
-     * Returns the variant of the projection based on the name and identifier 
of the given operation method.
+     * The variants of the projection based on the name and identifier of the 
given operation method.
      * See {@link #variant} for the list of possible values.
      */
-    private static byte getVariant(final OperationMethod method) {
-        if (identMatch(method, "(?i).*\\bSpherical\\b.*", 
LambertCylindricalEqualAreaSpherical.IDENTIFIER)) {
-            return Initializer.AUTHALIC_RADIUS;
+    private enum Variant implements ProjectionVariant {
+        /**
+         * The "Lambert Cylindrical Equal Area (Spherical)" case.
+         */
+        SPHERICAL(".*\\bSpherical\\b.*", 
LambertCylindricalEqualAreaSpherical.IDENTIFIER);
+
+        /** Name pattern for this variant.    */ private final Pattern 
operationName;
+        /** EPSG identifier for this variant. */ private final String  
identifier;
+        /** Creates a new enumeration value.  */
+        private Variant(final String operationName, final String identifier) {
+            this.operationName = Pattern.compile(operationName, 
Pattern.CASE_INSENSITIVE);
+            this.identifier    = identifier;
+        }
+
+        /** The expected name pattern of an operation method for this variant. 
*/
+        @Override public Pattern getOperationNamePattern() {
+            return operationName;
+        }
+
+        /** EPSG identifier of an operation method for this variant. */
+        @Override public String getIdentifier() {
+            return identifier;
+        }
+
+        /** Requests the use of authalic radius. */
+        @Override public boolean useAuthalicRadius() {
+            return true;
         }
-        return STANDARD_VARIANT;
     }
 
     /**
      * The type of Cylindrical Equal Area projection. Possible values are:
      *
      * <ul>
-     *   <li>{@link #STANDARD_VARIANT} if this projection is a default 
variant.</li>
-     *   <li>{@link Initializer#AUTHALIC_RADIUS} if this projection is the 
"Lambert Cylindrical Equal Area (Spherical)"
-     *       case, in which case the semi-major and semi-minor axis lengths 
should be replaced by the authalic radius
+     *   <li>{@code null} if this projection is a default variant.</li>
+     *   <li>{@link Variant#SPHERICAL} if this projection is the "Lambert 
Cylindrical Equal Area (Spherical)" case,
+     *       in which case the semi-major and semi-minor axis lengths should 
be replaced by the authalic radius
      *       (this replacement is performed by the {@link Initializer} 
constructor).</li>
      * </ul>
      *
      * Other cases may be added in the future.
-     *
-     * @see #getVariant(OperationMethod)
      */
-    private final byte variant;
+    private final Variant variant;
 
     /**
      * Creates a Cylindrical Equal Area projection from the given parameters.
@@ -112,6 +134,7 @@ public class CylindricalEqualArea extends 
EqualAreaProjection {
     @SuppressWarnings("fallthrough")
     @Workaround(library="JDK", version="1.7")
     private static Initializer initializer(final OperationMethod method, final 
Parameters parameters) {
+        final Variant variant = variant(method, Variant.values(), null);
         final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new 
EnumMap<>(ParameterRole.class);
         /*
          * "Longitude of origin" and "scale factor" are intentionally omitted 
from this map because they will
@@ -120,7 +143,7 @@ public class CylindricalEqualArea extends 
EqualAreaProjection {
         roles.put(ParameterRole.SCALE_FACTOR,   SCALE_FACTOR);
         roles.put(ParameterRole.FALSE_EASTING,  FALSE_EASTING);
         roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING);
-        return new Initializer(method, parameters, roles, getVariant(method));
+        return new Initializer(method, parameters, roles, variant);
     }
 
     /**
@@ -130,7 +153,7 @@ public class CylindricalEqualArea extends 
EqualAreaProjection {
     @Workaround(library="JDK", version="1.7")
     private CylindricalEqualArea(final Initializer initializer) {
         super(initializer);
-        variant = initializer.variant;
+        variant = (Variant) initializer.variant;
         final MatrixSIS denormalize = 
context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
         /*
          * The longitude of origin is normally subtracted in the 'normalize' 
matrix. But in the particular of case
@@ -194,7 +217,7 @@ public class CylindricalEqualArea extends 
EqualAreaProjection {
     @Override
     public MathTransform createMapProjection(final MathTransformFactory 
factory) throws FactoryException {
         CylindricalEqualArea kernel = this;
-        if (variant == Initializer.AUTHALIC_RADIUS || eccentricity == 0) {
+        if (variant == Variant.SPHERICAL || eccentricity == 0) {
             kernel = new Spherical(this);
         }
         return context.completeTransform(factory, kernel);
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
index 9f23271199..63699a5546 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
@@ -56,7 +56,7 @@ import static 
org.apache.sis.util.ArgumentChecks.ensureNonNull;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.6
  * @module
  */
@@ -93,21 +93,9 @@ final class Initializer {
     private final byte signum_λ0;
 
     /**
-     * Map projection variant.
-     * Values from 0 to 127 inclusive are convenience values at the discretion 
of {@link NormalizedProjection} subclasses.
-     * Values from 128 to 255 inclusive are values handled in a special way by 
{@link Initializer} constructor.
+     * Map projection variant, or {@code null} if none.
      */
-    final byte variant;
-
-    /**
-     * A {@link #variant} value telling the constructor to computing the 
authalic radius instead of using
-     * the semi-major and semi-minor axis lengths directly.
-     *
-     * <p>Note that this value is not necessarily equivalent to the {@code 
SPHERICAL} value defined in some
-     * map projection, because EPSG guidance notes recommend different 
approaches for spherical implementations.
-     * For example the Mercator projection will use the radius of conformal 
sphere instead of the authalic radius.</p>
-     */
-    static final byte AUTHALIC_RADIUS = (byte) 128;
+    final ProjectionVariant variant;
 
     /**
      * Creates a new initializer. The parameters are described in
@@ -117,13 +105,11 @@ final class Initializer {
      * @param parameters  the parameters of the projection to be created.
      * @param roles       parameters to look for <cite>central 
meridian</cite>, <cite>scale factor</cite>,
      *                    <cite>false easting</cite>, <cite>false 
northing</cite> and other values.
-     * @param variant     convenience field left at the discretion of {@link 
NormalizedProjection} subclasses.
-     *                    Values equal or greater than 128 are special values 
recognized by this constructor
-     *                    (see {@link #AUTHALIC_RADIUS}).
+     * @param variant     the map projection variant, or {@code null} if none.
      */
     Initializer(final OperationMethod method, final Parameters parameters,
-            final Map<ParameterRole, ? extends ParameterDescriptor<? extends 
Number>> roles,
-            final byte variant)
+                final Map<ParameterRole, ? extends ParameterDescriptor<? 
extends Number>> roles,
+                final ProjectionVariant variant)
     {
         ensureNonNull("method",     method);
         ensureNonNull("parameters", parameters);
@@ -153,7 +139,7 @@ final class Initializer {
         eccentricitySquared = new DoubleDouble();
         DoubleDouble k = DoubleDouble.createAndGuessError(a);  // The value by 
which to multiply all results of normalized projection.
         if (a != b) {
-            if (variant == AUTHALIC_RADIUS) {
+            if (variant != null && variant.useAuthalicRadius()) {
                 k.value = Formulas.getAuthalicRadius(a, b);
                 k.error = 0;
             } else {
@@ -271,7 +257,7 @@ final class Initializer {
 
     /**
      * Same as {@link #getAndStore(ParameterDescriptor)}, but returns the 
given default value if the parameter
-     * is not specified.  This method shall be used only for parameters having 
a default value more complex than
+     * is not specified. This method shall be used only for parameters having 
a default value more complex than
      * what we can represent in {@link ParameterDescriptor#getDefaultValue()}.
      */
     final double getAndStore(final ParameterDescriptor<Double> descriptor, 
final double defaultValue) {
@@ -284,6 +270,18 @@ final class Initializer {
         return value;
     }
 
+    /**
+     * Same as {@link #getAndStore(ParameterDescriptor, double)} but working 
on integer values.
+     */
+    final int getAndStore(final ParameterDescriptor<Integer> descriptor, final 
int defaultValue) {
+        final Integer value = parameters.getValue(descriptor);
+        if (value == null) {
+            return defaultValue;
+        }
+        context.getOrCreate(descriptor).setValue(value);
+        return value;
+    }
+
     /**
      * Returns {@code b/a} where {@code a} is the semi-major axis length and 
{@code b} the semi-minor axis length.
      * We retrieve this value from the eccentricity with {@code b/a = 
sqrt(1-ℯ²)}.
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
index 269ceeae53..d5d311c3e7 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.EnumMap;
+import java.util.regex.Pattern;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.operation.MathTransform;
@@ -61,7 +62,7 @@ import static 
org.apache.sis.internal.referencing.Formulas.fastHypot;
  * @author  André Gosselin (MPO)
  * @author  Rueben Schulz (UBC)
  * @author  Rémi Maréchal (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.6
  * @module
  */
@@ -69,34 +70,53 @@ public class LambertConicConformal extends 
ConformalProjection {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = -8971522854919443706L;
+    private static final long serialVersionUID = -8786460743797422415L;
 
     /**
-     * Codes for variants of Lambert Conical Conformal projection. Those 
variants modify the way the projections are
-     * constructed (e.g. in the way parameters are interpreted), but formulas 
are basically the same after construction.
+     * Variants of Lambert Conical Conformal projection. Those variants modify 
the way the projections are constructed
+     * (e.g. in the way parameters are interpreted), but formulas are 
basically the same after construction.
      * Those variants are not exactly the same than variants 1SP and 2SP used 
by EPSG, but they are closely related.
      *
      * <p>We do not provide such codes in public API because they duplicate 
the functionality of
      * {@link OperationMethod} instances. We use them only for constructors 
convenience.</p>
-     *
-     * <p><b>CONVENTION:</b> Codes for SP1 case must be odd, and codes for SP2 
case must be even.
-     *
-     * @see #getVariant(OperationMethod)
      */
-    private static final byte SP1  = 1,  WEST    = 3,                   // 
Must be odd
-                              SP2  = 2,  BELGIUM = 4,  MICHIGAN = 6;    // 
Must be even
+    private enum Variant implements ProjectionVariant {
+        // Declaration order matter. Patterns are matched in that order.
 
-    /**
-     * Returns the type of the projection based on the name and identifier of 
the given operation method.
-     * If this method can not identify the type, then the parameters should be 
considered as a 2SP case.
-     */
-    private static byte getVariant(final OperationMethod method) {
-        if (identMatch(method, "(?i).*\\bBelgium\\b.*",  
LambertConformalBelgium .IDENTIFIER)) return BELGIUM;
-        if (identMatch(method, "(?i).*\\bMichigan\\b.*", 
LambertConformalMichigan.IDENTIFIER)) return MICHIGAN;
-        if (identMatch(method, "(?i).*\\bWest\\b.*",     LambertConformalWest  
  .IDENTIFIER)) return WEST;
-        if (identMatch(method, "(?i).*\\b2SP\\b.*",      LambertConformal2SP   
  .IDENTIFIER)) return SP2;
-        if (identMatch(method, "(?i).*\\b1SP\\b.*",      LambertConformal1SP   
  .IDENTIFIER)) return SP1;
-        return STANDARD_VARIANT;                         // Unidentified case, 
to be considered as 2SP.
+        /** The <cite>"Lambert Conic Conformal (2SP Belgium)"</cite> 
projection. */
+        BELGIUM(".*\\bBelgium\\b.*", LambertConformalBelgium.IDENTIFIER, 
false),
+
+        /** The <cite>"Lambert Conic Conformal (2SP Michigan)"</cite> 
projection. */
+        MICHIGAN(".*\\bMichigan\\b.*", LambertConformalMichigan.IDENTIFIER, 
false),
+
+        /** The <cite>"Lambert Conic Conformal (West Orientated)"</cite> 
projection. */
+        WEST(".*\\bWest\\b.*", LambertConformalWest.IDENTIFIER, true),
+
+        /** The <cite>"Lambert Conic Conformal (1SP)"</cite> projection. */
+        ONE_PARALLEL(".*\\b1SP\\b.*", LambertConformal1SP.IDENTIFIER, true),
+
+        /** The  <cite>"Lambert Conic Conformal (2SP)"</cite> projection. */
+        TWO_PARALLELS(".*\\b2SP\\b.*", LambertConformal2SP.IDENTIFIER, false);
+
+        /** Name pattern for this variant.    */ private final Pattern 
operationName;
+        /** EPSG identifier for this variant. */ private final String 
identifier;
+        /** Number of standard parallels.     */ final boolean is1SP;
+        /** Creates a new enumeration value.  */
+        private Variant(final String operationName, final String identifier, 
final boolean is1SP) {
+            this.operationName = Pattern.compile(operationName, 
Pattern.CASE_INSENSITIVE);
+            this.identifier    = identifier;
+            this.is1SP         = is1SP;
+        }
+
+        /** The expected name pattern of an operation method for this variant. 
*/
+        @Override public Pattern getOperationNamePattern() {
+            return operationName;
+        }
+
+        /** EPSG identifier of an operation method for this variant. */
+        @Override public String getIdentifier() {
+            return identifier;
+        }
     }
 
     /**
@@ -176,7 +196,7 @@ public class LambertConicConformal extends 
ConformalProjection {
     @SuppressWarnings("fallthrough")
     @Workaround(library="JDK", version="1.7")
     private static Initializer initializer(final OperationMethod method, final 
Parameters parameters) {
-        final byte variant = getVariant(method);
+        final Variant variant = variant(method, Variant.values(), 
Variant.TWO_PARALLELS);
         final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new 
EnumMap<>(ParameterRole.class);
         /*
          * "Scale factor" is not formally a "Lambert Conformal (2SP)" 
argument, but we accept it
@@ -193,7 +213,7 @@ public class LambertConicConformal extends 
ConformalProjection {
                 eastingDirection = ParameterRole.FALSE_WESTING;
                 // Fallthrough
             }
-            case SP1: {
+            case ONE_PARALLEL: {
                 roles.put(eastingDirection,               
LambertConformal1SP.FALSE_EASTING);
                 roles.put(ParameterRole.FALSE_NORTHING,   
LambertConformal1SP.FALSE_NORTHING);
                 roles.put(ParameterRole.CENTRAL_MERIDIAN, 
LambertConformal1SP.LONGITUDE_OF_ORIGIN);
@@ -203,9 +223,8 @@ public class LambertConicConformal extends 
ConformalProjection {
                 scaleFactor = LambertConformalMichigan.SCALE_FACTOR;    // 
Ellipsoid scaling factor (EPSG:1038)
                 // Fallthrough
             }
-            case STANDARD_VARIANT:
             case BELGIUM:
-            case SP2: {
+            case TWO_PARALLELS: {
                 roles.put(eastingDirection,               
LambertConformal2SP.EASTING_AT_FALSE_ORIGIN);
                 roles.put(ParameterRole.FALSE_NORTHING,   
LambertConformal2SP.NORTHING_AT_FALSE_ORIGIN);
                 roles.put(ParameterRole.CENTRAL_MERIDIAN, 
LambertConformal2SP.LONGITUDE_OF_FALSE_ORIGIN);
@@ -224,8 +243,10 @@ public class LambertConicConformal extends 
ConformalProjection {
     @Workaround(library="JDK", version="1.7")
     private LambertConicConformal(final Initializer initializer) {
         super(initializer);
-        double φ0 = initializer.getAndStore(((initializer.variant & 1) != 0) ? 
 // Odd 'type' are SP1, even 'type' are SP2.
-                LambertConformal1SP.LATITUDE_OF_ORIGIN : 
LambertConformal2SP.LATITUDE_OF_FALSE_ORIGIN);
+        final Variant variant = (Variant) initializer.variant;
+        double φ0 = initializer.getAndStore(variant.is1SP
+                  ? LambertConformal1SP.LATITUDE_OF_ORIGIN
+                  : LambertConformal2SP.LATITUDE_OF_FALSE_ORIGIN);
         /*
          * Standard parallels (SP) are defined only for the 2SP case, but we 
look for them unconditionally
          * in case the user gave us non-standard parameters. For the 1SP case, 
or for the 2SP case left to
@@ -249,8 +270,8 @@ public class LambertConicConformal extends 
ConformalProjection {
          *
          *     t = tan(π/4 – φ/2) / [(1 – ℯ⋅sinφ)/(1 + ℯ⋅sinφ)] ^ (ℯ/2)
          *
-         * while our 'expΨ' function is defined like above, but with tan(π/4 + 
φ/2) instead of tan(π/4 - φ/2).
-         * Those two expressions are the reciprocal of each other if we 
reverse the sign of φ (see 'expΨ' for
+         * while our `expΨ` function is defined like above, but with tan(π/4 + 
φ/2) instead of tan(π/4 - φ/2).
+         * Those two expressions are the reciprocal of each other if we 
reverse the sign of φ (see `expΨ` for
          * trigonometric identities), but their accuracies are not equivalent: 
the hemisphere having values
          * closer to zero is favorized. The EPSG formulas favorize the North 
hemisphere.
          *
@@ -258,8 +279,8 @@ public class LambertConicConformal extends 
ConformalProjection {
          * values in order to match the EPSG formulas, but we will do that 
only if the map projection is
          * for the North hemisphere.
          *
-         * TEST: whether 'isNorth' is true of false does not change the 
formulas "correctness": it is only
-         * a small accuracy improvement. One can safely force this boolean 
value to 'true' or 'false' for
+         * TEST: whether `isNorth` is true of false does not change the 
formulas "correctness": it is only
+         * a small accuracy improvement. One can safely force this boolean 
value to `true` or `false` for
          * testing purpose.
          */
         final boolean isNorth = isPositive(φ0);
@@ -285,7 +306,7 @@ public class LambertConicConformal extends 
ConformalProjection {
          * for reducing the amount of calls to the logarithmic function. Note 
that this equation
          * tends toward 0/0 if φ₁ ≈ φ₂, which force us to do a special check 
for the SP1 case.
          */
-        if (abs(φ1 - φ2) >= ANGULAR_TOLERANCE) {                    // Should 
be 'true' for 2SP case.
+        if (abs(φ1 - φ2) >= ANGULAR_TOLERANCE) {                    // Should 
be `true` for 2SP case.
             final double sinφ2 = sin(φ2);
             final double m2 = initializer.scaleAtφ(sinφ2, cos(φ2));
             final double t2 = expΨ(φ2, eccentricity*sinφ2);
@@ -310,7 +331,7 @@ public class LambertConicConformal extends 
ConformalProjection {
          * Compute the radius of the parallel of latitude of the false origin.
          * This is related to the "ρ₀" term in Snyder. From EPG guide:
          *
-         *    r = a⋅F⋅tⁿ     where (in our case) a=1 and t is our 'expΨ' 
function.
+         *    r = a⋅F⋅tⁿ     where (in our case) a=1 and t is our `expΨ` 
function.
          *
          * EPSG uses this term in the computation of  y = FN + rF – r⋅cos(θ).
          */
@@ -326,7 +347,7 @@ public class LambertConicConformal extends 
ConformalProjection {
          * Normalization:
          *   - Subtract central meridian to longitudes (done by the 
super-class constructor).
          *   - Convert longitudes and latitudes from degrees to radians (done 
by the super-class constructor)
-         *   - Multiply longitude by 'n'.
+         *   - Multiply longitude by `n`.
          *   - In the Belgium case only, subtract BELGE_A to the scaled 
longitude.
          *
          * Denormalization
@@ -346,7 +367,7 @@ public class LambertConicConformal extends 
ConformalProjection {
         }
         final MatrixSIS normalize   = 
context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
         final MatrixSIS denormalize = 
context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
-        normalize  .convertAfter(0, sλ, (initializer.variant == BELGIUM) ? 
belgeA() : null);
+        normalize  .convertAfter(0, sλ, (variant == Variant.BELGIUM) ? 
belgeA() : null);
         normalize  .convertAfter(1, sφ, null);
         denormalize.convertBefore(0, F, null); F.negate();
         denormalize.convertBefore(1, F, rF);
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
index a0e3f9dfb0..88627c0719 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.EnumMap;
+import java.util.regex.Pattern;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.operation.Matrix;
@@ -27,14 +28,17 @@ import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.referencing.provider.Mercator1SP;
 import org.apache.sis.internal.referencing.provider.Mercator2SP;
 import org.apache.sis.internal.referencing.provider.MercatorSpherical;
+import org.apache.sis.internal.referencing.provider.MercatorAuxiliarySphere;
 import org.apache.sis.internal.referencing.provider.RegionalMercator;
 import org.apache.sis.internal.referencing.provider.PseudoMercator;
+import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.referencing.operation.matrix.Matrix2;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.ContextualParameters;
 import org.apache.sis.parameter.Parameters;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.Workaround;
 
 import static java.lang.Math.*;
@@ -73,7 +77,7 @@ import static org.apache.sis.math.MathFunctions.isPositive;
  * @author  Rueben Schulz (UBC)
  * @author  Simon Reynard (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
- * @version 0.8
+ * @version 1.2
  *
  * @see TransverseMercator
  * @see ObliqueMercator
@@ -85,51 +89,75 @@ public class Mercator extends ConformalProjection {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 2564172914329253286L;
+    private static final long serialVersionUID = 8732555724521630563L;
 
     /**
-     * Codes for variants of Mercator projection. Those variants modify the 
way the projections are constructed
+     * Variants of Mercator projection. Those variants modify the way the 
projections are constructed
      * (e.g. in the way parameters are interpreted), but formulas are 
basically the same after construction.
      * Those variants are not exactly the same than variants A, B and C used 
by EPSG, but they are related.
      *
      * <p>We do not provide such codes in public API because they duplicate 
the functionality of
      * {@link OperationMethod} instances. We use them only for constructors 
convenience.</p>
-     *
-     * <p><b>CONVENTION:</b> <strong>Spherical cases must be odd, all other 
cases must be even.</strong>
-     * This allow us to perform quick checks for all spherical cases using 
{@code if ((type & SPHERICAL) != 0)}.</p>
-     *
-     * @see #variant
-     * @see #getVariant(OperationMethod)
      */
-    private static final byte SPHERICAL = 1, PSEUDO = 3,            // Must be 
odd and SPHERICAL must be 1.
-                              REGIONAL  = 2, MILLER = 4;            // Must be 
even.
+    private enum Variant implements ProjectionVariant {
+        // Declaration order matter. Patterns are matched in that order.
 
-    /**
-     * Returns the variant of the projection based on the name and identifier 
of the given operation method.
-     */
-    private static byte getVariant(final OperationMethod method) {
-        if (identMatch(method, "(?i).*\\bvariant\\s*C\\b.*", RegionalMercator 
.IDENTIFIER)) return REGIONAL;
-        if (identMatch(method, "(?i).*\\bSpherical\\b.*",    
MercatorSpherical.IDENTIFIER)) return SPHERICAL;
-        if (identMatch(method, "(?i).*\\bPseudo.*",          PseudoMercator   
.IDENTIFIER)) return PSEUDO;
-        if (identMatch(method, "(?i).*\\bMiller.*",          null))            
             return MILLER;
-        return STANDARD_VARIANT;
+        /** The <cite>"Mercator (variant A)"</cite> projection (one standard 
parallel). */
+        ONE_PARALLEL(".*\\bvariant\\s*A\\b.*", Mercator1SP.IDENTIFIER, false),
+
+        /** The <cite>"Mercator (variant B)"</cite> projection (two standard 
parallels). */
+        TWO_PARALLELS(".*\\bvariant\\s*B\\b.*", Mercator2SP.IDENTIFIER, false),
+
+        /** The <cite>"Mercator (variant C)"</cite> projection. */
+        REGIONAL(".*\\bvariant\\s*C\\b.*", RegionalMercator.IDENTIFIER, false),
+
+        /** The <cite>"Mercator (Spherical)"</cite> projection. */
+        SPHERICAL(".*\\bSpherical\\b.*", MercatorSpherical.IDENTIFIER, true),
+
+        /** The <cite>"Popular Visualisation Pseudo Mercator"</cite> 
projection. */
+        PSEUDO(".*\\bPseudo.*", PseudoMercator.IDENTIFIER, true),
+
+        /** The <cite>"Mercator Auxiliary Sphere"</cite> projection. */
+        AUXILIARY(".*\\bAuxiliary\\s*Sphere\\b.*", null, true),
+
+        /** Miller projection. */
+        MILLER(".*\\bMiller.*", null, false);
+
+        /** Name pattern for this variant.    */ private final Pattern 
operationName;
+        /** EPSG identifier for this variant. */ private final String  
identifier;
+        /** Whether spherical case is used.   */ final boolean spherical;
+        /** Creates a new enumeration value.  */
+        private Variant(final String operationName, final String identifier, 
final boolean spherical) {
+            this.operationName = Pattern.compile(operationName, 
Pattern.CASE_INSENSITIVE);
+            this.identifier    = identifier;
+            this.spherical     = spherical;
+        }
+
+        /** The expected name pattern of an operation method for this variant. 
*/
+        @Override public Pattern getOperationNamePattern() {
+            return operationName;
+        }
+
+        /** EPSG identifier of an operation method for this variant. */
+        @Override public String getIdentifier() {
+            return identifier;
+        }
     }
 
     /**
      * The type of Mercator projection. Possible values are:
      * <ul>
-     *   <li>{@link #STANDARD_VARIANT} if this projection is a Mercator 
variant A or B.</li>
-     *   <li>{@link #REGIONAL}         if this projection is the "Mercator 
(variant C)" case.</li>
-     *   <li>{@link #SPHERICAL}        if this projection is the "Mercator 
(Spherical)" case.</li>
-     *   <li>{@link #PSEUDO}           if this projection is the "Pseudo 
Mercator" case.</li>
-     *   <li>{@link #MILLER}           if this projection is the "Miller 
Cylindrical" case.</li>
+     *   <li>{@link Variant#DEFAULT}   if this projection is a Mercator 
variant A or B.</li>
+     *   <li>{@link Variant#REGIONAL}  if this projection is the "Mercator 
(variant C)" case.</li>
+     *   <li>{@link Variant#SPHERICAL} if this projection is the "Mercator 
(Spherical)" case.</li>
+     *   <li>{@link Variant#PSEUDO}    if this projection is the "Pseudo 
Mercator" case.</li>
+     *   <li>{@link Variant#MILLER}    if this projection is the "Miller 
Cylindrical" case.</li>
+     *   <li>{@link Variant#AUXILIARY} if this projection is the "Mercator 
Auxiliary Sphere" case.</li>
      * </ul>
      *
      * Other cases may be added in the future.
-     *
-     * @see #getVariant(OperationMethod)
      */
-    private final byte variant;
+    private final Variant variant;
 
     /**
      * Creates a Mercator projection from the given parameters.
@@ -140,6 +168,7 @@ public class Mercator extends ConformalProjection {
      *   <li><cite>"Mercator (variant B)"</cite>, also known as 
<cite>"Mercator (2SP)"</cite>.</li>
      *   <li><cite>"Mercator (variant C)"</cite>.</li>
      *   <li><cite>"Mercator (Spherical)"</cite>.</li>
+     *   <li><cite>"Mercator Auxiliary Sphere"</cite>.</li>
      *   <li><cite>"Popular Visualisation Pseudo Mercator"</cite>.</li>
      *   <li><cite>"Miller Cylindrical"</cite>.</li>
      * </ul>
@@ -158,7 +187,7 @@ public class Mercator extends ConformalProjection {
     @SuppressWarnings("fallthrough")
     @Workaround(library="JDK", version="1.7")
     private static Initializer initializer(final OperationMethod method, final 
Parameters parameters) {
-        final byte variant = getVariant(method);
+        final Variant variant = variant(method, Variant.values(), 
Variant.TWO_PARALLELS);
         final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new 
EnumMap<>(ParameterRole.class);
         /*
          * "Longitude of origin" is a parameter of all Mercator projections, 
but is intentionally omitted from
@@ -208,7 +237,7 @@ public class Mercator extends ConformalProjection {
     @Workaround(library="JDK", version="1.7")
     private Mercator(final Initializer initializer) {
         super(initializer);
-        this.variant = initializer.variant;
+        variant = (Variant) initializer.variant;
         /*
          * The "Longitude of natural origin" parameter is found in all 
Mercator projections and is mandatory.
          * Since this is usually the Greenwich meridian, the default value is 
0°. We keep the value in degrees
@@ -227,7 +256,7 @@ public class Mercator extends ConformalProjection {
          * "Latitude of origin" can not have a non-zero value, if it still 
have non-zero value we will process as
          * for "Latitude of false origin".
          */
-        final double φ0 = toRadians(initializer.getAndStore((variant == 
REGIONAL)
+        final double φ0 = toRadians(initializer.getAndStore((variant == 
Variant.REGIONAL)
                 ? RegionalMercator.LATITUDE_OF_FALSE_ORIGIN : 
Mercator1SP.LATITUDE_OF_ORIGIN));
         /*
          * In theory, the "Latitude of 1st standard parallel" and the "Scale 
factor at natural origin" parameters
@@ -244,7 +273,7 @@ public class Mercator extends ConformalProjection {
          *
          * However in the particular case of Mercator projection, we will 
apply the longitude rotation in the
          * denormalization matrix instead.   This is possible only for this 
particular projection because the
-         * 'transform(…)' methods pass the longitude values unchanged. By 
keeping the normalization affine as
+         * `transform(…)` methods pass the longitude values unchanged. By 
keeping the normalization affine as
          * simple as possible, we increase the chances of efficient 
concatenation of an inverse with a forward
          * projection.
          */
@@ -255,7 +284,7 @@ public class Mercator extends ConformalProjection {
         if (λ0 != 0) {
             /*
              * Use double-double arithmetic here for consistency with the work 
done in the normalization matrix.
-             * The intent is to have exact value at 'double' precision when 
computing Matrix.invert(). Note that
+             * The intent is to have exact value at `double` precision when 
computing Matrix.invert(). Note that
              * there is no such goal for other parameters computed from sine 
or consine functions.
              */
             final DoubleDouble offset = DoubleDouble.createDegreesToRadians();
@@ -265,9 +294,34 @@ public class Mercator extends ConformalProjection {
         if (φ0 != 0) {
             denormalize.convertBefore(1, null, new DoubleDouble(-log(expΨ(φ0, 
eccentricity * sin(φ0)))));
         }
-        if (variant == MILLER) {
+        /*
+         * Variants of the Mercator projection which can be handled by scale 
factors.
+         * In the "Mercator Auxiliary Sphere" case, sphere types are:
+         *
+         *   0 = use semimajor axis or radius of the geographic coordinate 
system.
+         *   1 = use semiminor axis or radius.
+         *   2 = calculate and use authalic radius.
+         *   3 = use authalic radius and convert geodetic latitudes to 
authalic latitudes.
+         *       The conversion is not handled by this class and must be done 
by the caller.
+         */
+        if (variant == Variant.MILLER) {
             normalize  .convertBefore(1, 0.80, null);
             denormalize.convertBefore(1, 1.25, null);
+        } else if (variant == Variant.AUXILIARY) {
+            DoubleDouble ratio = null;
+            final int type = 
initializer.getAndStore(MercatorAuxiliarySphere.AUXILIARY_SPHERE_TYPE, 0);
+            switch (type) {
+                case 0: break;      // Same as "Popular Visualisation Pseudo 
Mercator".
+                case 1: ratio = initializer.axisLengthRatio(); break;
+                case 2:
+                case 3: ratio = new DoubleDouble(Formulas.getAuthalicRadius(1, 
initializer.axisLengthRatio().value)); break;
+                default: {
+                    throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
+                            
MercatorAuxiliarySphere.AUXILIARY_SPHERE_TYPE.getName().getCode(), type));
+                }
+            }
+            denormalize.convertAfter(0, ratio, null);
+            denormalize.convertAfter(1, ratio, null);
         }
         /*
          * At this point we are done, but we add here a little bit a maniac 
precision hunting.
@@ -322,7 +376,7 @@ public class Mercator extends ConformalProjection {
     @Override
     public MathTransform createMapProjection(final MathTransformFactory 
factory) throws FactoryException {
         Mercator kernel = this;
-        if ((variant & SPHERICAL) != 0 || eccentricity == 0) {
+        if (variant.spherical || eccentricity == 0) {
             kernel = new Spherical(this);
         }
         return context.completeTransform(factory, kernel);
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java
index 4bcb6ab8b4..1d7d59499d 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java
@@ -94,7 +94,7 @@ public class ModifiedAzimuthalEquidistant extends 
AzimuthalEquidistant {
         roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_ORIGIN);
         roles.put(ParameterRole.FALSE_EASTING,    FALSE_EASTING);
         roles.put(ParameterRole.FALSE_NORTHING,   FALSE_NORTHING);
-        return new Initializer(method, parameters, roles, STANDARD_VARIANT);
+        return new Initializer(method, parameters, roles, null);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java
index 872a4b6b27..9e8d7b2108 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.EnumMap;
+import java.util.regex.Pattern;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.OperationMethod;
@@ -49,7 +50,7 @@ import static 
org.apache.sis.internal.referencing.provider.Mollweide.*;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.2
  * @since   1.0
  * @module
  */
@@ -59,6 +60,30 @@ public class Mollweide extends NormalizedProjection {
      */
     private static final long serialVersionUID = 712275000459795291L;
 
+    /**
+     * Allowed projection variants. Current implementation supports only 
spherical formulas.
+     * We do not yet use this enumeration for detecting variants from the 
operation name.
+     */
+    private enum Variant implements ProjectionVariant {
+        /** The spherical case. */
+        SPHERICAL;
+
+        /** The expected name pattern of an operation method for this variant. 
*/
+        @Override public Pattern getOperationNamePattern() {
+            return null;
+        }
+
+        /** EPSG identifier of an operation method for this variant. */
+        @Override public String getIdentifier() {
+            return null;
+        }
+
+        /** Requests the use of authalic radius. */
+        @Override public boolean useAuthalicRadius() {
+            return true;
+        }
+    }
+
     /**
      * Work around for RFE #4093999 in Sun's bug database
      * ("Relax constraint on placement of this()/super() call in 
constructors").
@@ -69,7 +94,7 @@ public class Mollweide extends NormalizedProjection {
         roles.put(ParameterRole.CENTRAL_MERIDIAN, CENTRAL_MERIDIAN);
         roles.put(ParameterRole.FALSE_EASTING,    FALSE_EASTING);
         roles.put(ParameterRole.FALSE_NORTHING,   FALSE_NORTHING);
-        return new Initializer(method, parameters, roles, 
Initializer.AUTHALIC_RADIUS);
+        return new Initializer(method, parameters, roles, Variant.SPHERICAL);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
index 4822743e7f..9682cd4207 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
@@ -21,6 +21,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.Objects;
+import java.util.regex.Pattern;
 import java.io.Serializable;
 import java.lang.reflect.Modifier;
 import org.opengis.metadata.Identifier;
@@ -175,12 +176,6 @@ public abstract class NormalizedProjection extends 
AbstractMathTransform2D imple
      */
     static final int MAXIMUM_ITERATIONS = Formulas.MAXIMUM_ITERATIONS;
 
-    /**
-     * In map projection implementations that can have some variants, the 
constant for identifying
-     * the most standard form of the projection.
-     */
-    static final byte STANDARD_VARIANT = 0;
-
     /**
      * The internal parameter descriptors. Keys are implementation classes.  
Values are parameter descriptor groups
      * containing at least a parameter for the {@link #eccentricity} value, 
and optionally other internal parameter
@@ -426,7 +421,7 @@ public abstract class NormalizedProjection extends 
AbstractMathTransform2D imple
     protected NormalizedProjection(final OperationMethod method, final 
Parameters parameters,
             final Map<ParameterRole, ? extends ParameterDescriptor<? extends 
Number>> roles)
     {
-        this(new Initializer(method, parameters, roles, STANDARD_VARIANT));
+        this(new Initializer(method, parameters, roles, null));
     }
 
     /**
@@ -455,30 +450,33 @@ public abstract class NormalizedProjection extends 
AbstractMathTransform2D imple
     }
 
     /**
-     * Returns {@code true} if the projection specified by the given method 
has the given keyword or identifier.
-     * If non-null, the given identifier is presumed in the EPSG namespace and 
has precedence over the keyword.
-     *
-     * <div class="note"><b>Implementation note:</b>
-     * Since callers usually give a constant string for the {@code regex} 
argument, it would be more efficient to
-     * compile the {@link java.util.regex.Pattern} once for all. However the 
regular expression is used only as a
-     * fallback if the descriptor does not contain EPSG identifier, which 
should be rare. Usually, the regular
-     * expression will never be compiled.</div>
+     * Returns the variant of the map projection described by the given 
operation method.
+     * Identifiers are tested first because they have precedence over 
operation names.
      *
-     * @param  method      the user-specified projection method.
-     * @param  regex       the regular expression to use when using the 
operation name as the criterion.
-     * @param  identifier  the identifier to compare against the operation 
method name.
-     * @return {@code true} if the name of the given operation method contains 
the given keyword
-     *         or has an EPSG identifier equals to the given identifier.
+     * @param  method        the user-specified projection method.
+     * @param  variants      possible variants for the map projection.
+     * @param  defaultValue  value to return if no match is found.
+     * @return the variant of the given operation method, or {@code 
defaultValue} if none.
      */
-    static boolean identMatch(final OperationMethod method, final String 
regex, final String identifier) {
-        if (identifier != null) {
-            for (final Identifier id : method.getIdentifiers()) {
-                if (Constants.EPSG.equals(id.getCodeSpace())) {
-                    return identifier.equals(id.getCode());
+    static <V extends ProjectionVariant> V variant(final OperationMethod 
method, final V[] variants, final V defaultValue) {
+        for (final V variant : variants) {
+            final String identifier = variant.getIdentifier();
+            if (identifier != null) {
+                for (final Identifier id : method.getIdentifiers()) {
+                    if (Constants.EPSG.equals(id.getCodeSpace()) && 
identifier.equals(id.getCode())) {
+                        return variant;
+                    }
                 }
             }
         }
-        return method.getName().getCode().replace('_',' ').matches(regex);
+        final String name = method.getName().getCode().replace('_',' ');
+        for (final V variant : variants) {
+            final Pattern regex = variant.getOperationNamePattern();
+            if (regex.matcher(name).matches()) {
+                return variant;
+            }
+        }
+        return defaultValue;
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java
index a32206d5db..528763f215 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.EnumMap;
+import java.util.regex.Pattern;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.InvalidParameterValueException;
 import org.opengis.referencing.operation.OperationMethod;
@@ -69,31 +70,58 @@ public class ObliqueMercator extends ConformalProjection {
     /**
      * For compatibility with different versions during deserialization.
      */
-    private static final long serialVersionUID = -5289761492678147674L;
+    private static final long serialVersionUID = -2194259651400234956L;
 
     /**
-     * Bitmask for projections having easting/northing values defined at 
projection center
-     * instead of at coordinate system natural origin.
-     * This bit is unset for <cite>Hotine Oblique Mercator (variant A)</cite> 
(EPSG:9812)
-     * and is set for        <cite>Hotine Oblique Mercator (variant B)</cite> 
(EPSG:9815).
+     * Variants of Oblique Mercator projection.
      */
-    private static final byte CENTER = 1;
+    private enum Variant implements ProjectionVariant {
+        /** The <cite>Hotine Oblique Mercator (variant A)</cite> projection. */
+        DEFAULT(".*\\bvariant\\s*A\\b.*", IDENTIFIER_A, false, false),
 
-    /**
-     * Bitmask for projections having their central line defined by two points 
instead of an azimuth angle.
-     * The two points variants are used by ESRI.
-     */
-    private static final byte TWO_POINTS = 2;
+        /** The <cite>Hotine Oblique Mercator (variant B)</cite> projection. */
+        CENTER(".*\\bvariant\\s*B\\b.*", IDENTIFIER, false, true),
 
-    /**
-     * Returns the type of the projection based on the name and identifier of 
the given operation method.
-     */
-    private static byte getVariant(final OperationMethod method) {
-        if (identMatch(method, "(?i).*\\bvariant\\s*A\\b.*", IDENTIFIER_A))    
    return 0;
-        if (identMatch(method, "(?i).*\\bvariant\\s*B\\b.*", IDENTIFIER  ))    
    return CENTER;
-        if (identMatch(method, "(?i).*\\bTwo[_\\s]Point[_\\s]Natural\\b.*", 
null)) return TWO_POINTS;
-        if (identMatch(method, "(?i).*\\bTwo[_\\s]Point[_\\s]Center\\b.*",  
null)) return TWO_POINTS | CENTER;
-        return STANDARD_VARIANT;                // Unidentified case, to be 
considered as variant A.
+        /** The "<cite>Oblique Mercator</cite>" projection specified by two 
points on the central line. */
+        TWO_POINTS(".*\\bTwo\\s*Point\\s*Natural\\b.*", null, true, false),
+
+        /** The "<cite>Oblique Mercator</cite>" projection specified by two 
points on the central line. */
+        TWO_POINTS_CENTER(".*\\bTwo\\s*Point\\s*Center\\b.*", null, true, 
true);
+
+        /** Name pattern for this variant.    */ private final Pattern 
operationName;
+        /** EPSG identifier for this variant. */ private final String  
identifier;
+
+        /**
+         * Whether the projection has its central line defined by two points 
instead of an azimuth angle.
+         * The two points variants are used by ESRI.
+         */
+        final boolean twoPoints;
+
+        /**
+         * Whether easting/northing values are defined at projection
+         * center instead of at coordinate system natural origin.
+         * This is {@code false} for <cite>Hotine Oblique Mercator (variant 
A)</cite>
+         * and {@code true} for <cite>Hotine Oblique Mercator (variant 
B)</cite>.
+         */
+        final boolean center;
+
+        /** Creates a new enumeration value.  */
+        private Variant(final String operationName, final String identifier, 
final boolean twoPoints, final boolean center) {
+            this.operationName = Pattern.compile(operationName, 
Pattern.CASE_INSENSITIVE);
+            this.identifier    = identifier;
+            this.twoPoints     = twoPoints;
+            this.center        = center;
+        }
+
+        /** The expected name pattern of an operation method for this variant. 
*/
+        @Override public Pattern getOperationNamePattern() {
+            return operationName;
+        }
+
+        /** EPSG identifier of an operation method for this variant. */
+        @Override public String getIdentifier() {
+            return identifier;
+        }
     }
 
     /**
@@ -133,13 +161,12 @@ public class ObliqueMercator extends ConformalProjection {
      */
     @Workaround(library="JDK", version="1.7")
     private static Initializer initializer(final OperationMethod method, final 
Parameters parameters) {
-        final byte variant = getVariant(method);
-        final boolean isCenter = (variant & CENTER) != 0;
+        final Variant variant = variant(method, Variant.values(), 
Variant.DEFAULT);
         final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new 
EnumMap<>(ParameterRole.class);
 //      ParameterRole.CENTRAL_MERIDIAN intentionally excluded. It will be 
handled in the constructor instead.
         roles.put(ParameterRole.SCALE_FACTOR,   SCALE_FACTOR);
-        roles.put(ParameterRole.FALSE_EASTING,  isCenter ? EASTING_AT_CENTRE  
: FALSE_EASTING);
-        roles.put(ParameterRole.FALSE_NORTHING, isCenter ? NORTHING_AT_CENTRE 
: FALSE_NORTHING);
+        roles.put(ParameterRole.FALSE_EASTING,  variant.center ? 
EASTING_AT_CENTRE  : FALSE_EASTING);
+        roles.put(ParameterRole.FALSE_NORTHING, variant.center ? 
NORTHING_AT_CENTRE : FALSE_NORTHING);
         return new Initializer(method, parameters, roles, variant);
     }
 
@@ -149,6 +176,7 @@ public class ObliqueMercator extends ConformalProjection {
      */
     private ObliqueMercator(final Initializer initializer) {
         super(initializer);
+        final Variant variant = (Variant) initializer.variant;
         final double λc = 
toRadians(initializer.getAndStore(LONGITUDE_OF_CENTRE));
         final double φc = 
toRadians(initializer.getAndStore(LATITUDE_OF_CENTRE));
         final double sinφ  = sin(φc);
@@ -180,7 +208,7 @@ public class ObliqueMercator extends ConformalProjection {
          * Only the azimuth case is described in EPSG guidance notes.
          */
         double αc, γc, γ0, λ0;
-        if ((initializer.variant & TWO_POINTS) == 0) {
+        if (!variant.twoPoints) {
             αc = initializer.getAndStore(AZIMUTH);                             
         // Convert to radians later.
             γc = toRadians(initializer.getAndStore(RECTIFIED_GRID_ANGLE, αc));
             αc = toRadians(αc);
@@ -259,7 +287,7 @@ public class ObliqueMercator extends ConformalProjection {
          *         = A⋅(λc – λ₀)  if  αc = 90°.
          */
         final double ArB = A / B;
-        if ((initializer.variant & CENTER) != 0) {
+        if (variant.center) {
             final double uc;
             if (abs(abs(αc) - PI/2) < ANGULAR_TOLERANCE) {
                 uc = A * (λc - λ0);
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
index 8fff3f55fc..9a6cf818fd 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
@@ -129,7 +129,7 @@ public class ObliqueStereographic extends 
NormalizedProjection {
         roles.put(ParameterRole.SCALE_FACTOR,     SCALE_FACTOR);
         roles.put(ParameterRole.FALSE_EASTING,    FALSE_EASTING);
         roles.put(ParameterRole.FALSE_NORTHING,   FALSE_NORTHING);
-        return new Initializer(method, parameters, roles, STANDARD_VARIANT);
+        return new Initializer(method, parameters, roles, null);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Orthographic.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Orthographic.java
index 304b25ed89..4dbb366589 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Orthographic.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Orthographic.java
@@ -77,7 +77,7 @@ public class Orthographic extends NormalizedProjection {
         roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_ORIGIN);
         roles.put(ParameterRole.FALSE_EASTING,    FALSE_EASTING);
         roles.put(ParameterRole.FALSE_NORTHING,   FALSE_NORTHING);
-        return new Initializer(method, parameters, roles, STANDARD_VARIANT);
+        return new Initializer(method, parameters, roles, null);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
index f1206f8c2b..6861708782 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.EnumMap;
+import java.util.regex.Pattern;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.operation.MathTransform;
@@ -56,7 +57,7 @@ import static 
org.apache.sis.internal.referencing.Formulas.fastHypot;
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @author  Rueben Schulz (UBC)
  * @author  Rémi Maréchal (Geomatys)
- * @version 0.6
+ * @version 1.2
  *
  * @see ObliqueStereographic
  *
@@ -70,28 +71,48 @@ public class PolarStereographic extends ConformalProjection 
{
     private static final long serialVersionUID = -6635298308431138524L;
 
     /**
-     * Codes for variants of Polar Stereographic projection. Those variants 
modify the way the projections are
-     * constructed (e.g. in the way parameters are interpreted), but formulas 
are basically the same after construction.
+     * Variants of Polar Stereographic projection. Those variants modify the 
way the projections are constructed
+     * (e.g. in the way parameters are interpreted), but formulas are 
basically the same after construction.
      * Those variants are not exactly the same than variants A, B and C used 
by EPSG, but they are closely related.
      *
      * <p>We do not provide such codes in public API because they duplicate 
the functionality of
      * {@link OperationMethod} instances. We use them only for constructors 
convenience.</p>
      *
-     * @see #getVariant(OperationMethod)
+     * <p>The default case is {@link #B}.</p>
      */
-    private static final byte A = 1, B = 2, C = 3, NORTH = 4, SOUTH = 5;
+    private enum Variant implements ProjectionVariant {
+        /** The <cite>"Polar Stereographic (Variant A)"</cite> projection. */
+        A(".*\\bvariant\\s*A\\b.*",  PolarStereographicA.IDENTIFIER),
 
-    /**
-     * Returns the type of the projection based on the name and identifier of 
the given operation method.
-     * If this method can not identify the type, then the parameters should be 
considered as a 2SP case.
-     */
-    private static byte getVariant(final OperationMethod method) {
-        if (identMatch(method, "(?i).*\\bvariant\\s*A\\b.*",  
PolarStereographicA.IDENTIFIER)) return A;
-        if (identMatch(method, "(?i).*\\bvariant\\s*B\\b.*",  
PolarStereographicB.IDENTIFIER)) return B;
-        if (identMatch(method, "(?i).*\\bvariant\\s*C\\b.*",  
PolarStereographicC.IDENTIFIER)) return C;
-        if (identMatch(method, "(?i).*\\bNorth\\b.*",         null)) return 
NORTH;
-        if (identMatch(method, "(?i).*\\bSouth\\b.*",         null)) return 
SOUTH;
-        return STANDARD_VARIANT;            // Unidentified case, to be 
considered as variant B.
+        /** The <cite>"Polar Stereographic (Variant B)"</cite> projection. */
+        B(".*\\bvariant\\s*B\\b.*",  PolarStereographicB.IDENTIFIER),
+
+        /** The <cite>"Polar Stereographic (Variant C)"</cite> projection. */
+        C(".*\\bvariant\\s*C\\b.*",  PolarStereographicC.IDENTIFIER),
+
+        /** <cite>"Stereographic North Pole"</cite> projection (ESRI). */
+        NORTH(".*\\bNorth\\b.*", null),
+
+        /** <cite>"Stereographic South Pole"</cite> projection (ESRI). */
+        SOUTH(".*\\bSouth\\b.*", null);
+
+        /** Name pattern for this variant.    */ private final Pattern 
operationName;
+        /** EPSG identifier for this variant. */ private final String  
identifier;
+        /** Creates a new enumeration value.  */
+        private Variant(final String operationName, final String identifier) {
+            this.operationName = Pattern.compile(operationName, 
Pattern.CASE_INSENSITIVE);
+            this.identifier    = identifier;
+        }
+
+        /** The expected name pattern of an operation method for this variant. 
*/
+        @Override public Pattern getOperationNamePattern() {
+            return operationName;
+        }
+
+        /** EPSG identifier of an operation method for this variant. */
+        @Override public String getIdentifier() {
+            return identifier;
+        }
     }
 
     /**
@@ -99,9 +120,11 @@ public class PolarStereographic extends ConformalProjection 
{
      * The {@code method} argument can be the description of one of the 
following:
      *
      * <ul>
-     *   <li><cite>"Polar Stereographic (Variant A)"</cite>.</li>
-     *   <li><cite>"Polar Stereographic (Variant B)"</cite>.</li>
-     *   <li><cite>"Polar Stereographic (Variant C)"</cite>.</li>
+     *   <li><cite>"Polar Stereographic (Variant A)"</cite> (EPSG:9810).</li>
+     *   <li><cite>"Polar Stereographic (Variant B)"</cite> (EPSG:9829).</li>
+     *   <li><cite>"Polar Stereographic (Variant C)"</cite> (EPSG:9830).</li>
+     *   <li><cite>"Stereographic North Pole"</cite> (ESRI).</li>
+     *   <li><cite>"Stereographic South Pole"</cite> (ESRI).</li>
      * </ul>
      *
      * @param method      description of the projection parameters.
@@ -117,18 +140,18 @@ public class PolarStereographic extends 
ConformalProjection {
      */
     @Workaround(library="JDK", version="1.7")
     private static Initializer initializer(final OperationMethod method, final 
Parameters parameters) {
-        final byte variant = getVariant(method);
+        final Variant variant = variant(method, Variant.values(), Variant.B);
         final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new 
EnumMap<>(ParameterRole.class);
         ParameterDescriptor<Double> falseEasting  = 
PolarStereographicA.FALSE_EASTING;
         ParameterDescriptor<Double> falseNorthing = 
PolarStereographicA.FALSE_NORTHING;
-        if (variant == C) {
+        if (variant == Variant.C) {
             falseEasting  = PolarStereographicC.EASTING_AT_FALSE_ORIGIN;
             falseNorthing = PolarStereographicC.NORTHING_AT_FALSE_ORIGIN;
         }
         roles.put(ParameterRole.FALSE_EASTING,    falseEasting);
         roles.put(ParameterRole.FALSE_NORTHING,   falseNorthing);
         roles.put(ParameterRole.SCALE_FACTOR,     
PolarStereographicA.SCALE_FACTOR);
-        roles.put(ParameterRole.CENTRAL_MERIDIAN, (variant == A)
+        roles.put(ParameterRole.CENTRAL_MERIDIAN, (variant == Variant.A)
                 ? PolarStereographicA.LONGITUDE_OF_ORIGIN
                 : PolarStereographicB.LONGITUDE_OF_ORIGIN);
         return new Initializer(method, parameters, roles, variant);
@@ -141,7 +164,7 @@ public class PolarStereographic extends ConformalProjection 
{
     @Workaround(library="JDK", version="1.7")
     private PolarStereographic(final Initializer initializer) {
         super(initializer);
-        final byte variant = initializer.variant;
+        final Variant variant = (Variant) initializer.variant;
         /*
          * "Standard parallel" and "Latitude of origin" should be mutually 
exclusive,
          * but this is not a strict requirement for the constructor.
@@ -158,19 +181,19 @@ public class PolarStereographic extends 
ConformalProjection {
          *   
└───────────────────────────────────┴────────────────────┴─────────────┘
          */
         double φ0;
-        if (variant == A) {
+        if (variant == Variant.A) {
             φ0 = 
initializer.getAndStore(PolarStereographicA.LATITUDE_OF_ORIGIN);   // Mandatory
         } else {
             φ0 = 
initializer.getAndStore(PolarStereographicA.LATITUDE_OF_ORIGIN,    // Optional 
(should not be present)
-                    (variant == NORTH) ? Latitude.MAX_VALUE :
-                    (variant == SOUTH) ? Latitude.MIN_VALUE : Double.NaN);
+                    (variant == Variant.NORTH) ? Latitude.MAX_VALUE :
+                    (variant == Variant.SOUTH) ? Latitude.MIN_VALUE : 
Double.NaN);
         }
         if (abs(abs(φ0) - Latitude.MAX_VALUE) > Formulas.ANGULAR_TOLERANCE) {  
     // Can be only -90°, +90° or NaN
             throw new 
IllegalArgumentException(Resources.format(Resources.Keys.IllegalParameterValue_2,
                     PolarStereographicA.LATITUDE_OF_ORIGIN.getName(), φ0));
         }
         double φ1;
-        if (variant == B || variant == C || Double.isNaN(φ0)) {
+        if (variant == Variant.B || variant == Variant.C || Double.isNaN(φ0)) {
             φ1 = 
initializer.getAndStore(PolarStereographicB.STANDARD_PARALLEL);        // 
Mandatory
         } else {
             φ1 = 
initializer.getAndStore(PolarStereographicB.STANDARD_PARALLEL, φ0);    // 
Optional
@@ -231,7 +254,7 @@ public class PolarStereographic extends ConformalProjection 
{
             final double sinφ1 = sin(φ1);
             final double mF = initializer.scaleAtφ(sinφ1, cos(φ1));
             ρ = new DoubleDouble(mF / expΨ(φ1, eccentricity*sinφ1));
-            ρF = (variant == C) ? new DoubleDouble(-mF) : null;
+            ρF = (variant == Variant.C) ? new DoubleDouble(-mF) : null;
         }
         /*
          * At this point, all parameters have been processed. Now process to 
their
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Polyconic.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Polyconic.java
index 61c37d2f1f..b4bf84d41a 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Polyconic.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Polyconic.java
@@ -101,7 +101,7 @@ public class Polyconic extends MeridianArcBased {
         roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_ORIGIN);
         roles.put(ParameterRole.FALSE_EASTING,    FALSE_EASTING);
         roles.put(ParameterRole.FALSE_NORTHING,   FALSE_NORTHING);
-        return new Initializer(method, parameters, roles, STANDARD_VARIANT);
+        return new Initializer(method, parameters, roles, null);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionVariant.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionVariant.java
new file mode 100644
index 0000000000..9fa591ca68
--- /dev/null
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionVariant.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.operation.projection;
+
+import java.util.regex.Pattern;
+
+
+/**
+ * Variant of the map projection used. This interface is implemented by 
enumerations
+ * in {@link NormalizedProjection} sub-classes that support many variants.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+interface ProjectionVariant {
+    /**
+     * Returns the regular expression pattern to use for determining if the 
name of an operation method
+     * identifies this variant.
+     *
+     * @return the operation name pattern for this variant.
+     */
+    Pattern getOperationNamePattern();
+
+    /**
+     * Returns the EPSG identifier to compare against the operation method.
+     * If non-null, the identifier is presumed in the EPSG namespace and has 
precedence over the pattern.
+     *
+     * @return EPSG identifier for this variant, or {@code null} if none.
+     */
+    String getIdentifier();
+
+    /**
+     * Whether this variant is a spherical variant using authalic radius.
+     * This method can be overridden for handling authalic radius, but not 
conformance sphere radius.
+     * The latter is handled by {@link 
NormalizedProjection.ParameterRole#LATITUDE_OF_CONFORMAL_SPHERE_RADIUS}.
+     *
+     * @return whether this variant is a spherical variant using authalic 
radius.
+     */
+    default boolean useAuthalicRadius() {
+        return false;
+    }
+}
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java
index 046e8cd677..1a1b122053 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java
@@ -116,7 +116,7 @@ public class SatelliteTracking extends NormalizedProjection 
{
         final EnumMap<NormalizedProjection.ParameterRole, 
ParameterDescriptor<Double>> roles = new 
EnumMap<>(NormalizedProjection.ParameterRole.class);
         roles.put(NormalizedProjection.ParameterRole.CENTRAL_MERIDIAN, 
CENTRAL_MERIDIAN);
         roles.put(ParameterRole.LATITUDE_OF_CONFORMAL_SPHERE_RADIUS, 
LATITUDE_OF_ORIGIN);
-        return new Initializer(method, parameters, roles, STANDARD_VARIANT);
+        return new Initializer(method, parameters, roles, null);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Sinusoidal.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Sinusoidal.java
index 684fd59870..38a12c3142 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Sinusoidal.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Sinusoidal.java
@@ -61,7 +61,7 @@ public class Sinusoidal extends MeridianArcBased {
         roles.put(ParameterRole.CENTRAL_MERIDIAN, CENTRAL_MERIDIAN);
         roles.put(ParameterRole.FALSE_EASTING,    FALSE_EASTING);
         roles.put(ParameterRole.FALSE_NORTHING,   FALSE_NORTHING);
-        return new Initializer(method, parameters, roles, STANDARD_VARIANT);
+        return new Initializer(method, parameters, roles, null);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java
index 25e946bb95..c1a07dc8da 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.EnumMap;
+import java.util.regex.Pattern;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.operation.MathTransform;
@@ -65,7 +66,7 @@ import static 
org.apache.sis.internal.referencing.provider.TransverseMercator.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
- * @version 1.1
+ * @version 1.2
  *
  * @see Mercator
  * @see ObliqueMercator
@@ -116,11 +117,30 @@ public class TransverseMercator extends 
NormalizedProjection {
     private static final boolean ALLOW_TRIGONOMETRIC_IDENTITIES = true;
 
     /**
-     * The "South orientated" variant of Transverse Mercator projection.
-     * Currently this is informative only (the south variant is handled
-     * with {@link ParameterRole} instead).
+     * Variants of the map projection. Currently this is informative only
+     * (the south variant is handled with {@link ParameterRole} instead).
      */
-    private static final byte SOUTH_VARIANT = 1;
+    private enum Variant implements ProjectionVariant {
+        /** The "South orientated" variant of Transverse Mercator projection. 
*/
+        SOUTH_ORIENTATED(".*\\bSouth\\b.*", 
TransverseMercatorSouth.IDENTIFIER);
+
+        /** Name pattern for this variant.    */ private final Pattern 
operationName;
+        /** EPSG identifier for this variant. */ private final String  
identifier;
+        private Variant(final String operationName, final String identifier) {
+            this.operationName = Pattern.compile(operationName, 
Pattern.CASE_INSENSITIVE);
+            this.identifier    = identifier;
+        }
+
+        /** The expected name pattern of an operation method for this variant. 
*/
+        @Override public Pattern getOperationNamePattern() {
+            return operationName;
+        }
+
+        /** EPSG identifier of an operation method for this variant. */
+        @Override public String getIdentifier() {
+            return identifier;
+        }
+    }
 
     /**
      * Verifies if a trigonometric identity produced the expected value. This 
method is used in assertions only,
@@ -181,11 +201,11 @@ public class TransverseMercator extends 
NormalizedProjection {
      */
     @Workaround(library="JDK", version="1.7")
     private static Initializer initializer(final OperationMethod method, final 
Parameters parameters) {
-        final boolean isSouth = identMatch(method, "(?i).*\\bSouth\\b.*", 
TransverseMercatorSouth.IDENTIFIER);
+        final Variant variant = variant(method, Variant.values(), null);
         final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new 
EnumMap<>(ParameterRole.class);
         ParameterRole xOffset = ParameterRole.FALSE_EASTING;
         ParameterRole yOffset = ParameterRole.FALSE_NORTHING;
-        if (isSouth) {
+        if (variant == Variant.SOUTH_ORIENTATED) {
             xOffset = ParameterRole.FALSE_WESTING;
             yOffset = ParameterRole.FALSE_SOUTHING;
         }
@@ -193,7 +213,7 @@ public class TransverseMercator extends 
NormalizedProjection {
         roles.put(ParameterRole.SCALE_FACTOR, SCALE_FACTOR);
         roles.put(xOffset, FALSE_EASTING);
         roles.put(yOffset, FALSE_NORTHING);
-        return new Initializer(method, parameters, roles, isSouth ? 
SOUTH_VARIANT : STANDARD_VARIANT);
+        return new Initializer(method, parameters, roles, variant);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java
index 778a53c8d8..b5b66a2a30 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java
@@ -125,7 +125,7 @@ public class ZonedGridSystem extends 
AbstractMathTransform2D implements Serializ
         roles.put(NormalizedProjection.ParameterRole.SCALE_FACTOR,   
SCALE_FACTOR);
         roles.put(NormalizedProjection.ParameterRole.FALSE_EASTING,  
FALSE_EASTING);
         roles.put(NormalizedProjection.ParameterRole.FALSE_NORTHING, 
FALSE_NORTHING);
-        final Initializer initializer = new Initializer(method, parameters, 
roles, NormalizedProjection.STANDARD_VARIANT);
+        final Initializer initializer = new Initializer(method, parameters, 
roles, null);
         initialLongitude = initializer.getAndStore(INITIAL_LONGITUDE);
         zoneWidth        = initializer.getAndStore(ZONE_WIDTH);
         final MatrixSIS normalize = 
initializer.context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION);
diff --git 
a/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
 
b/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
index f5398c4f16..0e11fa01f6 100644
--- 
a/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
+++ 
b/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod
@@ -32,6 +32,7 @@ org.apache.sis.internal.referencing.provider.Mercator1SP
 org.apache.sis.internal.referencing.provider.Mercator2SP
 org.apache.sis.internal.referencing.provider.MercatorSpherical
 org.apache.sis.internal.referencing.provider.PseudoMercator
+org.apache.sis.internal.referencing.provider.MercatorAuxiliarySphere
 org.apache.sis.internal.referencing.provider.RegionalMercator
 org.apache.sis.internal.referencing.provider.MillerCylindrical
 org.apache.sis.internal.referencing.provider.LambertConformal1SP
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
index 18096e3c63..4810aa159a 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java
@@ -81,6 +81,7 @@ public final strictfp class ProvidersTest extends TestCase {
             Mercator2SP.class,
             MercatorSpherical.class,
             PseudoMercator.class,
+            MercatorAuxiliarySphere.class,
             RegionalMercator.class,
             MillerCylindrical.class,
             LambertConformal1SP.class,
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/InitializerTest.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/InitializerTest.java
index a6e93d7cea..1e0cd789af 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/InitializerTest.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/InitializerTest.java
@@ -77,7 +77,7 @@ public final strictfp class InitializerTest extends TestCase{
         roles.put(NormalizedProjection.ParameterRole.SCALE_FACTOR,     
ObliqueStereographic.SCALE_FACTOR);
         roles.put(NormalizedProjection.ParameterRole.FALSE_EASTING,    
ObliqueStereographic.FALSE_EASTING);
         roles.put(NormalizedProjection.ParameterRole.FALSE_NORTHING,   
ObliqueStereographic.FALSE_NORTHING);
-        final Initializer initializer = new Initializer(op, (Parameters) p, 
roles, NormalizedProjection.STANDARD_VARIANT);
+        final Initializer initializer = new Initializer(op, (Parameters) p, 
roles, null);
         /*
          * The following lines give an example of how Apache SIS projection 
constructors
          * use the Initializer class.
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
index 978fe9e707..89cb0ee67d 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
@@ -33,7 +33,7 @@ import org.apache.sis.util.Workaround;
  * This is used for testing methods other than {@code transform(…)} and {@code 
inverseTransform(…)}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
+ * @version 1.2
  * @since   0.6
  * @module
  */
@@ -74,7 +74,7 @@ final strictfp class NoOp extends ConformalProjection {
         super(new Initializer(new DefaultOperationMethod(
                 Collections.singletonMap(DefaultOperationMethod.NAME_KEY, 
parameters.getDescriptor().getName()),
                 DIMENSION, DIMENSION,
-                parameters.getDescriptor()), parameters, 
Collections.emptyMap(), STANDARD_VARIANT));
+                parameters.getDescriptor()), parameters, 
Collections.emptyMap(), null));
     }
 
     /**

Reply via email to