You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2015/10/06 14:14:10 UTC

svn commit: r1707026 - in /sis/branches/JDK8/core/sis-referencing/src: main/java/org/apache/sis/referencing/operation/projection/ test/java/org/apache/sis/referencing/operation/projection/

Author: desruisseaux
Date: Tue Oct  6 12:14:09 2015
New Revision: 1707026

URL: http://svn.apache.org/viewvc?rev=1707026&view=rev
Log:
Refactor TransverseMercator as a ConformalProjection subclass.

Modified:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
    sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java?rev=1707026&r1=1707025&r2=1707026&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java [UTF-8] Tue Oct  6 12:14:09 2015
@@ -36,11 +36,16 @@ import static java.lang.Math.*;
  * (Source: §1.3.3 in IOGP Publication 373-7-2 – Geomatics Guidance Note number 7, part 2 – April 2015).
  * Indeed, those two projections have some equation in commons which are provided in this base class.</p>
  *
- * <p>The polar stereographic projection is not documented as a special case of Lambert Conic Conformal,
+ * <p>The Polar Stereographic projection is not documented as a special case of Lambert Conic Conformal,
  * but the equations in the {@code PolarStereographic.transform(…)} and {@code inverseTransform(…)} methods
  * 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>
  *
+ * <p>The Transverse Mercator projection is also conformal, but does not use the formulas provided in this class.
+ * It will instead compute the coefficients itself and use its own, more complex, formulas with those coefficients.
+ * However the formulas provided in this {@code ConformalProjection} class can be seen as a special case of
+ * Transverse Mercator formulas for <var>x</var> = 0.</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
@@ -59,6 +64,42 @@ abstract class ConformalProjection exten
     private static final long serialVersionUID = 458860570536642265L;
 
     /**
+     * {@code false} for using the original formulas as published by EPSG, or {@code true} for using formulas
+     * modified using trigonometric identities. The use of trigonometric identities is for reducing the amount
+     * of calls to the {@link Math#sin(double)} and similar methods. Some identities used are:
+     *
+     * <ul>
+     *   <li>sin(2θ) = 2⋅sinθ⋅cosθ</li>
+     *   <li>cos(2θ) = cos²θ - sin²θ</li>
+     *   <li>sin(3θ) = (3 - 4⋅sin²θ)⋅sinθ</li>
+     *   <li>cos(3θ) = (4⋅cos³θ) - 3⋅cosθ</li>
+     *   <li>sin(4θ) = (4 - 8⋅sin²θ)⋅sinθ⋅cosθ</li>
+     *   <li>cos(4θ) = (8⋅cos⁴θ) - (8⋅cos²θ) + 1</li>
+     * </ul>
+     *
+     * Hyperbolic formulas (used in Transverse Mercator projection):
+     *
+     * <ul>
+     *   <li>sinh(2θ) = 2⋅sinhθ⋅coshθ</li>
+     *   <li>cosh(2θ) = cosh²θ + sinh²θ   =   2⋅cosh²θ - 1   =   1 + 2⋅sinh²θ</li>
+     *   <li>sinh(3θ) = (3 + 4⋅sinh²θ)⋅sinhθ</li>
+     *   <li>cosh(3θ) = ((4⋅cosh²θ) - 3)⋅coshθ</li>
+     *   <li>sinh(4θ) = (1 + 2⋅sinh²θ)⋅4.sinhθ⋅coshθ
+     *                = 4.cosh(2θ).sinhθ⋅coshθ</li>
+     *   <li>cosh(4θ) = (8⋅cosh⁴θ) - (8⋅cosh²θ) + 1
+     *                = 8⋅cosh²(θ) ⋅ (cosh²θ - 1) + 1
+     *                = 8⋅cosh²(θ) ⋅ sinh²(θ) + 1
+     *                = 2⋅sinh²(2θ) + 1</li>
+     * </ul>
+     *
+     * Note that since this boolean is static final, the compiler should exclude the code in the branch that is never
+     * executed (no need to comment-out that code).
+     *
+     * @see #identityEquals(double, double)
+     */
+    static final boolean ALLOW_TRIGONOMETRIC_IDENTITIES = true;
+
+    /**
      * The threshold value of {@link #excentricity} at which we consider the accuracy of the
      * series expansion insufficient. This threshold is determined empirically with the help
      * of the {@code MercatorMethodComparison} class in the test directory.
@@ -72,34 +113,49 @@ abstract class ConformalProjection exten
     static final double EXCENTRICITY_THRESHOLD = 0.16;
 
     /**
-     * Coefficients in the series expansion used by {@link #φ(double)}.
+     * {@code true} if the {@link #excentricity} value is greater than or equals to {@link #EXCENTRICITY_THRESHOLD},
+     * in which case the {@link #φ(double)} method will need to use an iterative method.
      *
-     * <p>Consider those fields as final. They are not only of the purpose of {@link #readObject(ObjectInputStream)}.</p>
+     * <p><strong>Consider this field as final!</strong>
+     * It is not final only for the purpose of {@link #readObject(ObjectInputStream)}.</p>
      */
-    private transient double c2χ, c4χ, c6χ, c8χ;
+    private transient boolean useIterations;
 
     /**
-     * {@code true} if the {@link #excentricity} value is greater than or equals to {@link #EXCENTRICITY_THRESHOLD},
-     * in which case the {@link #φ(double)} method will need to use an iterative method.
+     * Coefficients in the series expansion of the inverse projection,
+     * depending only on {@linkplain #excentricity excentricity} value.
+     * The series expansion is of the following form, where f(θ) is typically sin(θ):
+     *
+     *     <blockquote>ci₂⋅f(2θ) + ci₄⋅f(4θ) + ci₆⋅f(6θ) + ci₈⋅f(8θ)</blockquote>
      *
-     * <p>Consider this field as final. It is not only of the purpose of {@link #readObject(ObjectInputStream)}.</p>
+     * This {@code ConformalProjection} class uses those coefficients in {@link #φ(double)}.
+     * However some subclasses may compute those coefficients differently and use them in a
+     * different series expansion (but for the same purpose).
+     *
+     * <p><strong>Consider those fields as final!</strong> They are not final only for sub-class
+     * constructors convenience and for the purpose of {@link #readObject(ObjectInputStream)}.</p>
+     *
+     * @see #computeCoefficients()
      */
-    private transient boolean useIterations;
+    transient double ci2, ci4, ci6, ci8;
 
     /**
      * Creates a new normalized projection from the parameters computed by the given initializer.
      *
+     * <p>It is sub-classes responsibility to invoke {@code super.computeCoefficients()} in their
+     * constructor when ready, or to compute the coefficients themselves.</p>
+     *
      * @param initializer The initializer for computing map projection internal parameters.
      */
     ConformalProjection(final Initializer initializer) {
         super(initializer);
-        initialize();
     }
 
     /**
-     * Computes the transient fields after construction or deserialization.
+     * Computes the coefficients in the series expansions from the {@link #excentricitySquared} value.
+     * This method shall be invoked after {@code ConformalProjection} construction or deserialization.
      */
-    private void initialize() {
+    void computeCoefficients() {
         useIterations = (excentricity >= EXCENTRICITY_THRESHOLD);
         final double e2 = excentricitySquared;
         final double e4 = e2 * e2;
@@ -109,14 +165,20 @@ abstract class ConformalProjection exten
          * For each line below, add the smallest values first in order to reduce rounding errors.
          * The smallest values are the one using the excentricity raised to the highest power.
          */
-        c2χ  =    13/   360.* e8  +   1/ 12.* e6  +  5/24.* e4  +  e2/2;
-        c4χ  =   811/ 11520.* e8  +  29/240.* e6  +  7/48.* e4;
-        c6χ  =    81/  1120.* e8  +   7/120.* e6;
-        c8χ  =  4279/161280.* e8;
+        ci2  =    13/   360.* e8  +   1/ 12.* e6  +  5/24.* e4  +  e2/2;
+        ci4  =   811/ 11520.* e8  +  29/240.* e6  +  7/48.* e4;
+        ci6  =    81/  1120.* e8  +   7/120.* e6;
+        ci8  =  4279/161280.* e8;
+        /*
+         * When rewriting equations using trigonometric identities, some constants appear.
+         * For example sin(2θ) = 2⋅sinθ⋅cosθ, so we can factor out the 2 constant into the
+         * corresponding 'c' field.
+         */
         if (ALLOW_TRIGONOMETRIC_IDENTITIES) {
-            c4χ *= 2;
-            c6χ *= 4;
-            c8χ *= 8;
+            // Multiplication by powers of 2 does not bring any additional rounding error.
+            ci4 *= 2;
+            ci6 *= 4;
+            ci8 *= 8;
         }
     }
 
@@ -129,10 +191,10 @@ abstract class ConformalProjection exten
     ConformalProjection(final ConformalProjection other) {
         super(other);
         useIterations = other.useIterations;
-        c2χ = other.c2χ;
-        c4χ = other.c4χ;
-        c6χ = other.c6χ;
-        c8χ = other.c8χ;
+        ci2 = other.ci2;
+        ci4 = other.ci4;
+        ci6 = other.ci6;
+        ci8 = other.ci8;
     }
 
     /**
@@ -140,6 +202,12 @@ abstract class ConformalProjection exten
      * This formula is also part of other projections, since Mercator can be considered as a special case of
      * Lambert Conic Conformal for instance.
      *
+     * <div class="note"><b>Warning:</b>
+     * this method is valid only if the series expansion coefficients where computed by the
+     * {@link #computeCoefficients()} method defined in this class. This is not the case of
+     * {@link TransverseMercator} for instance. Note however that even in the later case,
+     * this method can still be seen as a special case of {@code TransverseMercator} formulas.</div>
+     *
      * <p>This function is <em>almost</em> the converse of the {@link #expOfNorthing(double, double)} function.
      * In a Mercator inverse projection, the value of the {@code expOfSouthing} argument is {@code exp(-y)}.</p>
      *
@@ -181,10 +249,10 @@ abstract class ConformalProjection exten
          * values, for reducing rounding errors due to IEEE 754 arithmetic.
          */
         if (!ALLOW_TRIGONOMETRIC_IDENTITIES) {
-            φ += c8χ * sin(8*φ)
-               + c6χ * sin(6*φ)
-               + c4χ * sin(4*φ)
-               + c2χ * sin(2*φ);
+            φ += ci8 * sin(8*φ)
+               + ci6 * sin(6*φ)
+               + ci4 * sin(4*φ)
+               + ci2 * sin(2*φ);
         } else {
             /*
              * Same formula than above, be rewriten using trigonometric identities in order to have only two
@@ -192,8 +260,8 @@ abstract class ConformalProjection exten
              */
             final double sin_2φ = sin(2*φ);
             final double sin2 = sin_2φ * sin_2φ;
-            φ += ((c4χ + c8χ * (0.50 - sin2)) * cos(2*φ)
-                + (c2χ + c6χ * (0.75 - sin2))) * sin_2φ;
+            φ += ((ci4 + ci8 * (0.50 - sin2)) * cos(2*φ)
+                + (ci2 + ci6 * (0.75 - sin2))) * sin_2φ;
         }
         /*
          * Note: a previous version checked if the value of the smallest term c8χ⋅sin(8φ) was smaller than
@@ -317,6 +385,18 @@ abstract class ConformalProjection exten
      */
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
-        initialize();
+        computeCoefficients();
+    }
+
+    /**
+     * Verifies if a trigonometric identity produced the expected value. This method is used in assertions only,
+     * for values close to the [-1 … +1] range. The tolerance threshold is approximatively 1.5E-12 (note that it
+     * still about 7000 time greater than {@code Math.ulp(1.0)}).
+     *
+     * @see #ALLOW_TRIGONOMETRIC_IDENTITIES
+     */
+    static boolean identityEquals(final double actual, final double expected) {
+        return !(abs(actual - expected) >                               // Use !(a > b) instead of (a <= b) in order to tolerate NaN.
+                (ANGULAR_TOLERANCE / 1000) * max(1, abs(expected)));    // Increase tolerance for values outside the [-1 … +1] range.
     }
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java?rev=1707026&r1=1707025&r2=1707026&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java [UTF-8] Tue Oct  6 12:14:09 2015
@@ -210,6 +210,7 @@ public class LambertConicConformal exten
     @Workaround(library="JDK", version="1.7")
     private LambertConicConformal(final Initializer initializer) {
         super(initializer);
+        super.computeCoefficients();
         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);
         /*
@@ -367,7 +368,7 @@ public class LambertConicConformal exten
 
     /**
      * 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>)
+     * as a whole. The transform returned by this method expects (<var>longitude</var>, <var>latitude</var>)
      * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
      *
      * <p>The non-linear part of the returned transform will be {@code this} transform, except if the ellipsoid

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java?rev=1707026&r1=1707025&r2=1707026&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] Tue Oct  6 12:14:09 2015
@@ -201,6 +201,7 @@ public class Mercator extends ConformalP
     @Workaround(library="JDK", version="1.7")
     private Mercator(final Initializer initializer) {
         super(initializer);
+        super.computeCoefficients();
         this.variant = initializer.variant;
         /*
          * The "Longitude of natural origin" parameter is found in all Mercator projections and is mandatory.
@@ -302,7 +303,7 @@ public class Mercator extends ConformalP
 
     /**
      * 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>)
+     * as a whole. The transform returned by this method expects (<var>longitude</var>, <var>latitude</var>)
      * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
      *
      * <p>The non-linear part of the returned transform will be {@code this} transform, except if the ellipsoid

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java?rev=1707026&r1=1707025&r2=1707026&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java [UTF-8] Tue Oct  6 12:14:09 2015
@@ -129,40 +129,6 @@ public abstract class NormalizedProjecti
     private static final long serialVersionUID = 1969740225939106310L;
 
     /**
-     * {@code false} for using the original formulas as published by EPSG, or {@code true} for using formulas
-     * modified using trigonometric identities. The use of trigonometric identities is for reducing the amount
-     * of calls to the {@link Math#sin(double)} and similar methods. Some identities used are:
-     *
-     * <ul>
-     *   <li>sin(2θ) = 2⋅sinθ⋅cosθ</li>
-     *   <li>cos(2θ) = cos²θ - sin²θ</li>
-     *   <li>sin(3θ) = (3 - 4⋅sin²θ)⋅sinθ</li>
-     *   <li>cos(3θ) = (4⋅cos³θ) - 3⋅cosθ</li>
-     *   <li>sin(4θ) = (4 - 8⋅sin²θ)⋅sinθ⋅cosθ</li>
-     *   <li>cos(4θ) = (8⋅cos⁴θ) - (8⋅cos²θ) + 1</li>
-     * </ul>
-     *
-     * Hyperbolic formulas:
-     *
-     * <ul>
-     *   <li>sinh(2θ) = 2⋅sinhθ⋅coshθ</li>
-     *   <li>cosh(2θ) = cosh²θ + sinh²θ   =   2⋅cosh²θ - 1   =   1 + 2⋅sinh²θ</li>
-     *   <li>sinh(3θ) = (3 + 4⋅sinh²θ)⋅sinhθ</li>
-     *   <li>cosh(3θ) = ((4⋅cosh²θ) - 3)⋅coshθ</li>
-     *   <li>sinh(4θ) = (1 + 2⋅sinh²θ)⋅4.sinhθ⋅coshθ
-     *                = 4.cosh(2θ).sinhθ⋅coshθ</li>
-     *   <li>cosh(4θ) = (8⋅cosh⁴θ) - (8⋅cosh²θ) + 1
-     *                = 8⋅cosh²(θ) ⋅ (cosh²θ - 1) + 1
-     *                = 8⋅cosh²(θ) ⋅ sinh²(θ) + 1
-     *                = 2⋅sinh²(2θ) + 1</li>
-     * </ul>
-     *
-     * Note that since this boolean is static final, the compiler should exclude the code in the branch that is never
-     * executed (no need to comment-out that code).
-     */
-    static final boolean ALLOW_TRIGONOMETRIC_IDENTITIES = true;
-
-    /**
      * Maximum difference allowed when comparing longitudes or latitudes in radians.
      * The current value takes the system-wide angular tolerance value (equivalent to
      * about 1 cm on Earth) converted to radians.
@@ -490,7 +456,7 @@ public abstract class NormalizedProjecti
 
     /**
      * 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>)
+     * as a whole. The transform returned by this method expects (<var>longitude</var>, <var>latitude</var>)
      * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
      * Conversion to other units and {@linkplain org.apache.sis.referencing.cs.CoordinateSystems#swapAndScaleAxes
      * changes in axis order} are <strong>not</strong> managed by the returned transform.

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java?rev=1707026&r1=1707025&r2=1707026&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] Tue Oct  6 12:14:09 2015
@@ -133,6 +133,7 @@ public class PolarStereographic extends
     @Workaround(library="JDK", version="1.7")
     private PolarStereographic(final Initializer initializer) {
         super(initializer);
+        super.computeCoefficients();
         final byte variant = initializer.variant;
         /*
          * "Standard parallel" and "Latitude of origin" should be mutually exclusive,
@@ -248,7 +249,7 @@ public class PolarStereographic extends
 
     /**
      * 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>)
+     * as a whole. The transform returned by this method expects (<var>longitude</var>, <var>latitude</var>)
      * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
      *
      * <p>The non-linear part of the returned transform will be {@code this} transform, except if the ellipsoid

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java?rev=1707026&r1=1707025&r2=1707026&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java [UTF-8] Tue Oct  6 12:14:09 2015
@@ -17,8 +17,6 @@
 package org.apache.sis.referencing.operation.projection;
 
 import java.util.EnumMap;
-import java.io.IOException;
-import java.io.ObjectInputStream;
 import org.opengis.util.FactoryException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.referencing.operation.MathTransform;
@@ -65,19 +63,26 @@ import static org.apache.sis.math.MathFu
  * @see Mercator
  * @see ObliqueMercator
  */
-public class TransverseMercator extends NormalizedProjection {
+public class TransverseMercator extends ConformalProjection {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = 8167193470189463354L;
 
     /**
-     * Internal coefficients for computation, depending only on value of excentricity.
-     * Defined in §1.3.5.1 of IOGP Publication 373-7-2 – Geomatics Guidance Note number 7, part 2 – April 2015.
+     * Coefficients in the series expansion of the forward projection,
+     * depending only on {@linkplain #excentricity excentricity} value.
+     * The series expansion is of the following form:
      *
-     * <p>Consider those fields as final. They are not only of the purpose of {@link #readObject(ObjectInputStream)}.</p>
+     *     <blockquote>cf₂⋅f(2θ) + cf₄⋅f(4θ) + cf₆⋅f(6θ) + cf₈⋅f(8θ)</blockquote>
+     *
+     * Those coefficients are named h₁, h₂, h₃ and h₄ in §1.3.5.1 of
+     * IOGP Publication 373-7-2 – Geomatics Guidance Note number 7, part 2 – April 2015.
+     *
+     * <p><strong>Consider those fields as final!</strong>
+     * They are not final only for the purpose of {@link #computeCoefficients()}.</p>
      */
-    private transient double h1, h2, h3, h4, ih1, ih2, ih3, ih4;
+    private transient double cf2, cf4, cf6, cf8;
 
     /**
      * Creates a Transverse Mercator projection from the given parameters.
@@ -138,7 +143,7 @@ public class TransverseMercator extends
              */
             final DoubleDouble t = initializer.axisLengthRatio();   // t  =  b/a
             t.ratio_1m_1p();                                        // t  =  (1 - t) / (1 + t)
-            initialize(t.doubleValue());
+            computeCoefficients(t.doubleValue());
             /*
              * Compute B  =  (1 + n²/4 + n⁴/64) / (1 + n)
              */
@@ -161,10 +166,10 @@ public class TransverseMercator extends
         final double Q = asinh(tan(φ0)) - excentricity * atanh(excentricity * sin(φ0));
         final double β = atan(sinh(Q));
         final DoubleDouble M0 = new DoubleDouble();
-        M0.value = h4 * sin(8*β)
-                 + h3 * sin(6*β)
-                 + h2 * sin(4*β)
-                 + h1 * sin(2*β)
+        M0.value = cf8 * sin(8*β)
+                 + cf6 * sin(6*β)
+                 + cf4 * sin(4*β)
+                 + cf2 * sin(2*β)
                  + β;
         M0.multiply(B);
         M0.negate();
@@ -188,21 +193,22 @@ public class TransverseMercator extends
         /*
          * When rewriting equations using trigonometric identities, some constants appear.
          * For example sin(2θ) = 2⋅sinθ⋅cosθ, so we can factor out the 2 constant into the
-         * corresponding 'h' field.  Note: this factorization can only be performed after
+         * corresponding 'c' field.  Note: this factorization can only be performed after
          * the constructor finished to compute other constants.
          */
         if (ALLOW_TRIGONOMETRIC_IDENTITIES) {
-            h2 *=  4;   ih2 *=  4;
-            h3 *= 16;   ih3 *= 16;
-            h4 *= 64;   ih4 *= 64;
+            // Multiplication by powers of 2 does not bring any additional rounding error.
+            cf4 *=  4;   ci4 *=  4;
+            cf6 *= 16;   ci6 *= 16;
+            cf8 *= 64;   ci8 *= 64;
         }
     }
 
     /**
-     * Restores transient fields after deserialization.
+     * Automatically invoked after deserialization for restoring transient fields.
      */
-    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
-        in.defaultReadObject();
+    @Override
+    final void computeCoefficients() {
         /*
          * Double-double precision is necessary for computing 'n', otherwise we have rounding errors
          * in the 3 last digits. Note that we still have sometime a 1 ULP difference compared to the
@@ -212,11 +218,12 @@ public class TransverseMercator extends
         t.subtract(excentricitySquared, 0);
         t.sqrt();
         t.ratio_1m_1p();
-        initialize(t.doubleValue());
+        computeCoefficients(t.doubleValue());
         if (ALLOW_TRIGONOMETRIC_IDENTITIES) {
-            h2 *=  4;   ih2 *=  4;
-            h3 *= 16;   ih3 *= 16;
-            h4 *= 64;   ih4 *= 64;
+            // Same scaling than in the constructor.
+            cf4 *=  4;   ci4 *=  4;
+            cf6 *= 16;   ci6 *= 16;
+            cf8 *= 64;   ci8 *= 64;
         }
     }
 
@@ -238,26 +245,26 @@ public class TransverseMercator extends
      *
      * @param n The value of {@code f / (2-f)} where {@code f} is the flattening factor.
      */
-    private void initialize(final double n) {
+    private void computeCoefficients(final double n) {
         final double n2 = n  * n;
         final double n3 = n2 * n;
         final double n4 = n2 * n2;
         /*
-         * Coefficients for direct projection.
+         * Coefficients for the forward projections.
          * Add the smallest values first in order to reduce rounding errors.
          */
-        h1 = (   41. /    180)*n4  +  ( 5. /  16)*n3  +  (-2. /  3)*n2  +  n/2;
-        h2 = (  557. /   1440)*n4  +  (-3. /   5)*n3  +  (13. / 48)*n2;
-        h3 = ( -103. /    140)*n4  +  (61. / 240)*n3;
-        h4 = (49561. / 161280)*n4;
+        cf2 = (   41. /    180)*n4  +  ( 5. /  16)*n3  +  (-2. /  3)*n2  +  n/2;
+        cf4 = (  557. /   1440)*n4  +  (-3. /   5)*n3  +  (13. / 48)*n2;
+        cf6 = ( -103. /    140)*n4  +  (61. / 240)*n3;
+        cf8 = (49561. / 161280)*n4;
         /*
-         * Coefficients for inverse projection.
+         * Coefficients for the inverse projections.
          * Add the smallest values first in order to reduce rounding errors.
          */
-        ih1 = (  -1. /    360)*n4  +  (37. /  96)*n3  +  (-2. /  3)*n2  +  n/2;
-        ih2 = (-437. /   1440)*n4  +  ( 1. /  15)*n3  +  ( 1. / 48)*n2;
-        ih3 = ( -37. /    840)*n4  +  (17. / 480)*n3;
-        ih4 = (4397. / 161280)*n4;
+        ci2 = (  -1. /    360)*n4  +  (37. /  96)*n3  +  (-2. /  3)*n2  +  n/2;
+        ci4 = (-437. /   1440)*n4  +  ( 1. /  15)*n3  +  ( 1. / 48)*n2;
+        ci6 = ( -37. /    840)*n4  +  (17. / 480)*n3;
+        ci8 = (4397. / 161280)*n4;
     }
 
     /**
@@ -265,19 +272,15 @@ public class TransverseMercator extends
      */
     TransverseMercator(final TransverseMercator other) {
         super(other);
-        h1  = other. h1;
-        h2  = other. h2;
-        h3  = other. h3;
-        h4  = other. h4;
-        ih1 = other.ih1;
-        ih2 = other.ih2;
-        ih3 = other.ih3;
-        ih4 = other.ih4;
+        cf2 = other.cf2;
+        cf4 = other.cf4;
+        cf6 = other.cf6;
+        cf8 = other.cf8;
     }
 
     /**
      * 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>)
+     * as a whole. The transform returned by this method expects (<var>longitude</var>, <var>latitude</var>)
      * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
      *
      * <p>The non-linear part of the returned transform will be {@code this} transform, except if the ellipsoid
@@ -309,8 +312,8 @@ public class TransverseMercator extends
                             final double[] dstPts, final int dstOff,
                             final boolean derivate) throws ProjectionException
     {
-        final double λ     = srcPts[srcOff];
-        final double φ     = srcPts[srcOff + 1];
+        final double λ     = srcPts[srcOff  ];
+        final double φ     = srcPts[srcOff+1];
         final double sinλ  = sin(λ);
         final double ℯsinφ = sin(φ) * excentricity;
         final double Q     = asinh(tan(φ)) - atanh(ℯsinφ) * excentricity;
@@ -346,12 +349,12 @@ public class TransverseMercator extends
         } else {
             final double sin2 = sin_2ξ0 * sin_2ξ0;
             final double cos2 = cos_2ξ0 * cos_2ξ0;
-            sin_4ξ0 = sin_2ξ0 * cos_2ξ0;                      // sin(4⋅ξ₀) ÷ 2
-            cos_4ξ0 = (cos2  - sin2)   * 0.5;                 // cos(4⋅ξ₀) ÷ 2
-            sin_6ξ0 = (0.75  - sin2)   * sin_2ξ0;             // sin(6⋅ξ₀) ÷ 4
-            cos_6ξ0 = (cos2  - 0.75)   * cos_2ξ0;             // cos(6⋅ξ₀) ÷ 4
-            sin_8ξ0 =          sin_4ξ0 * cos_4ξ0;             // sin(8⋅ξ₀) ÷ 8
-            cos_8ξ0 =  0.125 - sin_4ξ0 * sin_4ξ0;             // cos(8⋅ξ₀) ÷ 8
+            sin_4ξ0 = sin_2ξ0 * cos_2ξ0;                assert identityEquals(sin_4ξ0, sin(4*ξ0) / 2) : ξ0;
+            cos_4ξ0 = (cos2  - sin2)   * 0.5;           assert identityEquals(cos_4ξ0, cos(4*ξ0) / 2) : ξ0;
+            sin_6ξ0 = (0.75  - sin2)   * sin_2ξ0;       assert identityEquals(sin_6ξ0, sin(6*ξ0) / 4) : ξ0;
+            cos_6ξ0 = (cos2  - 0.75)   * cos_2ξ0;       assert identityEquals(cos_6ξ0, cos(6*ξ0) / 4) : ξ0;
+            sin_8ξ0 =          sin_4ξ0 * cos_4ξ0;       assert identityEquals(sin_8ξ0, sin(8*ξ0) / 8) : ξ0;
+            cos_8ξ0 =  0.125 - sin_4ξ0 * sin_4ξ0;       assert identityEquals(cos_8ξ0, cos(8*ξ0) / 8) : ξ0;
         }
         /*
          * Compute sinh(2⋅ξ₀), sinh(4⋅ξ₀), sinh(6⋅ξ₀), sinh(8⋅ξ₀) and same for cosh, but using the following
@@ -372,12 +375,12 @@ public class TransverseMercator extends
         } else {
             final double sinh2 = sinh_2η0 * sinh_2η0;
             final double cosh2 = cosh_2η0 * cosh_2η0;
-            cosh_4η0 = (cosh2 + sinh2) * 0.5;                   // cosh(4⋅η₀) ÷ 2
-            sinh_4η0 = cosh_2η0 * sinh_2η0;                     // sinh(4⋅η₀) ÷ 2
-            cosh_6η0 = cosh_2η0 * (cosh2   - 0.75);             // cosh(6⋅η₀) ÷ 4
-            sinh_6η0 = sinh_2η0 * (sinh2   + 0.75);             // sinh(6⋅η₀) ÷ 4
-            cosh_8η0 = sinh_4η0 * sinh_4η0 + 0.125;             // cosh(8⋅η₀) ÷ 8
-            sinh_8η0 = sinh_4η0 * cosh_4η0;                     // sinh(8⋅η₀) ÷ 8
+            cosh_4η0 = (cosh2 + sinh2) * 0.5;           assert identityEquals(cosh_4η0, cosh(4*η0) / 2) : η0;
+            sinh_4η0 = cosh_2η0 * sinh_2η0;             assert identityEquals(sinh_4η0, sinh(4*η0) / 2) : η0;
+            cosh_6η0 = cosh_2η0 * (cosh2   - 0.75);     assert identityEquals(cosh_6η0, cosh(6*η0) / 4) : η0;
+            sinh_6η0 = sinh_2η0 * (sinh2   + 0.75);     assert identityEquals(sinh_6η0, sinh(6*η0) / 4) : η0;
+            cosh_8η0 = sinh_4η0 * sinh_4η0 + 0.125;     assert identityEquals(cosh_8η0, cosh(8*η0) / 8) : η0;
+            sinh_8η0 = sinh_4η0 * cosh_4η0;             assert identityEquals(sinh_8η0, sinh(8*η0) / 8) : η0;
         }
         /*
          * Assuming that (λ, φ) ↦ Proj((λ, φ))
@@ -386,22 +389,22 @@ public class TransverseMercator extends
          * => (λ, φ) ↦ (η(λ, φ), ξ(λ, φ)).
          */
         //-- ξ(λ, φ)
-        final double ξ = h4 * sin_8ξ0 * cosh_8η0
-                       + h3 * sin_6ξ0 * cosh_6η0
-                       + h2 * sin_4ξ0 * cosh_4η0
-                       + h1 * sin_2ξ0 * cosh_2η0
+        final double ξ = cf8 * sin_8ξ0 * cosh_8η0
+                       + cf6 * sin_6ξ0 * cosh_6η0
+                       + cf4 * sin_4ξ0 * cosh_4η0
+                       + cf2 * sin_2ξ0 * cosh_2η0
                        + ξ0;
 
         //-- η(λ, φ)
-        final double η = h4 * cos_8ξ0 * sinh_8η0
-                       + h3 * cos_6ξ0 * sinh_6η0
-                       + h2 * cos_4ξ0 * sinh_4η0
-                       + h1 * cos_2ξ0 * sinh_2η0
+        final double η = cf8 * cos_8ξ0 * sinh_8η0
+                       + cf6 * cos_6ξ0 * sinh_6η0
+                       + cf4 * cos_4ξ0 * sinh_4η0
+                       + cf2 * cos_2ξ0 * sinh_2η0
                        + η0;
 
         if (dstPts != null) {
-            dstPts[dstOff    ] = η;
-            dstPts[dstOff + 1] = ξ;
+            dstPts[dstOff  ] = η;
+            dstPts[dstOff+1] = ξ;
         }
         if (!derivate) {
             return null;
@@ -436,31 +439,31 @@ public class TransverseMercator extends
          */
         //-- dξ(λ, φ) / dλ
         final double dξ_dλ = dξ0_dλ
-                           + 2 * (h1 * (dξ0_dλ * cos_2ξ0 * cosh_2η0 + dη0_dλ * sinh_2η0 * sin_2ξ0)
-                           + 3 *  h3 * (dξ0_dλ * cos_6ξ0 * cosh_6η0 + dη0_dλ * sinh_6η0 * sin_6ξ0)
-                           + 2 * (h2 * (dξ0_dλ * cos_4ξ0 * cosh_4η0 + dη0_dλ * sinh_4η0 * sin_4ξ0)
-                           + 2 *  h4 * (dξ0_dλ * cos_8ξ0 * cosh_8η0 + dη0_dλ * sinh_8η0 * sin_8ξ0)));
+                           + 2 * (cf2 * (dξ0_dλ * cos_2ξ0 * cosh_2η0 + dη0_dλ * sinh_2η0 * sin_2ξ0)
+                           + 3 *  cf6 * (dξ0_dλ * cos_6ξ0 * cosh_6η0 + dη0_dλ * sinh_6η0 * sin_6ξ0)
+                           + 2 * (cf4 * (dξ0_dλ * cos_4ξ0 * cosh_4η0 + dη0_dλ * sinh_4η0 * sin_4ξ0)
+                           + 2 *  cf8 * (dξ0_dλ * cos_8ξ0 * cosh_8η0 + dη0_dλ * sinh_8η0 * sin_8ξ0)));
 
         //-- dξ(λ, φ) / dφ
         final double dξ_dφ = dξ0_dφ
-                           + 2 * (h1 * (dξ0_dφ * cos_2ξ0 * cosh_2η0 + dη0_dφ * sinh_2η0 * sin_2ξ0)
-                           + 3 *  h3 * (dξ0_dφ * cos_6ξ0 * cosh_6η0 + dη0_dφ * sinh_6η0 * sin_6ξ0)
-                           + 2 * (h2 * (dξ0_dφ * cos_4ξ0 * cosh_4η0 + dη0_dφ * sinh_4η0 * sin_4ξ0)
-                           + 2 *  h4 * (dξ0_dφ * cos_8ξ0 * cosh_8η0 + dη0_dφ * sinh_8η0 * sin_8ξ0)));
+                           + 2 * (cf2 * (dξ0_dφ * cos_2ξ0 * cosh_2η0 + dη0_dφ * sinh_2η0 * sin_2ξ0)
+                           + 3 *  cf6 * (dξ0_dφ * cos_6ξ0 * cosh_6η0 + dη0_dφ * sinh_6η0 * sin_6ξ0)
+                           + 2 * (cf4 * (dξ0_dφ * cos_4ξ0 * cosh_4η0 + dη0_dφ * sinh_4η0 * sin_4ξ0)
+                           + 2 *  cf8 * (dξ0_dφ * cos_8ξ0 * cosh_8η0 + dη0_dφ * sinh_8η0 * sin_8ξ0)));
 
         //-- dη(λ, φ) / dλ
         final double dη_dλ = dη0_dλ
-                           + 2 * (h1 * (dη0_dλ * cosh_2η0 * cos_2ξ0 - dξ0_dλ * sin_2ξ0 * sinh_2η0)
-                           + 3 *  h3 * (dη0_dλ * cosh_6η0 * cos_6ξ0 - dξ0_dλ * sin_6ξ0 * sinh_6η0)
-                           + 2 * (h2 * (dη0_dλ * cosh_4η0 * cos_4ξ0 - dξ0_dλ * sin_4ξ0 * sinh_4η0)
-                           + 2 *  h4 * (dη0_dλ * cosh_8η0 * cos_8ξ0 - dξ0_dλ * sin_8ξ0 * sinh_8η0)));
+                           + 2 * (cf2 * (dη0_dλ * cosh_2η0 * cos_2ξ0 - dξ0_dλ * sin_2ξ0 * sinh_2η0)
+                           + 3 *  cf6 * (dη0_dλ * cosh_6η0 * cos_6ξ0 - dξ0_dλ * sin_6ξ0 * sinh_6η0)
+                           + 2 * (cf4 * (dη0_dλ * cosh_4η0 * cos_4ξ0 - dξ0_dλ * sin_4ξ0 * sinh_4η0)
+                           + 2 *  cf8 * (dη0_dλ * cosh_8η0 * cos_8ξ0 - dξ0_dλ * sin_8ξ0 * sinh_8η0)));
 
         //-- dη(λ, φ) / dφ
         final double dη_dφ = dη0_dφ
-                           + 2 * (h1 * (dη0_dφ * cosh_2η0 * cos_2ξ0 - dξ0_dφ * sin_2ξ0 * sinh_2η0)
-                           + 3 *  h3 * (dη0_dφ * cosh_6η0 * cos_6ξ0 - dξ0_dφ * sin_6ξ0 * sinh_6η0)
-                           + 2 * (h2 * (dη0_dφ * cosh_4η0 * cos_4ξ0 - dξ0_dφ * sin_4ξ0 * sinh_4η0)
-                           + 2 *  h4 * (dη0_dφ * cosh_8η0 * cos_8ξ0 - dξ0_dφ * sin_8ξ0 * sinh_8η0)));
+                           + 2 * (cf2 * (dη0_dφ * cosh_2η0 * cos_2ξ0 - dξ0_dφ * sin_2ξ0 * sinh_2η0)
+                           + 3 *  cf6 * (dη0_dφ * cosh_6η0 * cos_6ξ0 - dξ0_dφ * sin_6ξ0 * sinh_6η0)
+                           + 2 * (cf4 * (dη0_dφ * cosh_4η0 * cos_4ξ0 - dξ0_dφ * sin_4ξ0 * sinh_4η0)
+                           + 2 *  cf8 * (dη0_dφ * cosh_8η0 * cos_8ξ0 - dξ0_dφ * sin_8ξ0 * sinh_8η0)));
 
         return new Matrix2(dη_dλ, dη_dφ,
                            dξ_dλ, dξ_dφ);
@@ -476,8 +479,8 @@ public class TransverseMercator extends
                                     final double[] dstPts, final int dstOff)
             throws ProjectionException
     {
-        final double η = srcPts[srcOff];
-        final double ξ = srcPts[srcOff + 1];
+        final double η = srcPts[srcOff  ];
+        final double ξ = srcPts[srcOff+1];
         /*
          * Following calculation of sin_2ξ, sin_4ξ, etc. is basically a copy-and-paste of the code in transform(…).
          * Its purpose is the same than for transform(…): reduce the amount of calls to Math.sin(double) and other
@@ -508,34 +511,34 @@ public class TransverseMercator extends
         } else {
             final double sin2 = sin_2ξ * sin_2ξ;
             final double cos2 = cos_2ξ * cos_2ξ;
-            sin_4ξ = sin_2ξ * cos_2ξ;                   // sin(4⋅ξ) ÷ 2
-            cos_4ξ = (cos2 - sin2) * 0.5;               // cos(4⋅ξ) ÷ 2
-            sin_6ξ = (0.75  - sin2)  * sin_2ξ;          // sin(6⋅ξ) ÷ 4
-            cos_6ξ = (cos2  - 0.75)  * cos_2ξ;          // cos(6⋅ξ) ÷ 4
-            sin_8ξ =          sin_4ξ * cos_4ξ;          // sin(8⋅ξ) ÷ 8
-            cos_8ξ =  0.125 - sin_4ξ * sin_4ξ;          // cos(8⋅ξ) ÷ 8
+            sin_4ξ = sin_2ξ * cos_2ξ;                   assert identityEquals(sin_4ξ, sin(4*ξ) / 2) : ξ;
+            cos_4ξ = (cos2  - sin2)   * 0.5;            assert identityEquals(cos_4ξ, cos(4*ξ) / 2) : ξ;
+            sin_6ξ = (0.75  - sin2)   * sin_2ξ;         assert identityEquals(sin_6ξ, sin(6*ξ) / 4) : ξ;
+            cos_6ξ = (cos2  - 0.75)   * cos_2ξ;         assert identityEquals(cos_6ξ, cos(6*ξ) / 4) : ξ;
+            sin_8ξ =          sin_4ξ * cos_4ξ;          assert identityEquals(sin_8ξ, sin(8*ξ) / 8) : ξ;
+            cos_8ξ =  0.125 - sin_4ξ * sin_4ξ;          assert identityEquals(cos_8ξ, cos(8*ξ) / 8) : ξ;
 
             final double sinh2 = sinh_2η * sinh_2η;
             final double cosh2 = cosh_2η * cosh_2η;
-            cosh_4η = (cosh2 + sinh2) * 0.5;            // cosh(4⋅η) ÷ 2
-            sinh_4η = cosh_2η * sinh_2η;                // sinh(4⋅η) ÷ 2
-            cosh_6η = cosh_2η * (cosh2  - 0.75);        // cosh(6⋅η) ÷ 4
-            sinh_6η = sinh_2η * (sinh2  + 0.75);        // sinh(6⋅η) ÷ 4
-            cosh_8η = sinh_4η * sinh_4η + 0.125;        // cosh(8⋅η) ÷ 8
-            sinh_8η = sinh_4η * cosh_4η;                // sinh(8⋅η) ÷ 8
+            cosh_4η = (cosh2 + sinh2) * 0.5;            assert identityEquals(cosh_4η, cosh(4*η) / 2) : η;
+            sinh_4η = cosh_2η * sinh_2η;                assert identityEquals(sinh_4η, sinh(4*η) / 2) : η;
+            cosh_6η = cosh_2η * (cosh2   - 0.75);       assert identityEquals(cosh_6η, cosh(6*η) / 4) : η;
+            sinh_6η = sinh_2η * (sinh2   + 0.75);       assert identityEquals(sinh_6η, sinh(6*η) / 4) : η;
+            cosh_8η = sinh_4η * sinh_4η + 0.125;        assert identityEquals(cosh_8η, cosh(8*η) / 8) : η;
+            sinh_8η = sinh_4η * cosh_4η;                assert identityEquals(sinh_8η, sinh(8*η) / 8) : η;
         }
         /*
          * The actual inverse transform.
          */
-        final double ξ0 = ξ - (ih4 * sin_8ξ * cosh_8η
-                             + ih3 * sin_6ξ * cosh_6η
-                             + ih2 * sin_4ξ * cosh_4η
-                             + ih1 * sin_2ξ * cosh_2η);
-
-        final double η0 = η - (ih4 * cos_8ξ * sinh_8η
-                             + ih3 * cos_6ξ * sinh_6η
-                             + ih2 * cos_4ξ * sinh_4η
-                             + ih1 * cos_2ξ * sinh_2η);
+        final double ξ0 = ξ - (ci8 * sin_8ξ * cosh_8η
+                             + ci6 * sin_6ξ * cosh_6η
+                             + ci4 * sin_4ξ * cosh_4η
+                             + ci2 * sin_2ξ * cosh_2η);
+
+        final double η0 = η - (ci8 * cos_8ξ * sinh_8η
+                             + ci6 * cos_6ξ * sinh_6η
+                             + ci4 * cos_4ξ * sinh_4η
+                             + ci2 * cos_2ξ * sinh_2η);
 
         final double β = asin(sin(ξ0) / cosh(η0));
         final double Q = asinh(tan(β));
@@ -547,8 +550,8 @@ public class TransverseMercator extends
             final double c = excentricity * atanh(excentricity * tanh(Qp));
             Qp = Q + c;
             if (abs(c - p) <= ITERATION_TOLERANCE) {
-                dstPts[dstOff    ] = asin(tanh(η0) / cos(β));
-                dstPts[dstOff + 1] = atan(sinh(Qp));
+                dstPts[dstOff  ] = asin(tanh(η0) / cos(β));
+                dstPts[dstOff+1] = atan(sinh(Qp));
                 return;
             }
             p = c;
@@ -590,8 +593,8 @@ public class TransverseMercator extends
                                 final double[] dstPts, final int dstOff,
                                 final boolean derivate) throws ProjectionException
         {
-            final double λ    = srcPts[srcOff];
-            final double φ    = srcPts[srcOff + 1];
+            final double λ    = srcPts[srcOff  ];
+            final double φ    = srcPts[srcOff+1];
             final double sinλ = sin(λ);
             final double cosλ = cos(λ);
             final double sinφ = sin(φ);

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java?rev=1707026&r1=1707025&r2=1707026&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java [UTF-8] Tue Oct  6 12:14:09 2015
@@ -37,7 +37,7 @@ import org.junit.Test;
 
 import static java.lang.StrictMath.*;
 import static java.lang.Double.*;
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -49,7 +49,7 @@ import static org.junit.Assert.*;
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
  * @since   0.6
- * @version 0.6
+ * @version 0.7
  * @module
  */
 @DependsOn(ConformalProjectionTest.class)
@@ -336,4 +336,23 @@ public final strictfp class LambertConic
         tolerance = Formulas.LINEAR_TOLERANCE;
         compareEllipticalWithSpherical(CoordinateDomain.GEOGRAPHIC_SAFE, 0);
     }
+
+    /**
+     * Verifies that deserialized projections work as expected. This implies that deserialization
+     * recomputed the internal transient fields, especially the series expansion coefficients.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a coordinate.
+     */
+    @Test
+    @DependsOnMethod("testLambertConicConformal1SP")
+    public void testSerialization() throws FactoryException, TransformException {
+        createNormalizedProjection(true, 40);
+        final double[] source = CoordinateDomain.GEOGRAPHIC_RADIANS_NORTH.generateRandomInput(TestUtilities.createRandomNumberGenerator(), 2, 10);
+        final double[] target = new double[source.length];
+        transform.transform(source, 0, target, 0, 10);
+        transform = assertSerializedEquals(transform);
+        tolerance = Formulas.LINEAR_TOLERANCE;
+        verifyTransform(source, target);
+    }
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java?rev=1707026&r1=1707025&r2=1707026&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java [UTF-8] Tue Oct  6 12:14:09 2015
@@ -74,6 +74,7 @@ final strictfp class NoOp extends Confor
         super(new Initializer(new DefaultOperationMethod(
                 Collections.singletonMap(DefaultOperationMethod.NAME_KEY, parameters.getDescriptor().getName()),
                 2, 2, parameters.getDescriptor()), parameters, Collections.emptyMap(), (byte) 0));
+        super.computeCoefficients();
     }
 
     /**

Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java?rev=1707026&r1=1707025&r2=1707026&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java [UTF-8] Tue Oct  6 12:14:09 2015
@@ -24,9 +24,11 @@ import org.apache.sis.parameter.Paramete
 import org.apache.sis.referencing.operation.transform.CoordinateDomain;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.TestUtilities;
 import org.junit.Test;
 
 import static java.lang.StrictMath.toRadians;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -34,7 +36,7 @@ import static java.lang.StrictMath.toRad
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.6
- * @version 0.6
+ * @version 0.7
  * @module
  */
 @DependsOn(NormalizedProjectionTest.class)
@@ -138,4 +140,23 @@ public final strictfp class TransverseMe
         verifyDerivative(toRadians(-3), toRadians(30));
         verifyDerivative(toRadians(+6), toRadians(60));
     }
+
+    /**
+     * Verifies that deserialized projections work as expected. This implies that deserialization
+     * recomputed the internal transient fields, especially the series expansion coefficients.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a coordinate.
+     */
+    @Test
+    @DependsOnMethod("testTransverseMercator")
+    public void testSerialization() throws FactoryException, TransformException {
+        createNormalizedProjection(true, 40);
+        final double[] source = CoordinateDomain.GEOGRAPHIC_RADIANS_HALF_λ.generateRandomInput(TestUtilities.createRandomNumberGenerator(), 2, 10);
+        final double[] target = new double[source.length];
+        transform.transform(source, 0, target, 0, 10);
+        transform = assertSerializedEquals(transform);
+        tolerance = Formulas.LINEAR_TOLERANCE;
+        verifyTransform(source, target);
+    }
 }