sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1693908 [2/3] - in /sis/branches/JDK7: ./ core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/metadata/replace/ core/sis-metadata/src/main/java/org/apache/sis/io/wkt/ core/sis-metadata/src/main/java/org/apache/sis/metadata/ core/s...
Date Mon, 03 Aug 2015 14:25:02 GMT
Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -16,19 +16,15 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import java.util.Map;
 import java.io.IOException;
 import java.io.ObjectInputStream;
-import org.opengis.parameter.ParameterDescriptor;
-import org.opengis.referencing.operation.OperationMethod;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.parameter.Parameters;
 
 import static java.lang.Math.*;
 
 
 /**
- * Base class of {@link LambertConformal}, {@link Mercator} and {@link PolarStereographic} projections.
+ * Base class of {@link LambertConicConformal}, {@link Mercator} and {@link PolarStereographic} projections.
  * All those projections have in common the property of being <cite>conformal</cite>, i.e. they preserve
  * angles locally. However we do not put this base class in public API because we do not (yet) guarantee
  * than all conformal projections will extend this base class.
@@ -45,6 +41,12 @@ import static java.lang.Math.*;
  * appear to be the same with the <var>n</var> factor fixed to 1 or -1, so we leverage the code provided by
  * this base class. This class hierarchy is only an implementation convenience and not part of public API.</p>
  *
+ * <div class="note"><b>Reference:</b>
+ * “Lambert developed the regular Conformal Conic as the oblique aspect of a family containing the previously
+ * known polar Stereographic and regular Mercator projections. (…) If the standard parallels are symmetrical
+ * about the Equator, the regular Mercator results (although formulas must be revised). If the only standard
+ * parallel is a pole, the polar Stereographic results.” (Snyder, page 105)</div>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.6
  * @version 0.6
@@ -101,17 +103,12 @@ abstract class ConformalProjection exten
     private transient boolean useIterations;
 
     /**
-     * Constructs a new map projection from the supplied parameters.
+     * Creates a new normalized projection from the parameters computed by the given initializer.
      *
-     * @param method     Description of the map projection parameters.
-     * @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.
-     */
-    protected ConformalProjection(final OperationMethod method, final Parameters parameters,
-            final Map<ParameterRole, ? extends ParameterDescriptor<Double>> roles)
-    {
-        super(method, parameters, roles);
+     * @param initializer The initializer for computing map projection internal parameters.
+     */
+    ConformalProjection(final Initializer initializer) {
+        super(initializer);
         initialize();
     }
 

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import java.util.Map;
 import java.util.EnumMap;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterDescriptor;
@@ -39,6 +38,7 @@ import org.apache.sis.util.Workaround;
 import static java.lang.Math.*;
 import static java.lang.Double.*;
 import static org.apache.sis.math.MathFunctions.isPositive;
+import static org.apache.sis.internal.util.DoubleDouble.verbatim;
 
 
 /**
@@ -125,13 +125,33 @@ public class Mercator extends ConformalP
     private final byte variant;
 
     /**
-     * Returns the (<var>role</var> → <var>parameter</var>) associations for a Mercator projection of the given variant.
+     * Creates a Mercator projection from the given parameters.
+     * The {@code method} argument can be the description of one of the following:
+     *
+     * <ul>
+     *   <li><cite>"Mercator (variant A)"</cite>, also known as <cite>"Mercator (1SP)"</cite>.</li>
+     *   <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>"Popular Visualisation Pseudo Mercator"</cite>.</li>
+     *   <li><cite>"Miller Cylindrical"</cite>.</li>
+     * </ul>
      *
-     * @param  variant One of {@link #REGIONAL}, {@link #SPHERICAL}, {@link #PSEUDO} or {@link #MILLER} constants.
-     * @return The roles map to give to super-class constructor.
+     * @param method     Description of the projection parameters.
+     * @param parameters The parameter values of the projection to create.
+     */
+    public Mercator(final OperationMethod method, final Parameters parameters) {
+        this(initializer(method, parameters));
+    }
+
+    /**
+     * Work around for RFE #4093999 in Sun's bug database
+     * ("Relax constraint on placement of this()/super() call in constructors").
      */
     @SuppressWarnings("fallthrough")
-    private static Map<ParameterRole, ParameterDescriptor<Double>> roles(final byte variant) {
+    @Workaround(library="JDK", version="1.7")
+    private static Initializer initializer(final OperationMethod method, final Parameters parameters) {
+        final byte variant = getVariant(method);
         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
@@ -171,27 +191,7 @@ public class Mercator extends ConformalP
                 break;
             }
         }
-        return roles;
-    }
-
-    /**
-     * Creates a Mercator projection from the given parameters.
-     * The {@code method} argument can be the description of one of the following:
-     *
-     * <ul>
-     *   <li><cite>"Mercator (variant A)"</cite>, also known as <cite>"Mercator (1SP)"</cite>.</li>
-     *   <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>"Popular Visualisation Pseudo Mercator"</cite>.</li>
-     *   <li><cite>"Miller Cylindrical"</cite>.</li>
-     * </ul>
-     *
-     * @param method     Description of the projection parameters.
-     * @param parameters The parameter values of the projection to create.
-     */
-    public Mercator(final OperationMethod method, final Parameters parameters) {
-        this(method, parameters, getVariant(method));
+        return new Initializer(method, parameters, roles, variant);
     }
 
     /**
@@ -199,15 +199,15 @@ public class Mercator extends ConformalP
      * ("Relax constraint on placement of this()/super() call in constructors").
      */
     @Workaround(library="JDK", version="1.7")
-    private Mercator(final OperationMethod method, final Parameters parameters, final byte variant) {
-        super(method, parameters, roles(variant));
-        this.variant = variant;
+    private Mercator(final Initializer initializer) {
+        super(initializer);
+        this.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
          * for now; it will be converted to radians later.
          */
-        final double λ0 = getAndStore(parameters, Mercator1SP.LONGITUDE_OF_ORIGIN);
+        final double λ0 = initializer.getAndStore(Mercator1SP.LONGITUDE_OF_ORIGIN);
         /*
          * The "Latitude of natural origin" is not formally a parameter of Mercator projection. But the parameter
          * is included for completeness in CRS labelling, with the restriction (specified in EPSG documentation)
@@ -220,7 +220,7 @@ public class Mercator extends ConformalP
          * "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(getAndStore(parameters, (variant == REGIONAL)
+        final double φ0 = toRadians(initializer.getAndStore((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
@@ -228,9 +228,8 @@ public class Mercator extends ConformalP
          * the later is for projections "1SP" (namely variant A and spherical). However we let users specify both
          * if they really want, since we sometime see such CRS definitions.
          */
-        final double φ1 = toRadians(getAndStore(parameters, Mercator2SP.STANDARD_PARALLEL));
-        final DoubleDouble k0 = new DoubleDouble(cos(φ1), 0);
-        k0.divide(rν(sin(φ1)), 0);
+        final double φ1 = toRadians(initializer.getAndStore(Mercator2SP.STANDARD_PARALLEL));
+        final Number k0 = verbatim(initializer.scaleAtφ(sin(φ1), cos(φ1)));
         /*
          * In principle we should rotate the central meridian (λ0) in the normalization transform, as below:
          *
@@ -247,16 +246,21 @@ public class Mercator extends ConformalP
         denormalize.convertBefore(0, k0, null);
         denormalize.convertBefore(1, k0, null);
         if (λ0 != 0) {
+            /*
+             * Use double-double arithmetic here for consistency with the work done in the normalization matrix.
+             * The intend 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();
             offset.multiply(-λ0);
             denormalize.convertBefore(0, null, offset);
         }
         if (φ0 != 0) {
-            denormalize.convertBefore(1, null, new DoubleDouble(-log(expOfNorthing(φ0, excentricity * sin(φ0)))));
+            denormalize.convertBefore(1, null, verbatim(-log(expOfNorthing(φ0, excentricity * sin(φ0)))));
         }
         if (variant == MILLER) {
-              normalize.convertBefore(1, new DoubleDouble(0.80), null);
-            denormalize.convertBefore(1, new DoubleDouble(1.25), null);
+            normalize  .convertBefore(1, 0.80, null);
+            denormalize.convertBefore(1, 1.25, null);
         }
         /*
          * At this point we are done, but we add here a little bit a maniac precision hunting.
@@ -282,9 +286,9 @@ public class Mercator extends ConformalP
          * those remaning lines of code.
          */
         if (φ0 == 0 && isPositive(φ1 != 0 ? φ1 : φ0)) {
-            final DoubleDouble revert = new DoubleDouble(-1, 0);
-              normalize.convertBefore(1, revert, null);
-            denormalize.convertBefore(1, revert, null);  // Must be before false easting/northing.
+            final Number reverseSign = verbatim(-1);
+            normalize  .convertBefore(1, reverseSign, null);
+            denormalize.convertBefore(1, reverseSign, null);  // Must be before false easting/northing.
         }
     }
 

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -16,16 +16,14 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import java.util.List;
-import java.util.ArrayList;
 import java.util.Map;
+import java.util.HashMap;
 import java.io.Serializable;
+import java.lang.reflect.Modifier;
 import org.opengis.metadata.Identifier;
-import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
-import org.opengis.parameter.GeneralParameterDescriptor;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransform2D;
@@ -34,22 +32,22 @@ import org.opengis.referencing.operation
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.opengis.util.FactoryException;
 import org.apache.sis.util.Debug;
+import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.parameter.Parameters;
-import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
-import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import org.apache.sis.referencing.operation.transform.AbstractMathTransform2D;
 import org.apache.sis.referencing.operation.transform.ContextualParameters;
-import org.apache.sis.internal.referencing.provider.MapProjection;
 import org.apache.sis.internal.referencing.Formulas;
-import org.apache.sis.internal.util.DoubleDouble;
+import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.util.Utilities;
 import org.apache.sis.internal.util.Numerics;
 
 import static java.lang.Math.*;
-import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 
 // Branch-dependent imports
 import java.util.Objects;
@@ -132,14 +130,19 @@ public abstract class NormalizedProjecti
 
     /**
      * Maximum difference allowed when comparing longitudes or latitudes in radians.
-     * The current value take the system-wide angular tolerance value (equivalent to
+     * The current value takes the system-wide angular tolerance value (equivalent to
      * about 1 cm on Earth) converted to radians.
      *
      * <p>Some formulas use this tolerance value for testing sines or cosines of an angle.
      * In the sine case, this is justified because sin(θ) ≅ θ when θ is small.
      * Similar reasoning applies to cosine with cos(θ) ≅ θ + π/2 when θ is small.</p>
+     *
+     * <p>Some formulas may use this tolerance value as a <em>linear</em> tolerance on the unit sphere.
+     * This is okay because the arc length for an angular tolerance θ is r⋅θ, but in this class r=1.</p>
      */
     static final double ANGULAR_TOLERANCE = Formulas.ANGULAR_TOLERANCE * (PI/180);
+    // Note: an alternative way to compute this value could be Formulas.LINEAR_TOLERANCE / AUTHALIC_RADIUS.
+    // But the later is only 0.07% lower than the current value.
 
     /**
      * Desired accuracy for the result of iterative computations, in radians.
@@ -149,7 +152,7 @@ public abstract class NormalizedProjecti
      * So if the linear tolerance is 1 cm, then the accuracy that we will seek for is 0.25 cm (about
      * 4E-10 radians). The 0.25 factor is a safety margin for meeting the 1 cm accuracy.</p>
      */
-    static final double ITERATION_TOLERANCE = Formulas.ANGULAR_TOLERANCE * (PI/180) * 0.25;
+    static final double ITERATION_TOLERANCE = ANGULAR_TOLERANCE * 0.25;
 
     /**
      * Maximum number of iterations for iterative computations.
@@ -160,6 +163,17 @@ public abstract class NormalizedProjecti
     static final int MAXIMUM_ITERATIONS = 15;
 
     /**
+     * The internal parameter descriptors. Keys are implementation classes.  Values are parameter descriptor groups
+     * containing at least a parameter for the {@link #excentricity} value, and optionally other internal parameter
+     * added by some subclasses.
+     *
+     * <p>Entries are created only when first needed. Those descriptors are usually never created since they are
+     * used only by {@link #getParameterDescriptors()}, which is itself invoked mostly for debugging purpose.</p>
+     */
+    @Debug
+    private static final Map<Class<?>,ParameterDescriptorGroup> DESCRIPTORS = new HashMap<>();
+
+    /**
      * The parameters used for creating this projection. They are used for formatting <cite>Well Known Text</cite> (WKT)
      * and error messages. Subclasses shall not use the values defined in this object for computation purpose, except at
      * construction time.
@@ -383,60 +397,21 @@ public abstract class NormalizedProjecti
      *        <cite>false easting</cite>, <cite>false northing</cite> and other values.
      */
     protected NormalizedProjection(final OperationMethod method, final Parameters parameters,
-            final Map<ParameterRole, ? extends ParameterDescriptor<Double>> roles)
+            final Map<ParameterRole, ? extends ParameterDescriptor<? extends Number>> roles)
     {
-        ensureNonNull("method",     method);
-        ensureNonNull("parameters", parameters);
-        ensureNonNull("roles",      roles);
-        context = new ContextualParameters(method);
-        /*
-         * Note: we do not use Map.getOrDefault(K,V) below because the user could have explicitly associated
-         * a null value to keys (we are paranoiac...) and because it conflicts with the "? extends" part of
-         * in this constructor signature.
-         */
-        ParameterDescriptor<Double> semiMajor = roles.get(ParameterRole.SEMI_MAJOR);
-        ParameterDescriptor<Double> semiMinor = roles.get(ParameterRole.SEMI_MINOR);
-        if (semiMajor == null) semiMajor = MapProjection.SEMI_MAJOR;
-        if (semiMinor == null) semiMinor = MapProjection.SEMI_MINOR;
-
-              double a  = getAndStore(parameters, semiMajor);
-        final double b  = getAndStore(parameters, semiMinor);
-        final double λ0 = getAndStore(parameters, roles.get(ParameterRole.CENTRAL_MERIDIAN));
-        final double fe = getAndStore(parameters, roles.get(ParameterRole.FALSE_EASTING))
-                        - getAndStore(parameters, roles.get(ParameterRole.FALSE_WESTING));
-        final double fn = getAndStore(parameters, roles.get(ParameterRole.FALSE_NORTHING))
-                        - getAndStore(parameters, roles.get(ParameterRole.FALSE_SOUTHING));
-        final double rs = b / a;
-        excentricitySquared = 1 - (rs * rs);
-        excentricity = sqrt(excentricitySquared);
-        if (excentricitySquared != 0) {
-            final ParameterDescriptor<Double> radius = roles.get(ParameterRole.LATITUDE_OF_CONFORMAL_SPHERE_RADIUS);
-            if (radius != null) {
-                /*
-                 * EPSG said: R is the radius of the sphere and will normally be one of the CRS parameters.
-                 * If the figure of the earth used is an ellipsoid rather than a sphere then R should be calculated
-                 * as the radius of the conformal sphere at the projection origin at latitude φ₀ using the formula
-                 * for Rc given in section 1.2, table 3.
-                 *
-                 * Table 3 gives:
-                 * Radius of conformal sphere Rc = a √(1 – ℯ²) / (1 – ℯ²⋅sin²φ)
-                 *
-                 * Using √(1 – ℯ²) = b/a we rewrite as: Rc = b / (1 – ℯ²⋅sin²φ)
-                 */
-                final double sinφ = sin(toRadians(parameters.doubleValue(radius)));
-                a = b / (1 - excentricitySquared * (sinφ*sinφ));
-            }
-        }
-        context.normalizeGeographicInputs(λ0);
-        final DoubleDouble k = new DoubleDouble(a);
-        final ParameterDescriptor<Double> scaleFactor = roles.get(ParameterRole.SCALE_FACTOR);
-        if (scaleFactor != null) {
-            k.multiply(getAndStore(parameters, scaleFactor));
-        }
-        final MatrixSIS denormalize = context.getMatrix(false);
-        denormalize.convertAfter(0, k, new DoubleDouble(fe));
-        denormalize.convertAfter(1, k, new DoubleDouble(fn));
-        inverse = new Inverse();
+        this(new Initializer(method, parameters, roles, (byte) 0));
+    }
+
+    /**
+     * Creates a new normalized projection from the parameters computed by the given initializer.
+     *
+     * @param initializer The initializer for computing map projection internal parameters.
+     */
+    NormalizedProjection(final Initializer initializer) {
+        context             = initializer.context;
+        excentricitySquared = initializer.excentricitySquared.value;
+        excentricity        = sqrt(excentricitySquared);  // DoubleDouble.sqrt() does not make any difference here.
+        inverse             = new Inverse();
     }
 
     /**
@@ -480,50 +455,6 @@ public abstract class NormalizedProjecti
     }
 
     /**
-     * Gets a parameter value identified by the given descriptor and stores it in the {@link #context}.
-     * A "contextual parameter" is a parameter that apply to the normalize → {@code this} → denormalize
-     * chain as a whole. It does not really apply to this {@code NormalizedProjection} instance when taken alone.
-     *
-     * <p>This method performs the following actions:</p>
-     * <ul>
-     *   <li>Convert the value to the units specified by the descriptor.</li>
-     *   <li>Ensure that the value is contained in the range specified by the descriptor.</li>
-     *   <li>Store the value only if different than the default value.</li>
-     * </ul>
-     *
-     * This method shall be invoked at construction time only.
-     */
-    final double getAndStore(final Parameters parameters, final ParameterDescriptor<Double> descriptor) {
-        if (descriptor == null) {
-            return 0;   // Default value for all parameters except scale factor.
-        }
-        final double value = parameters.doubleValue(descriptor);    // Apply a unit conversion if needed.
-        final Double defaultValue = descriptor.getDefaultValue();
-        if (defaultValue == null || !defaultValue.equals(value)) {
-            MapProjection.validate(descriptor, value);
-            context.getOrCreate(descriptor).setValue(value);
-        }
-        return value;
-    }
-
-    /**
-     * Same as {@link #getAndStore(Parameters, 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 what we can represent in {@link ParameterDescriptor#getDefaultValue()}.
-     */
-    final double getAndStore(final Parameters parameters, final ParameterDescriptor<Double> descriptor,
-            final double defaultValue)
-    {
-        final Double value = parameters.getValue(descriptor);   // Apply a unit conversion if needed.
-        if (value == null) {
-            return defaultValue;
-        }
-        MapProjection.validate(descriptor, value);
-        context.parameter(descriptor.getName().getCode()).setValue(value);
-        return value;
-    }
-
-    /**
      * Returns the sequence of <cite>normalization</cite> → {@code this} → <cite>denormalization</cite> transforms
      * as a whole. The transform returned by this method except (<var>longitude</var>, <var>latitude</var>)
      * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
@@ -566,18 +497,11 @@ public abstract class NormalizedProjecti
     }
 
     /**
-     * Returns a copy of the parameter values for this projection.
-     * This base class supplies a value only for the following parameters:
-     *
-     * <ul>
-     *   <li>Semi-major axis length, which is set to 1.</li>
-     *   <li>Semi-minor axis length, which is set to
-     *       <code>sqrt(1 - {@linkplain #excentricitySquared ℯ²})</code>.</li>
-     * </ul>
-     *
-     * Subclasses must complete if needed. Many projections will not need to complete,
-     * because most parameters like the scale factor or the false easting/northing can
-     * be handled by the (de)normalization affine transforms.
+     * Returns a copy of non-linear internal parameter values of this {@code NormalizedProjection}.
+     * The returned group contained at least the {@link #excentricity} parameter value.
+     * Some subclasses add more non-linear parameters, but most of them do not because many parameters
+     * like the <cite>scale factor</cite> or the <cite>false easting/northing</cite> are handled by the
+     * {@linkplain ContextualParameters#getMatrix(boolean) (de)normalization affine transforms} instead.
      *
      * <div class="note"><b>Note:</b>
      * This method is mostly for {@linkplain org.apache.sis.io.wkt.Convention#INTERNAL debugging purposes}
@@ -585,56 +509,85 @@ public abstract class NormalizedProjecti
      * Most GIS applications will instead be interested in the {@linkplain #getContextualParameters()
      * contextual parameters}.</div>
      *
-     * @return A copy of the parameter values for this normalized projection.
+     * @return A copy of the internal parameter values for this normalized projection.
      */
     @Debug
     @Override
     public ParameterValueGroup getParameterValues() {
-        return getParameterValues(new String[] {
-            Constants.SEMI_MAJOR,
-            Constants.SEMI_MINOR
-        });
+        final ParameterValueGroup group = getParameterDescriptors().createValue();
+        group.parameter("excentricity").setValue(excentricity);
+        final String[] names  = getInternalParameterNames();
+        final double[] values = getInternalParameterValues();
+        for (int i=0; i<names.length; i++) {
+            group.parameter(names[i]).setValue(values[i]);
+        }
+        return group;
     }
 
     /**
-     * Filters the parameter descriptor in order to retain only the parameters of the given names, and
-     * sets the semi-major and semi-minor axis lengths. The specified parameters list should contains at
-     * least the {@code "semi_major"} and {@code "semi_minor"} strings.
+     * Returns a description of the non-linear internal parameters of this {@code NormalizedProjection}.
+     * The returned group contained at least a descriptor for the {@link #excentricity} parameter.
+     * Subclasses may add more parameters.
+     *
+     * <p>This method is for inspecting the parameter values of this non-linear kernel only,
+     * not for inspecting the {@linkplain #getContextualParameters() contextual parameters}.
+     * Inspecting the kernel parameter values is usually for debugging purpose only.</p>
      *
-     * <p>This filtered descriptor is used for displaying the parameter values of this non-linear kernel only,
-     * not for displaying the {@linkplain #getContextualParameters() contextual parameters}. Since displaying
-     * the kernel parameter values is for debugging purpose only, it is not worth to cache this descriptor.</p>
+     * @return A description of the internal parameters.
      */
     @Debug
-    final ParameterValueGroup getParameterValues(final String[] nonLinearParameters) {
-        ParameterDescriptorGroup descriptor = getParameterDescriptors();
-        final List<GeneralParameterDescriptor> filtered = new ArrayList<>(nonLinearParameters.length);
-        for (final GeneralParameterDescriptor p : descriptor.descriptors()) {
-            for (final String name : nonLinearParameters) {
-                if (IdentifiedObjects.isHeuristicMatchForName(p, name)) {
-                    filtered.add(p);
-                    break;
+    @Override
+    public ParameterDescriptorGroup getParameterDescriptors() {
+        Class<?> type = getClass();
+        while (!Modifier.isPublic(type.getModifiers())) {
+            type = type.getSuperclass();
+        }
+        ParameterDescriptorGroup group;
+        synchronized (DESCRIPTORS) {
+            group = DESCRIPTORS.get(type);
+            if (group == null) {
+                final ParameterBuilder builder = new ParameterBuilder().setRequired(true);
+                if (Utilities.isSIS(type)) {
+                    builder.setCodeSpace(Citations.SIS, "SIS");
                 }
+                final String[] names = getInternalParameterNames();
+                final ParameterDescriptor<?>[] parameters = new ParameterDescriptor<?>[names.length + 1];
+                for (int i=0; i<parameters.length; i++) {
+                    final ParameterDescriptor<?> p;
+                    if (i == 0) {
+                        final ParameterDescriptorGroup existing = CollectionsExt.first(DESCRIPTORS.values());
+                        if (existing != null) {
+                            p = (ParameterDescriptor<?>) existing.descriptor("excentricity");
+                        } else {
+                            p = builder.addName(Citations.SIS, "excentricity").createBounded(0, 1, Double.NaN, null);
+                        }
+                    } else {
+                        p = builder.addName(names[i-1]).create(Double.class, null);
+                    }
+                    parameters[i] = p;
+                }
+                group = builder.addName(CharSequences.camelCaseToSentence(type.getSimpleName())).createGroup(1, 1, parameters);
+                DESCRIPTORS.put(type, group);
             }
         }
-        descriptor = new DefaultParameterDescriptorGroup(IdentifiedObjects.getProperties(descriptor),
-                1, 1, filtered.toArray(new GeneralParameterDescriptor[filtered.size()]));
-        /*
-         * Parameter values for the ellipsoid semi-major and semi-minor axis lengths are 1 and <= 1
-         * respectively because the denormalization (e.g. multiplication by a scale factor) will be
-         * applied by an affine transform after this NormalizedProjection.
-         */
-        final ParameterValueGroup values = descriptor.createValue();
-        for (final GeneralParameterDescriptor desc : filtered) {
-            final String name = desc.getName().getCode();
-            final ParameterValue<?> p = values.parameter(name);
-            switch (name) {
-                case Constants.SEMI_MAJOR: p.setValue(1.0); break;
-                case Constants.SEMI_MINOR: p.setValue(sqrt(1 - excentricitySquared)); break;
-                default: p.setValue(context.parameter(name).getValue());
-            }
-        }
-        return values;
+        return group;
+    }
+
+    /**
+     * Returns the names of any additional internal parameters (other than {@link #excentricity})
+     * that this projection has. The length of this array must be the same than the length of the
+     * {@link #getInternalParameterValues()} array, if the later is non-null.
+     */
+    String[] getInternalParameterNames() {
+        return CharSequences.EMPTY_ARRAY;
+    }
+
+    /**
+     * Returns the values of any additional internal parameters (other than {@link #excentricity}) that
+     * this projection has. Those values are also compared by {@link #equals(Object, ComparisonMode)}.
+     */
+    double[] getInternalParameterValues() {
+        return null;
     }
 
     /**
@@ -760,14 +713,20 @@ public abstract class NormalizedProjecti
     }
 
     /**
-     * Computes a hash code value for this map projection.
-     * The default implementation computes a value from the parameters given at construction time.
+     * Computes a hash code value for this {@code NormalizedProjection}.
      *
      * @return The hash code value.
      */
     @Override
     protected int computeHashCode() {
-        return context.hashCode() + 31 * super.computeHashCode();
+        long c = Double.doubleToLongBits(excentricity);
+        final double[] parameters = getInternalParameterValues();
+        if (parameters != null) {
+            for (int i=0; i<parameters.length; i++) {
+                c = c*31 + Double.doubleToLongBits(parameters[i]);
+            }
+        }
+        return super.computeHashCode() ^ Numerics.hashCode(c);
     }
 
     /**
@@ -792,70 +751,82 @@ public abstract class NormalizedProjecti
      * @return {@code true} if the given object is equivalent to this map projection.
      */
     @Override
+    @SuppressWarnings("fallthrough")
     public boolean equals(final Object object, final ComparisonMode mode) {
         if (object == this) {
             return true;
         }
-        if (super.equals(object, mode)) {
-            final double e1, e2;
-            final NormalizedProjection that = (NormalizedProjection) object;
-            if (mode.ordinal() < ComparisonMode.IGNORE_METADATA.ordinal()) {
+        if (!super.equals(object, mode)) {
+            return false;
+        }
+        final NormalizedProjection that = (NormalizedProjection) object;
+        switch (mode) {
+            case STRICT:
+            case BY_CONTRACT: {
                 if (!Objects.equals(context, that.context)) {
                     return false;
                 }
-                e1 = this.excentricitySquared;
-                e2 = that.excentricitySquared;
-            } else {
-                e1 = this.excentricity;
-                e2 = that.excentricity;
+                // Fall through for comparing the excentricity.
+            }
+            case IGNORE_METADATA: {
+                /*
+                 * There is no need to compare both 'excentricity' and 'excentricitySquared' since the former
+                 * is computed from the later. We are better to compare 'excentricitySquared' since it is the
+                 * original value from which the other value is derived.
+                 */
+                if (!Numerics.equals(excentricitySquared, that.excentricitySquared)) {
+                    return false;
+                }
+                break;
+            }
+            default: {
+                /*
+                 * We want to compare the excentricity with a tolerance threshold corresponding approximatively
+                 * to an error of 1 cm on Earth. The excentricity for an ellipsoid of semi-major axis a=1 is:
+                 *
+                 *     ℯ² = 1 - b²
+                 *
+                 * If we add a slight ε error to the semi-minor axis length (where ε will be our linear tolerance
+                 * threshold), we get:
+                 *
+                 *     (ℯ + ε′)²    =    1 - (b + ε)²    ≈    1 - (b² + 2⋅b⋅ε)    assuming ε ≪ b
+                 *
+                 * Replacing  1 - b²  by  ℯ²:
+                 *
+                 *     ℯ² + 2⋅ℯ⋅ε′  ≈   ℯ² - 2⋅b⋅ε
+                 *
+                 * After a few rearrangements:
+                 *
+                 *     ε′  ≈   ε⋅(ℯ - 1/ℯ)
+                 *
+                 * Note that  ε′  is negative for  ℯ < 1  so we actually need to compute  ε⋅(1/ℯ - ℯ)  instead.
+                 * The result is less than 2E-8 for the excentricity of the Earth.
+                 */
+                final double e = max(excentricity, that.excentricity);
+                if (!Numerics.epsilonEqual(excentricity, that.excentricity, ANGULAR_TOLERANCE * (1/e - e))) {
+                    assert (mode != ComparisonMode.DEBUG) : Numerics.messageForDifference(
+                            "excentricity", excentricity, that.excentricity);
+                    return false;
+                }
+                break;
             }
+        }
+        final double[] parameters = getInternalParameterValues();
+        if (parameters != null) {
             /*
-             * There is no need to compare both 'excentricity' and 'excentricitySquared' since
-             * the former is computed from the later. In strict comparison mode, we are better
-             * to compare the 'excentricitySquared' since it is the original value from which
-             * the other value is derived. However in approximative comparison mode, we need
-             * to use the 'excentricity', otherwise we would need to take the square of the
-             * tolerance factor before comparing 'excentricitySquared'.
+             * super.equals(…) guarantees that the two objects are of the same class.
+             * So in SIS implementation, this implies that the arrays have the same length.
              */
-            return Numerics.epsilonEqual(e1, e2, mode);
+            final double[] others = that.getInternalParameterValues();
+            assert others.length == parameters.length;
+            for (int i=0; i<parameters.length; i++) {
+                if (!Numerics.epsilonEqual(parameters[i], others[i], mode)) {
+                    assert (mode != ComparisonMode.DEBUG) : Numerics.messageForDifference(
+                            getInternalParameterNames()[i], parameters[i], others[i]);
+                    return false;
+                }
+            }
         }
-        return false;
-    }
-
-
-
-
-    //////////////////////////////////////////////////////////////////////////////////////////
-    ////////                                                                          ////////
-    ////////                       FORMULAS FROM EPSG or SNYDER                       ////////
-    ////////                                                                          ////////
-    //////////////////////////////////////////////////////////////////////////////////////////
-
-    /**
-     * Computes the reciprocal of the radius of curvature of the ellipsoid perpendicular to the meridian at latitude φ.
-     * That radius of curvature is:
-     *
-     * <blockquote>ν = 1 / √(1 - ℯ²⋅sin²φ)</blockquote>
-     *
-     * This method returns 1/ν.
-     *
-     * <div class="section">Relationship with Snyder</div>
-     * This is related to functions (14-15) from Snyder (used for computation of scale factors
-     * at the true scale latitude) as below:
-     *
-     * <blockquote>m = cosφ / rν</blockquote>
-     *
-     * Special cases:
-     * <ul>
-     *   <li>If φ is 0°, then <var>m</var> is 1.</li>
-     *   <li>If φ is ±90°, then <var>m</var> is 0 provided that we are not in the spherical case
-     *       (otherwise we get {@link Double#NaN}).</li>
-     * </ul>
-     *
-     * @param  sinφ The sine of the φ latitude in radians.
-     * @return Reciprocal of the radius of curvature of the ellipsoid perpendicular to the meridian at latitude φ.
-     */
-    final double rν(final double sinφ) {
-        return sqrt(1 - excentricitySquared * (sinφ*sinφ));
+        return true;
     }
 }

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import java.util.Map;
 import java.util.EnumMap;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterDescriptor;
@@ -34,8 +33,10 @@ import org.apache.sis.parameter.Paramete
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.Workaround;
 import org.apache.sis.measure.Latitude;
+import org.apache.sis.math.MathFunctions;
 
 import static java.lang.Math.*;
+import static org.apache.sis.internal.util.DoubleDouble.verbatim;
 
 
 /**
@@ -53,7 +54,7 @@ import static java.lang.Math.*;
  * @see EquatorialStereographic
  * @see ObliqueStereographic
  */
-public class PolarStereographic extends ConformalProjection {  // Seen as a special case of LambertConformal.
+public class PolarStereographic extends ConformalProjection {
     /**
      * For cross-version compatibility.
      */
@@ -85,12 +86,30 @@ public class PolarStereographic extends
     }
 
     /**
-     * Returns the (<var>role</var> → <var>parameter</var>) associations for a Polar Stereographic projection.
+     * Creates a Polar Stereographic projection from the given parameters.
+     * 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>
+     * </ul>
      *
-     * @param  variant One of {@link #A}, {@link #B}, {@link #C}, {@link #NORTH} or {@link #SOUTH} constants.
-     * @return The roles map to give to super-class constructor.
+     * @param method     Description of the projection parameters.
+     * @param parameters The parameter values of the projection to create.
      */
-    private static Map<ParameterRole, ParameterDescriptor<Double>> roles(final byte variant) {
+    public PolarStereographic(final OperationMethod method, final Parameters parameters) {
+        this(initializer(method, parameters));
+    }
+
+    /**
+     * Work around for RFE #4093999 in Sun's bug database
+     * ("Relax constraint on placement of this()/super() call in constructors").
+     */
+    @SuppressWarnings("fallthrough")
+    @Workaround(library="JDK", version="1.7")
+    private static Initializer initializer(final OperationMethod method, final Parameters parameters) {
+        final byte variant = getVariant(method);
         final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class);
         ParameterDescriptor<Double> falseEasting  = PolarStereographicA.FALSE_EASTING;
         ParameterDescriptor<Double> falseNorthing = PolarStereographicA.FALSE_NORTHING;
@@ -104,24 +123,7 @@ public class PolarStereographic extends
         roles.put(ParameterRole.CENTRAL_MERIDIAN, (variant == A)
                 ? PolarStereographicA.LONGITUDE_OF_ORIGIN
                 : PolarStereographicB.LONGITUDE_OF_ORIGIN);
-        return roles;
-    }
-
-    /**
-     * Creates a Polar Stereographic projection from the given parameters.
-     * 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>
-     * </ul>
-     *
-     * @param method     Description of the projection parameters.
-     * @param parameters The parameter values of the projection to create.
-     */
-    public PolarStereographic(final OperationMethod method, final Parameters parameters) {
-        this(method, parameters, getVariant(method));
+        return new Initializer(method, parameters, roles, variant);
     }
 
     /**
@@ -129,8 +131,9 @@ public class PolarStereographic extends
      * ("Relax constraint on placement of this()/super() call in constructors").
      */
     @Workaround(library="JDK", version="1.7")
-    private PolarStereographic(final OperationMethod method, final Parameters parameters, final byte variant) {
-        super(method, parameters, roles(variant));
+    private PolarStereographic(final Initializer initializer) {
+        super(initializer);
+        final byte variant = initializer.variant;
         /*
          * "Standard parallel" and "Latitude of origin" should be mutually exclusive,
          * but this is not a strict requirement for the constructor.
@@ -148,9 +151,9 @@ public class PolarStereographic extends
          */
         double φ0;
         if (variant == A) {
-            φ0 = getAndStore(parameters, PolarStereographicA.LATITUDE_OF_ORIGIN);   // Mandatory
+            φ0 = initializer.getAndStore(PolarStereographicA.LATITUDE_OF_ORIGIN);   // Mandatory
         } else {
-            φ0 = getAndStore(parameters, PolarStereographicA.LATITUDE_OF_ORIGIN,    // Optional (should not be present)
+            φ0 = initializer.getAndStore(PolarStereographicA.LATITUDE_OF_ORIGIN,    // Optional (should not be present)
                     (variant == NORTH) ? Latitude.MAX_VALUE :
                     (variant == SOUTH) ? Latitude.MIN_VALUE : Double.NaN);
         }
@@ -160,9 +163,9 @@ public class PolarStereographic extends
         }
         double φ1;
         if (variant == B || variant == C || Double.isNaN(φ0)) {
-            φ1 = getAndStore(parameters, PolarStereographicB.STANDARD_PARALLEL);        // Mandatory
+            φ1 = initializer.getAndStore(PolarStereographicB.STANDARD_PARALLEL);        // Mandatory
         } else {
-            φ1 = getAndStore(parameters, PolarStereographicB.STANDARD_PARALLEL, φ0);    // Optional
+            φ1 = initializer.getAndStore(PolarStereographicB.STANDARD_PARALLEL, φ0);    // Optional
         }
         /*
          * At this point we should ensure that the sign of φ0 is the same than the sign of φ1,
@@ -171,10 +174,18 @@ public class PolarStereographic extends
          * It may be possible to specify φ0 and φ1 if the caller used his own parameter descriptor,
          * in which case maybe he really wanted different sign (e.g. for testing purpose).
          */
-        final boolean isNorthPole = (φ1 >= 0);
-        φ1 = -abs(toRadians(φ1));  // May be anything in [-π/2 … 0] range.
-        final double ρ;
-        Double ρF = null;    // Actually -ρF (compared to EPSG guide).
+        final boolean isNorth = MathFunctions.isPositive(φ1);
+        if (isNorth) {
+            /*
+             * The South case has the most "natural" formulas. For the North case, we use the same formulas
+             * with only the sign reversed before and after projection. This sign reversal is done in the
+             * (de)normalization matrices at the end of this method. But we need to apply the same politic
+             * on the parameters that we will use below.
+             */
+            φ1 = -φ1;
+        }
+        φ1 = toRadians(φ1);  // May be anything in [-π/2 … 0] range.
+        final Number ρ, ρF;  // This ρF is actually -ρF in EPSG guide.
         if (abs(φ1 + PI/2) < ANGULAR_TOLERANCE) {
             /*
              * Polar Stereographic (variant A)
@@ -188,7 +199,8 @@ public class PolarStereographic extends
              *
              * In the spherical case, should give ρ == 2.
              */
-            ρ = 2 / sqrt(pow(1+excentricity, 1+excentricity) * pow(1-excentricity, 1-excentricity));
+            ρ = verbatim(2 / sqrt(pow(1+excentricity, 1+excentricity) * pow(1-excentricity, 1-excentricity)));
+            ρF = null;
         } else {
             /*
              * Polar Stereographic (variant B or C)
@@ -208,11 +220,9 @@ public class PolarStereographic extends
              * In the spherical case, should give ρ = 1 + sinφ1   (Synder 21-7 and 21-11).
              */
             final double sinφ1 = sin(φ1);
-            final double mF = cos(φ1) / rν(sinφ1);
-            ρ = mF / expOfNorthing(φ1, excentricity*sinφ1);
-            if (variant == C) {
-                ρF = -mF;
-            }
+            final double mF = initializer.scaleAtφ(sinφ1, cos(φ1));
+            ρ = verbatim(mF / expOfNorthing(φ1, excentricity*sinφ1));
+            ρF = (variant == C) ? verbatim(-mF) : null;
         }
         /*
          * At this point, all parameters have been processed. Now process to their
@@ -221,9 +231,11 @@ public class PolarStereographic extends
         final MatrixSIS denormalize = context.getMatrix(false);
         denormalize.convertBefore(0, ρ, null);
         denormalize.convertBefore(1, ρ, ρF);
-        if (isNorthPole) {
-            context.getMatrix(true).convertAfter(1, -1, null);
-            denormalize.convertBefore(1, -1, null);
+        if (isNorth) {
+            final Number reverseSign = verbatim(-1);
+            final MatrixSIS normalize = context.getMatrix(true);
+            normalize  .convertAfter (1, reverseSign, null);
+            denormalize.convertBefore(1, reverseSign, null);
         }
     }
 
@@ -269,7 +281,7 @@ public class PolarStereographic extends
                             final boolean derivate) throws ProjectionException
     {
         /*
-         * Note: formulas below are very similar to LambertConformal.transform(…) with n = -1.
+         * Note: formulas below are very similar to LambertConicConformal.transform(…) with n = -1.
          */
         final double θ    = srcPts[srcOff  ];   // θ = λ - λ₀
         final double φ    = srcPts[srcOff+1];   // Sign may be reversed

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -16,17 +16,16 @@
  */
 package org.apache.sis.referencing.operation.projection;
 
-import java.util.Map;
 import java.util.EnumMap;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.OperationMethod;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
-import org.apache.sis.internal.referencing.provider.MapProjection;
 import org.apache.sis.internal.referencing.provider.TransverseMercatorSouth;
 import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.parameter.Parameters;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.Workaround;
 
 import static java.lang.Math.*;
 import static org.apache.sis.math.MathFunctions.asinh;
@@ -72,11 +71,29 @@ public class TransverseMercator extends
     private final double h1, h2, h3, h4, ih1, ih2, ih3, ih4;
 
     /**
-     * Returns the (<var>role</var> → <var>parameter</var>) associations for a Transverse Mercator projection.
+     * Creates a Transverse Mercator projection from the given parameters.
+     * The {@code method} argument can be the description of one of the following:
      *
-     * @return The roles map to give to super-class constructor.
+     * <ul>
+     *   <li><cite>"Transverse Mercator"</cite>.</li>
+     *   <li><cite>"Transverse Mercator (South Orientated)"</cite>.</li>
+     * </ul>
+     *
+     * @param method     Description of the projection parameters.
+     * @param parameters The parameter values of the projection to create.
      */
-    private static Map<ParameterRole, ParameterDescriptor<Double>> roles(final boolean isSouth) {
+    public TransverseMercator(final OperationMethod method, final Parameters parameters) {
+        this(initializer(method, parameters));
+    }
+
+    /**
+     * Work around for RFE #4093999 in Sun's bug database
+     * ("Relax constraint on placement of this()/super() call in constructors").
+     */
+    @SuppressWarnings("fallthrough")
+    @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 EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class);
         ParameterRole xOffset = ParameterRole.FALSE_EASTING;
         ParameterRole yOffset = ParameterRole.FALSE_NORTHING;
@@ -88,47 +105,45 @@ public class TransverseMercator extends
         roles.put(ParameterRole.SCALE_FACTOR,     org.apache.sis.internal.referencing.provider.TransverseMercator.SCALE_FACTOR);
         roles.put(xOffset,                        org.apache.sis.internal.referencing.provider.TransverseMercator.FALSE_EASTING);
         roles.put(yOffset,                        org.apache.sis.internal.referencing.provider.TransverseMercator.FALSE_NORTHING);
-        return roles;
+        return new Initializer(method, parameters, roles, isSouth ? (byte) 1 : (byte) 0);
     }
 
     /**
-     * Returns the type of the projection based on the name and identifier of the given operation method.
+     * Work around for RFE #4093999 in Sun's bug database
+     * ("Relax constraint on placement of this()/super() call in constructors").
      */
-    private static boolean isSouth(final OperationMethod method) {
-        return identMatch(method, "(?i).*\\bSouth\\b.*", TransverseMercatorSouth.IDENTIFIER);
-    }
-
-    /**
-     * Creates a Transverse Mercator projection from the given parameters.
-     * The {@code method} argument can be the description of one of the following:
-     *
-     * <ul>
-     *   <li><cite>"Transverse Mercator"</cite>.</li>
-     *   <li><cite>"Transverse Mercator (South Orientated)"</cite>.</li>
-     * </ul>
-     *
-     * @param method     Description of the projection parameters.
-     * @param parameters The parameter values of the projection to create.
-     */
-    public TransverseMercator(final OperationMethod method, final Parameters parameters) {
-        super(method, parameters, roles(isSouth(method)));
-        final double φ0 = toRadians(getAndStore(parameters,
+    @Workaround(library="JDK", version="1.7")
+    private TransverseMercator(final Initializer initializer) {
+        super(initializer);
+        final double φ0 = toRadians(initializer.getAndStore(
                 org.apache.sis.internal.referencing.provider.TransverseMercator.LATITUDE_OF_ORIGIN));
-        final double rs = parameters.doubleValue(MapProjection.SEMI_MINOR)
-                        / parameters.doubleValue(MapProjection.SEMI_MAJOR);
-
-        final double n  = (1 - rs) / (1 + rs);       // Rewrite of n = f / (2-f)
+        /*
+         * Opportunistically use double-double arithmetic for computation of B since we will store
+         * it in the denormalization matrix, and there is no sine/cosine functions involved here.
+         */
+        final double n;
+        final DoubleDouble B;
+        {   // For keeping the 't' variable locale.
+            /*
+             * EPSG gives:      n  =  f / (2-f)
+             * We rewrite as:   n  =  (1 - b/a) / (1 + b/a)
+             */
+            final DoubleDouble t = initializer.axisLengthRatio();   // t  =  b/a
+            t.ratio_1m_1p();                                        // t  =  (1 - t) / (1 + t)
+            n = t.doubleValue();
+            /*
+             * Compute B  =  (1 + n²/4 + n⁴/64) / (1 + n)
+             */
+            B = new DoubleDouble(t);        // B  =  n
+            B.square();
+            B.series(1, 0.25, 1./64);       // B  =  (1 + n²/4 + n⁴/64)
+            t.add(1,0);
+            B.divide(t);                    // B  =  (1 + n²/4 + n⁴/64) / (1 + n)
+        }
         final double n2 = n  * n;
         final double n3 = n2 * n;
         final double n4 = n2 * n2;
         /*
-         * Compute B  =  (n4/64 + n2/4 + 1) / (n + 1)
-         * Opportunistically uses double-double arithmetic since we use it anyway for denormalization matrix.
-         */
-        final DoubleDouble B = new DoubleDouble(n);
-        B.add(1);
-        B.inverseDivide(1, n4/64 + n2/4);
-        /*
          * Coefficients for direct projection.
          * Add the smallest values first in order to reduce rounding errors.
          */
@@ -146,21 +161,22 @@ public class TransverseMercator extends
         ih4 = (4397. / 161280)*n4;
         /*
          * Compute M₀ = B⋅(ξ₁ + ξ₂ + ξ₃ + ξ₄) and negate in anticipation for what will be needed
-         * in the denormalization matrix. We opportunistically use double-double arithmetic, but
-         * the precision is actually not better than double (in current SIS version) because of
-         * the precision of trigonometric functions. We may improve on that in the future if it
-         * seems useful.
+         * in the denormalization matrix. We opportunistically use double-double arithmetic but
+         * only for the final multiplication by B, for consistency with the translation term to
+         * be stored in the denormalization matrix. It is not worth to use double-double in the
+         * sum of sine functions because the extra digits would be meaningless.
          *
          * NOTE: the EPSG documentation makes special cases for φ₀ = 0 or ±π/2. This is not
          * needed here; we verified that the code below produces naturally the expected values.
          */
         final double Q = asinh(tan(φ0)) - excentricity * atanh(excentricity * sin(φ0));
         final double β = atan(sinh(Q));
-        final DoubleDouble M0 = new DoubleDouble(β, 0);
-        M0.add(h1 * sin(2*β), 0);
-        M0.add(h2 * sin(4*β), 0);
-        M0.add(h3 * sin(6*β), 0);
-        M0.add(h4 * sin(8*β), 0);
+        final DoubleDouble M0 = new DoubleDouble();
+        M0.value = h4 * sin(8*β)
+                 + h3 * sin(6*β)
+                 + h2 * sin(4*β)
+                 + h1 * sin(2*β)
+                 + β;
         M0.multiply(B);
         M0.negate();
         /*

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -64,6 +64,7 @@ abstract class AbstractLinearTransform e
      * Returns a copy of the matrix that user can modify.
      */
     @Override
+    @SuppressWarnings("CloneDoesntCallSuperClone")
     public final Matrix clone() {
         return Matrices.copy(this);
     }
@@ -151,10 +152,8 @@ abstract class AbstractLinearTransform e
             return true;
         }
         if (object != null) {
-            if (getClass() == object.getClass()) {
-                if (mode.ordinal() < ComparisonMode.APPROXIMATIVE.ordinal()) {
-                    return equalsSameClass(object);
-                }
+            if (getClass() == object.getClass() && !mode.isApproximative()) {
+                return equalsSameClass(object);
             }
             if (mode != ComparisonMode.STRICT) {
                 if (object instanceof LinearTransform) {

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -874,14 +874,13 @@ public abstract class AbstractMathTransf
      * <p>The default implementation returns {@code true} if the following conditions are meet:</p>
      * <ul>
      *   <li>{@code object} is an instance of the same class than {@code this}. We require the
-     *        same class because there is no interface for the various kinds of transform.</li>
-     *   <li>The {@linkplain #getParameterDescriptors() parameter descriptors} are equal according
+     *       same class because there is no interface for the various kinds of transform.</li>
+     *   <li>If the hash code value has already been {@linkplain #computeHashCode() computed} for both
+     *       instances, their values are the same <i>(opportunist performance enhancement)</i>.</li>
+     *   <li>The {@linkplain #getContextualParameters() contextual parameters} are equal according
      *       the given comparison mode.</li>
      * </ul>
      *
-     * The {@linkplain #getParameterValues() parameter values} are <strong>not</strong> compared because
-     * subclasses can typically compare those values more efficiently by accessing to their member fields.
-     *
      * @param  object The object to compare with this transform.
      * @param  mode The strictness level of the comparison. Default to {@link ComparisonMode#STRICT STRICT}.
      * @return {@code true} if the given object is considered equals to this math transform.
@@ -896,7 +895,7 @@ public abstract class AbstractMathTransf
              * If the classes are the same, then the hash codes should be computed in the same way. Since those
              * codes are cached, this is an efficient way to quickly check if the two objects are different.
              */
-            if (mode.ordinal() < ComparisonMode.APPROXIMATIVE.ordinal()) {
+            if (!mode.isApproximative()) {
                 final int tc = hashCode;
                 if (tc != 0) {
                     final int oc = that.hashCode;
@@ -906,11 +905,11 @@ public abstract class AbstractMathTransf
                 }
             }
             // See the policy documented in the LenientComparable javadoc.
-            if (mode.ordinal() >= ComparisonMode.IGNORE_METADATA.ordinal()) {
+            if (mode.isIgnoringMetadata()) {
                 return true;
             }
-            return Utilities.deepEquals(this.getParameterDescriptors(),
-                                        that.getParameterDescriptors(), mode);
+            return Utilities.deepEquals(this.getContextualParameters(),
+                                        that.getContextualParameters(), mode);
         }
         return false;
     }

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -428,6 +428,35 @@ class ConcatenatedTransform extends Abst
                 i = ((AbstractMathTransform) step).beforeFormat(transforms, i, false);
             }
         }
+        /*
+         * Merge consecutive affine transforms. The transforms list should never contain consecutive instances
+         * of LinearTransform because the ConcatenatedTransform.create(…) method already merged them  (this is
+         * verified by assertions in MathTransforms). However the above loop may have created synthetic affine
+         * transforms for WKT formatting purpose. Those synthetic affine transforms are actually represented by
+         * Matrix objects (rather than full MathTransform objects), and two matrices may have been generated
+         * consecutively.
+         */
+        Matrix after = null;
+        for (int i=transforms.size(); --i >= 0;) {
+            final Object step = transforms.get(i);
+            if (step instanceof Matrix) {
+                if (after != null) {
+                    final Matrix merged = Matrices.multiply(after, (Matrix) step);
+                    if (merged.isIdentity()) {
+                        transforms.subList(i, i+2).clear();
+                        after = null;
+                    } else {
+                        transforms.set(i, MathTransforms.linear(merged));
+                        transforms.remove(i+1);
+                        after = merged;
+                    }
+                } else {
+                    after = (Matrix) step;
+                }
+            } else {
+                after = null;
+            }
+        }
         return transforms;
     }
 

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -37,6 +37,7 @@ import org.opengis.referencing.operation
 import org.opengis.referencing.operation.MathTransformFactory;
 import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
 import org.apache.sis.internal.referencing.WKTUtilities;
+import org.apache.sis.internal.referencing.Formulas;
 import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.util.DoubleDouble;
@@ -562,7 +563,7 @@ public class ContextualParameters extend
      */
     @Override
     public int hashCode() {
-        return (normalize.hashCode() + 31*denormalize.hashCode()) ^ (int) serialVersionUID;
+        return (normalize.hashCode() + 31*denormalize.hashCode()) ^ Arrays.hashCode(values) ^ (int) serialVersionUID;
     }
 
     /**
@@ -575,9 +576,10 @@ public class ContextualParameters extend
     public boolean equals(final Object object) {
         if (object != null && object.getClass() == getClass()) {
             final ContextualParameters that = (ContextualParameters) object;
-            return Objects.equals(descriptor,  that.descriptor) &&
-                   Objects.equals(normalize,   that.normalize)  &&
-                   Objects.equals(denormalize, that.denormalize);
+            return Objects.equals(descriptor,  that.descriptor)  &&
+                   Objects.equals(normalize,   that.normalize)   &&
+                   Objects.equals(denormalize, that.denormalize) &&
+                    Arrays.equals(values,      that.values);
         }
         return false;
     }
@@ -702,11 +704,31 @@ public class ContextualParameters extend
          * At this point "userDefined" is the affine transform to show to user instead of the
          * "before" affine transform. Replaces "before" by "userDefined" locally (but not yet
          * in the list), or set it to null (meaning that it will be removed from the list) if
-         * it is identity, which happen quite often. Note that in the former (non-null) case,
-         * the coefficients are often either 0 or 1 since the transform is often for changing
-         * axis order, so it is worth to attempt rounding coefficents.
+         * it is identity, which happen quite often.
+         *
+         * Note on rounding error: the coefficients are often either 0 or 1 since the transform
+         * is often for changing axis order. Thanks to double-double arithmetic in SIS matrices,
+         * the non-zero values are usually accurate. But the values that should be zero are much
+         * harder to get right. Sometime we see small values (around 1E-12) in the last column of
+         * the 'before' matrix below. Since this column contains translation terms, those numbers
+         * are in the unit of measurement of input values of the MathTransform after the matrix.
+         *
+         *   - For forward map projections, those values are conceptually in decimal degrees
+         *     (in fact the values are converted to radians but not by this 'before' matrix).
+         *
+         *   - For inverse map projections, those values are conceptually in metres (in fact
+         *     converted to distances on a unitary ellipsoid but not by this 'before' matrix).
+         *
+         *   - Geographic/Geocentric transformations behave like map projections in regard to units.
+         *     Molodensky transformations conceptually use always decimal degrees. There is not much
+         *     other cases since this mechanism is internal to SIS (not in public API).
+         *
+         * Consequently we set the tolerance threshold to ANGULAR_TOLERANCE. We do not bother (at least
+         * for now) to identify the cases where we could use LINEAR_TOLERANCE because just checking the
+         * 'inverse' flag is not sufficient (e.g. the Molodensky case). Since the angular tolerance is
+         * smaller than the linear one, unconditional usage of ANGULAR_TOLERANCE is more conservative.
          */
-        before = userDefined.isIdentity() ? null : userDefined;
+        before = Matrices.isIdentity(userDefined, Formulas.ANGULAR_TOLERANCE) ? null : userDefined;
         /*
          * Compute the "after" affine transform in a way similar than the "before" affine.
          * Note that if this operation fails, we will cancel everything we would have done
@@ -722,7 +744,23 @@ public class ContextualParameters extend
         if (hasAfter) {
             userDefined = Matrices.multiply(after, userDefined);
         }
-        after = userDefined.isIdentity() ? null : userDefined;
+        /*
+         * Note on rounding error: same discussion than the "note on rounding error" of the 'before' matrix,
+         * with the following differences:
+         *
+         *   - For forward map projections, unit of measurements of translation terms are conceptually
+         *     metres (instead than degrees) multiplied by the scale factors in the 'after' matrix.
+         *
+         *   - For inverse map projections, unit of measurements of translation terms are conceptually
+         *     degrees (instead than metres) multiplied by the scale factors in the 'after' matrix.
+         *
+         *   - And so on for all cases: swap the units of the forward and inverse cases, then multiply
+         *     by the scale factor. Note that the multiplication step does not exist in the 'before' case.
+         *
+         * Since we are seeking for the identity matrix, the scale factor is 1. We do not bother to distinguish
+         * the ANGULAR_TOLERANCE and LINEAR_TOLERANCE cases for the same reasons than for the 'before' matrix.
+         */
+        after = Matrices.isIdentity(userDefined, Formulas.ANGULAR_TOLERANCE) ? null : userDefined;
         /*
          * At this point we have computed all the affine transforms to show to the user.
          * We can replace the elements in the list. The transform referenced by transforms.get(index)

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -36,6 +36,7 @@ import org.opengis.parameter.ParameterVa
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.parameter.ParameterNotFoundException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.datum.Ellipsoid;
@@ -538,12 +539,34 @@ public class DefaultMathTransformFactory
                  */
                 failure = e;
             }
+            final boolean isIvfDefinitive;
             if (mismatchedParam != null) {
                 final LogRecord record = Messages.getResources((Locale) null).getLogRecord(Level.WARNING,
                         Messages.Keys.MismatchedEllipsoidAxisLength_3, ellipsoid.getName().getCode(),
                         mismatchedParam.getDescriptor().getName().getCode(), mismatchedValue);
                 record.setLoggerName(Loggers.COORDINATE_OPERATION);
                 Logging.log(DefaultMathTransformFactory.class, "createBaseToDerived", record);
+                isIvfDefinitive = false;
+            } else {
+                isIvfDefinitive = ellipsoid.isIvfDefinitive();
+            }
+            /*
+             * Following is specific to Apache SIS. We use this non-standard API for allowing the
+             * NormalizedProjection class (our base class for all map projection implementations)
+             * to known that the ellipsoid definitive parameter is the inverse flattening factor
+             * instead than the semi-major axis length. It makes a small difference in the accuracy
+             * of the excentricity parameter.
+             */
+            if (isIvfDefinitive) try {
+                parameters.parameter(Constants.INVERSE_FLATTENING).setValue(ellipsoid.getInverseFlattening());
+            } catch (ParameterNotFoundException e) {
+                /*
+                 * Should never happen with Apache SIS implementation, but may happen if the given parameters come
+                 * from another implementation. We can safely abandon our attempt to set the inverse flattening value,
+                 * since it was redundant with semi-minor axis length.
+                 */
+                Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
+                        DefaultMathTransformFactory.class, "createBaseToDerived", e);
             }
         }
         MathTransform baseToDerived;

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -78,4 +78,25 @@ public final strictfp class FormulasTest
     public void testGetAuthalicRadius() {
         assertEquals(ReferencingServices.AUTHALIC_RADIUS, Formulas.getAuthalicRadius(6378137, 6356752), 0.5);
     }
+
+    /**
+     * Tests {@link Formulas#getSemiMinor(double, double)}.
+     */
+    @Test
+    public void testGetSemiMinor() {
+        assertEquals("WGS 84",             6356752.314245179,  Formulas.getSemiMinor(6378137, 298.257223563), 1E-9);
+        assertEquals("International 1924", 6356911.9461279465, Formulas.getSemiMinor(6378388, 297), 1E-9);
+        assertEquals("Clarke 1858",        20855233, // Unit in feet. Is the definitive parameter for this ellipsoid.
+                Formulas.getSemiMinor(20926348, 294.26067636926103), 1E-8);
+    }
+
+    /**
+     * Tests {@link Formulas#getInverseFlattening(double, double)}.
+     */
+    @Test
+    public void testGetInverseFlattening() {
+        assertEquals("WGS 84", 298.2572235629972, Formulas.getInverseFlattening(6378137, 6356752.314245179), 1E-11);
+        assertEquals("International 1924", 297, Formulas.getInverseFlattening(6378388, 6356911.9461279465), 1E-11);
+        assertEquals("Clarke 1858", 294.26067636926103, Formulas.getInverseFlattening(20926348, 20855233), 1E-11);
+    }
 }

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -22,7 +22,6 @@ import org.opengis.referencing.cs.*;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.datum.PrimeMeridian;
-import org.apache.sis.referencing.datum.DefaultPrimeMeridian;
 import org.apache.sis.referencing.datum.HardCodedDatum;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.internal.metadata.WKTKeywords;
@@ -31,8 +30,6 @@ import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
-import static java.util.Collections.singletonMap;
-import static org.opengis.referencing.IdentifiedObject.NAME_KEY;
 import static org.apache.sis.internal.referencing.ReferencingUtilities.*;
 
 
@@ -46,26 +43,6 @@ import static org.apache.sis.internal.re
  */
 public final strictfp class ReferencingUtilitiesTest extends TestCase {
     /**
-     * Tests {@link ReferencingUtilities#isGreenwichLongitudeEquals(PrimeMeridian, PrimeMeridian)}.
-     */
-    @Test
-    public void testIsGreenwichLongitudeEquals() {
-        assertFalse(isGreenwichLongitudeEquals(null, null)); // "null" interpreted as "unknown".
-        assertFalse(isGreenwichLongitudeEquals(null, HardCodedDatum.GREENWICH));
-        assertFalse(isGreenwichLongitudeEquals(HardCodedDatum.GREENWICH, null));
-        assertFalse(isGreenwichLongitudeEquals(HardCodedDatum.GREENWICH, HardCodedDatum.PARIS));
-        assertFalse(isGreenwichLongitudeEquals(HardCodedDatum.PARIS, HardCodedDatum.PARIS_RGS));
-        assertFalse(isGreenwichLongitudeEquals(HardCodedDatum.PARIS_RGS, HardCodedDatum.PARIS));
-        assertTrue (isGreenwichLongitudeEquals(HardCodedDatum.PARIS, HardCodedDatum.PARIS));
-        /*
-         * Test two prime meridians using different units (Paris in grade and Paris in degrees).
-         */
-        final PrimeMeridian pd = new DefaultPrimeMeridian(singletonMap(NAME_KEY, "Paris"), 2.33722917, NonSI.DEGREE_ANGLE);
-        assertTrue(isGreenwichLongitudeEquals(HardCodedDatum.PARIS, pd));
-        assertTrue(isGreenwichLongitudeEquals(pd, HardCodedDatum.PARIS));
-    }
-
-    /**
      * Tests {@link ReferencingUtilities#getGreenwichLongitude(PrimeMeridian, Unit)}.
      */
     @Test

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -28,12 +28,13 @@ import org.junit.Test;
 import static org.junit.Assert.*;
 import static org.apache.sis.internal.util.Constants.SEMI_MAJOR;
 import static org.apache.sis.internal.util.Constants.SEMI_MINOR;
+import static org.apache.sis.internal.util.Constants.EARTH_RADIUS;
+import static org.apache.sis.internal.util.Constants.INVERSE_FLATTENING;
+import static org.apache.sis.internal.util.Constants.IS_IVF_DEFINITIVE;
 import static org.apache.sis.internal.util.Constants.CENTRAL_MERIDIAN;
+import static org.apache.sis.internal.util.Constants.STANDARD_PARALLEL;
 import static org.apache.sis.internal.util.Constants.STANDARD_PARALLEL_1;
 import static org.apache.sis.internal.util.Constants.STANDARD_PARALLEL_2;
-import static org.apache.sis.parameter.MapProjectionDescriptor.EARTH_RADIUS;
-import static org.apache.sis.parameter.MapProjectionDescriptor.INVERSE_FLATTENING;
-import static org.apache.sis.parameter.MapProjectionDescriptor.STANDARD_PARALLEL;
 
 
 /**
@@ -103,17 +104,24 @@ public final strictfp class MapProjectio
         final MapProjectionDescriptor descriptor = createDescriptor(0);
         final ParameterValueGroup parameters = descriptor.createValue();
 
-        parameters.parameter(SEMI_MAJOR).setValue(6378206.4); // Clarke 1866
+        parameters.parameter(SEMI_MAJOR).setValue(6378206.4);  // Clarke 1866
         parameters.parameter(SEMI_MINOR).setValue(6356583.8);
         assertEquals(294.97870, parameters.parameter(INVERSE_FLATTENING).doubleValue(), 0.00001);
         assertEquals(6378206.4, parameters.parameter(SEMI_MAJOR)        .doubleValue(), 0.5);
         assertEquals(6356583.8, parameters.parameter(SEMI_MINOR)        .doubleValue(), 0.5);
+        assertFalse("isIvfDefinitive", parameters.parameter(IS_IVF_DEFINITIVE).booleanValue());
 
-        parameters.parameter(SEMI_MAJOR).setValue(6378137.000); // WGS84
+        parameters.parameter(SEMI_MAJOR).setValue(6378137.0);  // WGS84
         parameters.parameter(INVERSE_FLATTENING).setValue(298.257223563);
         assertEquals(298.257, parameters.parameter(INVERSE_FLATTENING).doubleValue(), 0.001);
         assertEquals(6378137, parameters.parameter(SEMI_MAJOR)        .doubleValue(), 0.5);
         assertEquals(6356752, parameters.parameter(SEMI_MINOR)        .doubleValue(), 0.5);
+        assertTrue("isIvfDefinitive", parameters.parameter(IS_IVF_DEFINITIVE).booleanValue());
+
+        parameters.parameter(SEMI_MAJOR).setValue(6378350.9);  // Clarke 1858 (approximative)
+        parameters.parameter(SEMI_MINOR).setValue(6356675.0);
+        assertEquals(294.26, parameters.parameter(INVERSE_FLATTENING).doubleValue(), 0.001);
+        assertFalse("isIvfDefinitive", parameters.parameter(IS_IVF_DEFINITIVE).booleanValue());
     }
 
     /**

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderMock.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderMock.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderMock.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderMock.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -32,6 +32,19 @@ import org.opengis.util.GenericName;
  */
 final strictfp class BuilderMock extends Builder<BuilderMock> {
     /**
+     * Creates a new builder.
+     */
+    BuilderMock() {
+    }
+
+    /**
+     * Creates a new builder initialized to the given object.
+     */
+    BuilderMock(final IdentifiedObject object) {
+        super(object);
+    }
+
+    /**
      * Convenience accessor for the property value assigned to {@link IdentifiedObject#NAME_KEY}.
      */
     Object getName() {

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java?rev=1693908&r1=1693907&r2=1693908&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java [UTF-8] Mon Aug  3 14:25:01 2015
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing;
 
 import java.util.Map;
+import java.util.HashMap;
 import org.opengis.util.NameSpace;
 import org.opengis.util.LocalName;
 import org.opengis.util.GenericName;
@@ -24,6 +25,7 @@ import org.opengis.util.NameFactory;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.Identifier;
 import org.apache.sis.internal.simple.SimpleCitation;
+import org.apache.sis.internal.simple.SimpleIdentifier;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.metadata.iso.ImmutableIdentifier;
 import org.apache.sis.metadata.iso.citation.Citations;
@@ -49,6 +51,7 @@ public final strictfp class BuilderTest
      * Tests {@link Builder#verifyParameterizedType(Class)}.
      */
     @Test
+    @SuppressWarnings("ResultOfObjectAllocationIgnored")
     public void testVerifyParameterizedType() {
         final class Invalid extends Builder<BuilderMock> {
         }
@@ -281,4 +284,36 @@ public final strictfp class BuilderTest
             "GeoTIFF:CT_Mercator"
         }, builder.getAsStrings(1));
     }
+
+    /**
+     * Tests the {@link Builder#Builder(IdentifiedObject)} constructor.
+     *
+     * @since 0.6
+     */
+    @Test
+    public void testCreationFromObject() {
+        final Map<String,Object> properties = new HashMap<>();
+        final Identifier id = new SimpleIdentifier(null, "An identifier", false);
+        assertNull(properties.put(AbstractIdentifiedObject.IDENTIFIERS_KEY, id));
+        assertNull(properties.put(AbstractIdentifiedObject.ALIAS_KEY,       "An alias"));
+        assertNull(properties.put(AbstractIdentifiedObject.NAME_KEY,        "Dummy object"));
+        assertNull(properties.put(AbstractIdentifiedObject.REMARKS_KEY,     "Some remarks"));
+        final BuilderMock builder = new BuilderMock(new AbstractIdentifiedObject(properties));
+
+        assertEquals("Expected only name and remarks.", 2, builder.properties.size());
+        builder.onCreate(false);
+        assertEquals("Expected name, aliases, identifiers and remarks.", 4, builder.properties.size());
+
+        assertEquals(AbstractIdentifiedObject.NAME_KEY, "Dummy object",
+                builder.properties.get(AbstractIdentifiedObject.NAME_KEY).toString());
+
+        assertEquals(AbstractIdentifiedObject.REMARKS_KEY, "Some remarks",
+                builder.properties.get(AbstractIdentifiedObject.REMARKS_KEY).toString());
+
+        assertEquals(AbstractIdentifiedObject.ALIAS_KEY, "An alias",
+                ((Object[]) builder.properties.get(AbstractIdentifiedObject.ALIAS_KEY))[0].toString());
+
+        assertSame(AbstractIdentifiedObject.IDENTIFIERS_KEY, id,
+                ((Object[]) builder.properties.get(AbstractIdentifiedObject.IDENTIFIERS_KEY))[0]);
+    }
 }



Mime
View raw message