sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch master updated: Cherry-pick the https://issues.apache.org/jira/browse/SIS-486 fix from geoapi-4.0 branch.
Date Mon, 06 Jan 2020 18:04:58 GMT
This is an automated email from the ASF dual-hosted git repository.

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


The following commit(s) were added to refs/heads/master by this push:
     new 9633967  Cherry-pick the https://issues.apache.org/jira/browse/SIS-486 fix from geoapi-4.0
branch.
9633967 is described below

commit 9633967f4caeb387755dbe5011a620b1054b41f5
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Jan 6 19:03:45 2020 +0100

    Cherry-pick the https://issues.apache.org/jira/browse/SIS-486 fix from geoapi-4.0 branch.
---
 .../operation/projection/AlbersEqualArea.java      | 35 +++++++++++-----
 .../operation/projection/Initializer.java          | 42 +++++++++++++++++++
 .../projection/LambertConicConformal.java          | 29 +++++++++----
 .../operation/projection/NormalizedProjection.java | 36 +++++++++++++++-
 .../operation/projection/ObliqueStereographic.java | 37 ++++++++++------
 .../operation/projection/SatelliteTracking.java    | 15 ++++++-
 .../operation/projection/AlbersEqualAreaTest.java  | 49 +++++++++++++++++++++-
 .../projection/LambertConicConformalTest.java      |  9 ++--
 .../org/apache/sis/internal/util/DoubleDouble.java | 11 ++++-
 .../java/org/apache/sis/measure/Longitude.java     |  6 +--
 .../apache/sis/internal/util/DoubleDoubleTest.java |  9 ++--
 11 files changed, 231 insertions(+), 47 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java
index fe17827..92f1538 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java
@@ -50,7 +50,7 @@ import static org.apache.sis.internal.referencing.provider.AlbersEqualArea.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
- * @version 0.8
+ * @version 1.1
  * @since   0.8
  * @module
  */
@@ -58,7 +58,7 @@ public class AlbersEqualArea extends EqualAreaProjection {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = -3024658742514888646L;
+    private static final long serialVersionUID = -3466040922402982480L;
 
     /**
      * Internal coefficients for computation, depending only on eccentricity and values of
standards parallels.
@@ -82,6 +82,15 @@ public class AlbersEqualArea extends EqualAreaProjection {
     final double C;
 
     /**
+     * A bound of the [−n⋅π … n⋅π] range, which is the valid range of  θ = n⋅λ
 values.
+     * Some (not all) θ values need to be shifted inside that range before to use them
+     * in trigonometric functions.
+     *
+     * @see Initializer#boundOfScaledLongitude(DoubleDouble)
+     */
+    final double θ_bound;
+
+    /**
      * Creates an Albers Equal Area projection from the given parameters.
      *
      * @param method      description of the projection parameters.
@@ -163,7 +172,8 @@ public class AlbersEqualArea extends EqualAreaProjection {
         final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
         denormalize.convertBefore(0, rn, null); rn.negate();
         denormalize.convertBefore(1, rn, ρ0);   rn.inverseDivide(-1);
-        normalize.convertAfter(0, rn, null);
+        normalize.convertAfter(0, rn, null);    // On this line, `rn` became `n`.
+        θ_bound = initializer.boundOfScaledLongitude(rn);
     }
 
     /**
@@ -171,8 +181,9 @@ public class AlbersEqualArea extends EqualAreaProjection {
      */
     AlbersEqualArea(final AlbersEqualArea other) {
         super(other);
-        nm = other.nm;
-        C  = other.C;
+        nm      = other.nm;
+        C       = other.C;
+        θ_bound = other.θ_bound;
     }
 
     /**
@@ -227,8 +238,9 @@ public class AlbersEqualArea extends EqualAreaProjection {
                             final double[] dstPts, final int dstOff,
                             final boolean derivate) throws ProjectionException
     {
-        final double θ = srcPts[srcOff  ];      // θ = n⋅λ
-        final double φ = srcPts[srcOff+1];
+        // θ = n⋅λ  reduced to  [−n⋅π … n⋅π]  range.
+        final double θ = wraparoundScaledLongitude(srcPts[srcOff], θ_bound);
+        final double φ = srcPts[srcOff + 1];
         final double cosθ = cos(θ);
         final double sinθ = sin(θ);
         final double sinφ = sin(φ);
@@ -288,7 +300,7 @@ public class AlbersEqualArea extends EqualAreaProjection {
      *
      * @author  Martin Desruisseaux (Geomatys)
      * @author  Rémi Maréchal (Geomatys)
-     * @version 0.8
+     * @version 1.1
      * @since   0.8
      * @module
      */
@@ -296,7 +308,7 @@ public class AlbersEqualArea extends EqualAreaProjection {
         /**
          * For cross-version compatibility.
          */
-        private static final long serialVersionUID = 9090765015127854096L;
+        private static final long serialVersionUID = -7238296545347764989L;
 
         /**
          * Constructs a new map projection from the parameters of the given projection.
@@ -315,8 +327,9 @@ public class AlbersEqualArea extends EqualAreaProjection {
                                 final double[] dstPts, final int dstOff,
                                 final boolean derivate)
         {
-            final double θ = srcPts[srcOff];                // θ = n⋅λ
-            final double φ = srcPts[srcOff+1];
+            // θ = n⋅λ  reduced to  [−n⋅π … n⋅π]  range.
+            final double θ = wraparoundScaledLongitude(srcPts[srcOff], θ_bound);
+            final double φ = srcPts[srcOff + 1];
             final double cosθ = cos(θ);
             final double sinθ = sin(θ);
             final double sinφ = sin(φ);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
index 804d082..ff0ac5c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java
@@ -88,6 +88,11 @@ final class Initializer {
     final DoubleDouble eccentricitySquared;
 
     /**
+     * Sign of central meridian: -1 if negative, 0 if zero, +1 if positive.
+     */
+    private final byte signum_λ0;
+
+    /**
      * Map projection variant.
      * Values from 0 to 127 inclusive are convenience values at the discretion of {@link
NormalizedProjection} subclasses.
      * Values from 128 to 255 inclusive are values handled in a special way by {@link Initializer}
constructor.
@@ -143,6 +148,8 @@ final class Initializer {
         final double fn = getAndStore(roles.get(ParameterRole.FALSE_NORTHING))
                         - getAndStore(roles.get(ParameterRole.FALSE_SOUTHING));
 
+        signum_λ0 = (λ0 > 0) ? (byte) +1 :
+                    (λ0 < 0) ? (byte) -1 : 0;
         eccentricitySquared = new DoubleDouble();
         DoubleDouble k = DoubleDouble.createAndGuessError(a);  // The value by which to multiply
all results of normalized projection.
         if (a != b) {
@@ -379,4 +386,39 @@ final class Initializer {
         s.inverseDivide(cosφ);
         return s.doubleValue();
     }
+
+    /**
+     * Returns a bound of the [−n⋅π … n⋅π] range, which is the valid range of 
θ = n⋅λ  values.
+     * This method is invoked by map projections that multiply the longitude values by some
scale factor before
+     * to use them in trigonometric functions. Usually we do not explicitly wraparound the
longitude values,
+     * because trigonometric functions do that automatically for us. However if the longitude
is multiplied
+     * by some factor before to be used in trigonometric functions, then that implicit wraparound
is not the
+     * one we expect. The map projection code needs to perform explicit wraparound in such
cases.
+     *
+     * @param  n  the factor by which longitude values are multiplied before use in trigonometry.
+     * @return a bound of the [−n⋅π … n⋅π] range.
+     *
+     * @see NormalizedProjection#wraparoundScaledLongitude(double, double)
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-486">SIS-486</a>
+     */
+    final double boundOfScaledLongitude(final double n) {
+        return boundOfScaledLongitude(new DoubleDouble(n));
+    }
+
+    /**
+     * Same as {@link #boundOfScaledLongitude(double)} with opportunistic use of double-double
precision.
+     * This is used when than object is available anyway.
+     *
+     * @param  n  the factor by which longitude values are multiplied before use in trigonometry.
+     * @return a bound of the [−n⋅π … n⋅π] range.
+     */
+    final double boundOfScaledLongitude(final DoubleDouble n) {
+        if (signum_λ0 == 0 || n.doubleValue() >= 1) {
+            return Double.NaN;                          // Do not apply any wraparound.
+        }
+        final DoubleDouble r = DoubleDouble.createPi();
+        r.multiply(n);
+        final double θ_bound = abs(r.doubleValue());
+        return (signum_λ0 < 0) ? θ_bound : -θ_bound;
+    }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
index 090f0da..91209c3 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
@@ -60,7 +60,7 @@ import static org.apache.sis.math.MathFunctions.isPositive;
  * @author  André Gosselin (MPO)
  * @author  Rueben Schulz (UBC)
  * @author  Rémi Maréchal (Geomatys)
- * @version 0.8
+ * @version 1.1
  * @since   0.6
  * @module
  */
@@ -68,7 +68,7 @@ public class LambertConicConformal extends ConformalProjection {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 2067358524298002016L;
+    private static final long serialVersionUID = -8971522854919443706L;
 
     /**
      * Codes for variants of Lambert Conical Conformal projection. Those variants modify
the way the projections are
@@ -141,6 +141,15 @@ public class LambertConicConformal extends ConformalProjection {
     final double n;
 
     /**
+     * A bound of the [−n⋅π … n⋅π] range, which is the valid range of  θ = n⋅λ
 values.
+     * Some (not all) θ values need to be shifted inside that range before to use them
+     * in trigonometric functions.
+     *
+     * @see Initializer#boundOfScaledLongitude(DoubleDouble)
+     */
+    final double θ_bound;
+
+    /**
      * Creates a Lambert projection from the given parameters.
      * The {@code method} argument can be the description of one of the following:
      *
@@ -339,6 +348,7 @@ public class LambertConicConformal extends ConformalProjection {
         normalize  .convertAfter(1, sφ, null);
         denormalize.convertBefore(0, F, null); F.negate();
         denormalize.convertBefore(1, F, rF);
+        θ_bound = initializer.boundOfScaledLongitude(sλ);
     }
 
     /**
@@ -346,7 +356,8 @@ public class LambertConicConformal extends ConformalProjection {
      */
     LambertConicConformal(final LambertConicConformal other) {
         super(other);
-        n = other.n;
+        n       = other.n;
+        θ_bound = other.θ_bound;
     }
 
     /**
@@ -406,8 +417,8 @@ public class LambertConicConformal extends ConformalProjection {
          * the first non-linear one moved to the "normalize" affine transform, and the linear
operations
          * applied after the last non-linear one moved to the "denormalize" affine transform.
          */
-        final double θ    = srcPts[srcOff  ];     // θ = λ⋅n  (ignoring longitude of
origin)
-        final double φ    = srcPts[srcOff+1];     // Sign may be reversed
+        final double θ    = wraparoundScaledLongitude(srcPts[srcOff], θ_bound);     //
θ = Δλ⋅n
+        final double φ    = srcPts[srcOff + 1];                         // Sign may be reversed
         final double absφ = abs(φ);
         final double sinθ = sin(θ);
         final double cosθ = cos(θ);
@@ -490,7 +501,7 @@ public class LambertConicConformal extends ConformalProjection {
      * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
      * @author  André Gosselin (MPO)
      * @author  Rueben Schulz (UBC)
-     * @version 0.6
+     * @version 1.1
      * @since   0.6
      * @module
      */
@@ -498,7 +509,7 @@ public class LambertConicConformal extends ConformalProjection {
         /**
          * For cross-version compatibility.
          */
-        private static final long serialVersionUID = -7005092237343502956L;
+        private static final long serialVersionUID = -8077690516096472987L;
 
         /**
          * Constructs a new map projection from the parameters of the given projection.
@@ -517,8 +528,8 @@ public class LambertConicConformal extends ConformalProjection {
                                 final double[] dstPts, final int dstOff,
                                 final boolean derivate)
         {
-            final double θ    = srcPts[srcOff  ];       // θ = λ⋅n
-            final double φ    = srcPts[srcOff+1];       // Sign may be reversed
+            final double θ    = wraparoundScaledLongitude(srcPts[srcOff], θ_bound);   
 // θ = Δλ⋅n
+            final double φ    = srcPts[srcOff + 1];                         // Sign may
be reversed
             final double absφ = abs(φ);
             final double sinθ = sin(θ);
             final double cosθ = cos(θ);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
index 5d127cc..761fc01 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
@@ -126,7 +126,7 @@ import org.opengis.referencing.ReferenceIdentifier;
  * @author  André Gosselin (MPO)
  * @author  Rueben Schulz (UBC)
  * @author  Rémi Maréchal (Geomatys)
- * @version 0.8
+ * @version 1.1
  *
  * @see ContextualParameters
  * @see <a href="http://mathworld.wolfram.com/MapProjection.html">Map projections on
MathWorld</a>
@@ -647,6 +647,40 @@ public abstract class NormalizedProjection extends AbstractMathTransform2D
imple
     }
 
     /**
+     * If the given scaled longitude θ=n⋅λ is outside the [−n⋅π … n⋅π] range,
maybe shifts θ to that range.
+     * This method intentionally does <strong>not</strong> force θ to be inside
that range in all cases.
+     * We avoid explicit wraparounds as much as possible (as opposed to implicit wraparounds
performed by
+     * trigonometric functions) because they tend to introduce discontinuities. We perform
wraparounds only
+     * when necessary for the problem of area spanning the anti-meridian (±180°).
+     *
+     * <div class="note"><b>Example:</b>
+     * a CRS for Alaska may have the central meridian at λ₀=−154° of longitude. If
the point to project is
+     * at λ=177° of longitude, calculations will be performed with Δλ=331° while the
correct value that we
+     * need to use is Δλ=−29°.</div>
+     *
+     * In order to avoid wraparound operations as much as possible, we test only the bound
where anti-meridian
+     * problem may happen; no wraparound will be applied for the opposite bound. Furthermore
we add or subtract
+     * 360° only once. Even if the point did many turns around the Earth, the 360° shift
will still be applied
+     * at most once. The desire to apply the minimal amount of shifts is the reason why we
do not use
+     * {@link Math#IEEEremainder(double, double)}.
+     *
+     * @param  θ        the scaled longitude value θ=n⋅λ where <var>n</var>
is a projection-dependent factor.
+     * @param  θ_bound  minimal (if negative) or maximal (if positive) value of θ before
to apply the shift.
+     *                  This is computed by <code>{@linkplain Initializer#boundOfScaledLongitude(double)
+     *                  Initializer.boundOfScaledLongitude}(n)</code>
+     * @return θ or shifted θ.
+     *
+     * @see Initializer#boundOfScaledLongitude(double)
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-486">SIS-486</a>
+     */
+    static double wraparoundScaledLongitude(double θ, final double θ_bound) {
+        if (θ_bound < 0 ? θ < θ_bound : θ > θ_bound) {
+            θ -= 2*θ_bound;
+        }
+        return θ;
+    }
+
+    /**
      * Converts a single coordinate in {@code srcPts} at the given offset and stores the
result
      * in {@code dstPts} at the given offset. In addition, opportunistically computes the
      * transform derivative if requested.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
index e4828f8..64adcd0 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
@@ -61,7 +61,7 @@ import static org.apache.sis.internal.referencing.provider.ObliqueStereographic.
  *
  * @author  Rémi Maréchal (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.1
  * @since   0.7
  * @module
  */
@@ -69,7 +69,7 @@ public class ObliqueStereographic extends NormalizedProjection {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = -1454098847621943639L;
+    private static final long serialVersionUID = -1725537881127730658L;
 
     /**
      * Conformal latitude of origin (χ₀), together with its sine and cosine.
@@ -90,7 +90,16 @@ public class ObliqueStereographic extends NormalizedProjection {
      * More precisely <var>g</var> and <var>h</var> are used to compute
intermediate parameters <var>i</var>
      * and <var>j</var>, which are themselves used to compute conformal latitude
and longitude.
      */
-    final double g, h;
+    private final double g, h;
+
+    /**
+     * A bound of the [−n⋅π … n⋅π] range, which is the valid range of  θ = n⋅λ
 values.
+     * Some (not all) θ values need to be shifted inside that range before to use them
+     * in trigonometric functions.
+     *
+     * @see Initializer#boundOfScaledLongitude(DoubleDouble)
+     */
+    private final double θ_bound;
 
     /**
      * Creates an Oblique Stereographic projection from the given parameters.
@@ -173,6 +182,7 @@ public class ObliqueStereographic extends NormalizedProjection {
         final double R2 = 2 * initializer.radiusOfConformalSphere(sinφ0);
         denormalize.convertBefore(0, R2, null);
         denormalize.convertBefore(1, R2, null);
+        θ_bound = initializer.boundOfScaledLongitude(n);
     }
 
     /**
@@ -180,13 +190,14 @@ public class ObliqueStereographic extends NormalizedProjection {
      */
     ObliqueStereographic(final ObliqueStereographic other) {
         super(other);
-        χ0    = other.χ0;
-        sinχ0 = other.sinχ0;
-        cosχ0 = other.cosχ0;
-        c     = other.c;
-        n     = other.n;
-        g     = other.g;
-        h     = other.h;
+        χ0      = other.χ0;
+        sinχ0   = other.sinχ0;
+        cosχ0   = other.cosχ0;
+        c       = other.c;
+        n       = other.n;
+        g       = other.g;
+        h       = other.h;
+        θ_bound = other.θ_bound;
     }
 
     /**
@@ -256,8 +267,9 @@ public class ObliqueStereographic extends NormalizedProjection {
                             final double[] dstPts, final int dstOff,
                             final boolean derivate) throws ProjectionException
     {
-        final double Λ     = srcPts[srcOff  ];      // Λ = λ⋅n  (see below), ignoring
longitude of origin.
-        final double φ     = srcPts[srcOff+1];
+        // Λ = λ⋅n  (see below), ignoring longitude of origin.
+        final double Λ     = wraparoundScaledLongitude(srcPts[srcOff], θ_bound);
+        final double φ     = srcPts[srcOff + 1];
         final double sinφ  = sin(φ);
         final double ℯsinφ = eccentricity * sinφ;
         final double Sa    = (1 +  sinφ) / (1 -  sinφ);
@@ -407,6 +419,7 @@ public class ObliqueStereographic extends NormalizedProjection {
                                 final double[] dstPts, final int dstOff,
                                 final boolean derivate)
         {
+            // No need to enforce [−n⋅π … n⋅π] range here because n=1.
             final double λ = srcPts[srcOff  ];
             final double φ = srcPts[srcOff+1];
             /*
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java
index a941896..430e92f 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java
@@ -69,7 +69,7 @@ public class SatelliteTracking extends NormalizedProjection {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 859940667477896653L;
+    private static final long serialVersionUID = -209787336760184649L;
 
     /**
      * Sines and cosines of inclination between the plane of the Earth's Equator and the
plane
@@ -99,6 +99,15 @@ public class SatelliteTracking extends NormalizedProjection {
     private final boolean isConic;
 
     /**
+     * A bound of the [−n⋅π … n⋅π] range, which is the valid range of  θ = n⋅λ
 values.
+     * Some (not all) θ values need to be shifted inside that range before to use them
+     * in trigonometric functions.
+     *
+     * @see Initializer#boundOfScaledLongitude(DoubleDouble)
+     */
+    private final double θ_bound;
+
+    /**
      * Work around for RFE #4093999 in Sun's bug database ("Relax constraint on
      * placement of this()/super() call in constructors").
      */
@@ -194,6 +203,7 @@ public class SatelliteTracking extends NormalizedProjection {
             normalize  .convertAfter (0,  n,  null);
             denormalize.convertBefore(0, +ρf, null);
             denormalize.convertBefore(1, -ρf, ρ0);
+            θ_bound = initializer.boundOfScaledLongitude(n);
         } else {
             /*
              * Cylindrical projection case. The equations are (ignoring R and λ₀):
@@ -205,7 +215,7 @@ public class SatelliteTracking extends NormalizedProjection {
              * The cosφ₁ (for x at dimension 0) and cosφ₁/F₁′ (for y at dimension
1) factors are computed
              * in advance and stored below. The remaining factor to compute in transform(…)
method is L.
              */
-            n = s0 = Double.NaN;
+            n = s0 = θ_bound = Double.NaN;
             final double cotF = sqrt(cos2_φ1 - cos2_i) / (p2_on_p1*cos2_φ1 - cos_i);  
 // Cotangente of F₁.
             denormalize.convertBefore(0, cosφ1,      null);
             denormalize.convertBefore(1, cosφ1*cotF, null);
@@ -298,6 +308,7 @@ public class SatelliteTracking extends NormalizedProjection {
         double x = srcPts[srcOff];
         double y = λt - p2_on_p1 * λpm;
         if (isConic) {
+            x = wraparoundScaledLongitude(x, θ_bound);
             λpm = n*y + s0;                     // Use this variable for a new purpose.
Needed for derivative.
             if ((Double.doubleToRawLongBits(λpm) ^ Double.doubleToRawLongBits(n)) < 0)
{
                 /*
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
index a30c1cc..db87c4b 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AlbersEqualAreaTest.java
@@ -39,7 +39,7 @@ import static org.junit.Assert.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
- * @version 0.8
+ * @version 1.1
  * @since   0.8
  * @module
  */
@@ -248,4 +248,51 @@ public final strictfp class AlbersEqualAreaTest extends MapProjectionTestCase
{
                        new int[]    {  5,  5},                  // Number of points to test
                        TestUtilities.createRandomNumberGenerator());
     }
+
+    /**
+     * Tests the projection of point where the difference between the given longitude value
and central meridian
+     * is close to 360°. In most map other map projection implementations, we rely on range
reductions performed
+     * automatically by trigonometric functions. However we can not rely on that effect in
the particular case of
+     * {@link AlbersEqualArea} because the longitude is pre-multiplied by a <var>n</var>
factor before to be used
+     * in trigonometric functions. The range reduction must be performed explicitly in map
projection code.
+     *
+     * <p>The math transform tested here is:</p>
+     * {@preformat wkt
+     *   Param_MT["Albers Equal Area",
+     *     Parameter["semi_major", 6378206.4, Unit["metre"]],
+     *     Parameter["semi_minor", 6356583.8, Unit["metre"]],
+     *     Parameter["Latitude of false origin", 50, Unit["degree"]],
+     *     Parameter["Longitude of false origin", -154, Unit["degree"]],
+     *     Parameter["Latitude of 1st standard parallel", 55, Unit["degree"]],
+     *     Parameter["Latitude of 2nd standard parallel", 65, Unit["degree"]]]
+     * }
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-486">SIS-486</a>
+     */
+    @Test
+    public void testLongitudeWraparound() throws FactoryException, TransformException {
+        createCompleteProjection(new org.apache.sis.internal.referencing.provider.AlbersEqualArea(),
+                6378206.4,  // Semi-major axis length
+                6356583.8,  // Semi-minor axis length
+                -154,       // Central meridian
+                50,         // Latitude of origin
+                55,         // Standard parallel 1
+                65,         // Standard parallel 2
+                NaN,        // Scale factor (none)
+                NaN,        // False easting (none)
+                NaN);       // False northing (none)
+
+        tolerance = Formulas.LINEAR_TOLERANCE;
+        /*
+         * Skip inverse transform because the 176.003° become -183.997°. It is not the
purpose
+         * of this test to verify longitude wraparound in inverse projection (we do not expect
+         * such wraparound to be applied).
+         */
+        isInverseTransformSupported = false;
+        verifyTransform(new double[] {176.00296518775082, 52.00158201757688},
+                        new double[] {-2000419.117, 680784.426});
+    }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
index 31155dd..059dfd3 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
@@ -51,7 +51,7 @@ import static org.apache.sis.test.Assert.PENDING_NEXT_GEOAPI_RELEASE;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
- * @version 0.8
+ * @version 1.1
  * @since   0.6
  * @module
  */
@@ -352,9 +352,12 @@ public final strictfp class LambertConicConformalTest extends MapProjectionTestC
     @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[] source = new double[] {
+            70*PI/180, 27*PI/180,
+            30*PI/180, 56*PI/180
+        };
         final double[] target = new double[source.length];
-        transform.transform(source, 0, target, 0, 10);
+        transform.transform(source, 0, target, 0, source.length / 2);
         transform = assertSerializedEquals(transform);
         tolerance = Formulas.LINEAR_TOLERANCE;
         verifyTransform(source, target);
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java
index 65498a5..b30d0cd 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java
@@ -56,7 +56,7 @@ import org.apache.sis.math.DecimalFunctions;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  *
  * @see <a href="http://en.wikipedia.org/wiki/Double-double_%28arithmetic%29#Double-double_arithmetic">Wikipedia:
Double-double arithmetic</a>
  *
@@ -309,6 +309,15 @@ public final class DoubleDouble extends Number {
     }
 
     /**
+     * Returns a new {@code DoubleDouble} instance initialized to the π value.
+     *
+     * @return an instance initialized to the 3.14159265358979323846264338327950 value.
+     */
+    public static DoubleDouble createPi() {
+        return new DoubleDouble(3.14159265358979323846264338327950, 1.2246467991473532E-16);
+    }
+
+    /**
      * Returns a new {@code DoubleDouble} instance initialized to the conversion factor
      * from radians to angular degrees.
      *
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java b/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java
index ec19f87..fe049be 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java
@@ -127,9 +127,9 @@ public final class Longitude extends Angle {
     }
 
     /**
-     * Returns the given longitude value normalized to the [{@linkplain #MIN_VALUE -180}
… {@linkplain #MAX_VALUE 180})°
-     * range (upper value is exclusive). If the given value is outside the longitude range,
then this method adds or
-     * subtracts a multiple of 360° in order to bring back the value to that range.
+     * Returns the given longitude value normalized to the [{@value #MIN_VALUE} … {@value
#MAX_VALUE})°
+     * range (upper value is exclusive). If the given value is outside the longitude range,
then this
+     * method adds or subtracts a multiple of 360° in order to bring back the value to that
range.
      *
      * <p>Special cases:</p>
      * <ul>
diff --git a/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java
b/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java
index 0401fc8..c08fc24 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java
@@ -37,7 +37,7 @@ import static java.lang.StrictMath.*;
  * Those tests need {@link DoubleDouble#DISABLED} to be set to {@code false}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.4
  * @module
  */
@@ -444,9 +444,10 @@ public final strictfp class DoubleDoubleTest extends TestCase {
         for (int i=0; ; i++) {
             final DoubleDouble dd;
             switch (i) {
-                case 0:  dd = DoubleDouble.createRadiansToDegrees(); break;
-                case 1:  dd = DoubleDouble.createDegreesToRadians(); break;
-                case 2:  dd = DoubleDouble.createSecondsToRadians(); break;
+                case 0:  dd = DoubleDouble.createPi();               break;
+                case 1:  dd = DoubleDouble.createRadiansToDegrees(); break;
+                case 2:  dd = DoubleDouble.createDegreesToRadians(); break;
+                case 3:  dd = DoubleDouble.createSecondsToRadians(); break;
                 default: return;                                             // Test done.
             }
             assertEquals(DoubleDouble.errorForWellKnownValue(dd.value), dd.error, STRICT);


Mime
View raw message