sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1535556 - in /sis/branches/JDK7/core: sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/ sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ sis-metadata/src/test/java/org/apache/sis/test/suite/ sis-referencing/src/m...
Date Thu, 24 Oct 2013 21:23:55 GMT
Author: desruisseaux
Date: Thu Oct 24 21:23:54 2013
New Revision: 1535556

URL: http://svn.apache.org/r1535556
Log:
Partial work for SIS-143: DefaultGeographicBoundingBox shall support spanning of anti-meridian.
This fix allow the box to stores such value, but the operations (union, intersect, area) are not
yet anti-meridian aware.

Added:
    sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBoxTest.java   (with props)
Modified:
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/package-info.java
    sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ExtentsTest.java
    sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java
    sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java
    sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/measure/AngleTest.java

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBox.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -22,10 +22,10 @@ import javax.xml.bind.annotation.XmlType
 import org.opengis.geometry.Envelope;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.measure.Angle;
 import org.apache.sis.measure.Latitude;
 import org.apache.sis.measure.Longitude;
 import org.apache.sis.measure.ValueRange;
+import org.apache.sis.math.MathFunctions;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
@@ -52,6 +52,25 @@ import java.util.Objects;
  *   <li>{@link #intersect(GeographicBoundingBox)} for the intersection between the two bounding boxes.</li>
  * </ul>
  *
+ * {@section Validation and normalization}
+ * All constructors and setter methods in this class perform the following argument validation or normalization:
+ *
+ * <ul>
+ *   <li>If the {@linkplain #getSouthBoundLatitude() south bound latitude} is greater than the
+ *       {@linkplain #getNorthBoundLatitude() north bound latitude}, then an exception is thrown.</li>
+ *   <li>If any latitude is set to a value outside the
+ *       [{@linkplain Latitude#MIN_VALUE -90} … {@linkplain Latitude#MAX_VALUE 90}]° range,
+ *       then that latitude will be clamped.</li>
+ *   <li>If any longitude is set to a value outside the
+ *       [{@linkplain Longitude#MIN_VALUE -180} … {@linkplain Longitude#MAX_VALUE 180}]° range,
+ *       then a multiple of 360° will be added or subtracted to that longitude in order to bring
+ *       it back inside the range.</li>
+ * </ul>
+ *
+ * If the {@linkplain #getWestBoundLongitude() west bound longitude} is greater than the
+ * {@linkplain #getEastBoundLongitude() east bound longitude}, then the box spans the anti-meridian.
+ * See {@linkplain org.apache.sis.geometry.GeneralEnvelope} for more information on anti-meridian spanning.
+ *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Touraïvane (IRD)
  * @author  Cédric Briançon (Geomatys)
@@ -125,8 +144,7 @@ public class DefaultGeographicBoundingBo
      * @param southBoundLatitude The minimal φ value.
      * @param northBoundLatitude The maximal φ value.
      *
-     * @throws IllegalArgumentException If (<var>west bound</var> &gt; <var>east bound</var>)
-     *         or (<var>south bound</var> &gt; <var>north bound</var>).
+     * @throws IllegalArgumentException If (<var>south bound</var> &gt; <var>north bound</var>).
      *         Note that {@linkplain Double#NaN NaN} values are allowed.
      *
      * @see #setBounds(double, double, double, double)
@@ -138,21 +156,12 @@ public class DefaultGeographicBoundingBo
             throws IllegalArgumentException
     {
         super(true);
+        verifyBounds(southBoundLatitude, northBoundLatitude);
         this.westBoundLongitude = westBoundLongitude;
         this.eastBoundLongitude = eastBoundLongitude;
         this.southBoundLatitude = southBoundLatitude;
         this.northBoundLatitude = northBoundLatitude;
-        final Angle min, max;
-        if (westBoundLongitude > eastBoundLongitude) {
-            min = new Longitude(westBoundLongitude);
-            max = new Longitude(eastBoundLongitude);
-        } else if (southBoundLatitude > northBoundLatitude) {
-            min = new Latitude(southBoundLatitude);
-            max = new Latitude(northBoundLatitude);
-        } else {
-            return;
-        }
-        throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalRange_2, min, max));
+        normalize();
     }
 
     /**
@@ -171,6 +180,13 @@ public class DefaultGeographicBoundingBo
             eastBoundLongitude = object.getEastBoundLongitude();
             southBoundLatitude = object.getSouthBoundLatitude();
             northBoundLatitude = object.getNorthBoundLatitude();
+            verifyBounds(southBoundLatitude, northBoundLatitude);
+            normalize();
+        } else {
+            westBoundLongitude = Double.NaN;
+            eastBoundLongitude = Double.NaN;
+            southBoundLatitude = Double.NaN;
+            northBoundLatitude = Double.NaN;
         }
     }
 
@@ -203,7 +219,10 @@ public class DefaultGeographicBoundingBo
      * Returns the western-most coordinate of the limit of the dataset extent.
      * The value is expressed in longitude in decimal degrees (positive east).
      *
-     * @return The western-most longitude between -180 and +180°,
+     * <p>Note that the returned value is greater than the {@linkplain #getEastBoundLongitude()
+     * east bound longitude} if this box is spanning over the anti-meridian.</p>
+     *
+     * @return The western-most longitude between -180° and +180° inclusive,
      *         or {@linkplain Double#NaN NaN} if undefined.
      */
     @Override
@@ -216,12 +235,16 @@ public class DefaultGeographicBoundingBo
     /**
      * Sets the western-most coordinate of the limit of the dataset extent.
      * The value is expressed in longitude in decimal degrees (positive east).
+     * Values outside the [-180 … 180]° range are {@linkplain Longitude#normalize(double) normalized}.
      *
-     * @param newValue The western-most longitude between -180 and +180°,
+     * @param newValue The western-most longitude between -180° and +180° inclusive,
      *        or {@linkplain Double#NaN NaN} to undefine.
      */
-    public void setWestBoundLongitude(final double newValue) {
+    public void setWestBoundLongitude(double newValue) {
         checkWritePermission();
+        if (newValue != Longitude.MAX_VALUE) { // Do not normalize +180° to -180°.
+            newValue = Longitude.normalize(newValue);
+        }
         westBoundLongitude = newValue;
     }
 
@@ -229,7 +252,10 @@ public class DefaultGeographicBoundingBo
      * Returns the eastern-most coordinate of the limit of the dataset extent.
      * The value is expressed in longitude in decimal degrees (positive east).
      *
-     * @return The eastern-most longitude between -180 and +180°,
+     * <p>Note that the returned value is smaller than the {@linkplain #getWestBoundLongitude()
+     * west bound longitude} if this box is spanning over the anti-meridian.</p>
+     *
+     * @return The eastern-most longitude between -180° and +180° inclusive,
      *         or {@linkplain Double#NaN NaN} if undefined.
      */
     @Override
@@ -242,12 +268,16 @@ public class DefaultGeographicBoundingBo
     /**
      * Sets the eastern-most coordinate of the limit of the dataset extent.
      * The value is expressed in longitude in decimal degrees (positive east).
+     * Values outside the [-180 … 180]° range are {@linkplain Longitude#normalize(double) normalized}.
      *
-     * @param newValue The eastern-most longitude between -180 and +180°,
+     * @param newValue The eastern-most longitude between -180° and +180° inclusive,
      *        or {@linkplain Double#NaN NaN} to undefine.
      */
-    public void setEastBoundLongitude(final double newValue) {
+    public void setEastBoundLongitude(double newValue) {
         checkWritePermission();
+        if (newValue != Longitude.MAX_VALUE) { // Do not normalize +180° to -180°.
+            newValue = Longitude.normalize(newValue);
+        }
         eastBoundLongitude = newValue;
     }
 
@@ -255,7 +285,7 @@ public class DefaultGeographicBoundingBo
      * Returns the southern-most coordinate of the limit of the dataset extent.
      * The value is expressed in latitude in decimal degrees (positive north).
      *
-     * @return The southern-most latitude between -90 and +90°,
+     * @return The southern-most latitude between -90° and +90° inclusive,
      *         or {@linkplain Double#NaN NaN} if undefined.
      */
     @Override
@@ -268,20 +298,26 @@ public class DefaultGeographicBoundingBo
     /**
      * Sets the southern-most coordinate of the limit of the dataset extent.
      * The value is expressed in latitude in decimal degrees (positive north).
+     * Values outside the [-90 … 90]° range are {@linkplain Latitude#clamp(double) clamped}.
+     * If the result is greater than the {@linkplain #getNorthBoundLatitude() north bound latitude},
+     * then the north bound is set to {@link Double#NaN}.
      *
-     * @param newValue The southern-most latitude between -90 and +90°,
+     * @param newValue The southern-most latitude between -90° and +90° inclusive,
      *        or {@linkplain Double#NaN NaN} to undefine.
      */
     public void setSouthBoundLatitude(final double newValue) {
         checkWritePermission();
-        southBoundLatitude = newValue;
+        southBoundLatitude = Latitude.clamp(newValue);
+        if (southBoundLatitude > northBoundLatitude) {
+            northBoundLatitude = Double.NaN;
+        }
     }
 
     /**
      * Returns the northern-most, coordinate of the limit of the dataset extent.
      * The value is expressed in latitude in decimal degrees (positive north).
      *
-     * @return The northern-most latitude between -90 and +90°,
+     * @return The northern-most latitude between -90° and +90° inclusive,
      *         or {@linkplain Double#NaN NaN} if undefined.
      */
     @Override
@@ -294,13 +330,81 @@ public class DefaultGeographicBoundingBo
     /**
      * Sets the northern-most, coordinate of the limit of the dataset extent.
      * The value is expressed in latitude in decimal degrees (positive north).
+     * Values outside the [-90 … 90]° range are {@linkplain Latitude#clamp(double) clamped}.
+     * If the result is smaller than the {@linkplain #getSouthBoundLatitude() south bound latitude},
+     * then the south bound is set to {@link Double#NaN}.
      *
-     * @param newValue The northern-most latitude between -90 and +90°,
+     * @param newValue The northern-most latitude between -90° and +90° inclusive,
      *        or {@linkplain Double#NaN NaN} to undefine.
      */
     public void setNorthBoundLatitude(final double newValue) {
         checkWritePermission();
-        northBoundLatitude = newValue;
+        northBoundLatitude = Latitude.clamp(newValue);
+        if (northBoundLatitude < southBoundLatitude) {
+            southBoundLatitude = Double.NaN;
+        }
+    }
+
+    /**
+     * Verifies that the given bounding box is valid. This method verifies only the latitude values,
+     * because we allow the west bound longitude to be greater then east bound longitude (they are
+     * boxes spanning the anti-meridian).
+     *
+     * <p>This method should be invoked <strong>before</strong> {@link #normalize()}.</p>
+     *
+     * @throws IllegalArgumentException If (<var>south bound</var> &gt; <var>north bound</var>).
+     *         Note that {@linkplain Double#NaN NaN} values are allowed.
+     */
+    private static void verifyBounds(final double southBoundLatitude, final double northBoundLatitude)
+            throws IllegalArgumentException
+    {
+        if (southBoundLatitude > northBoundLatitude) { // Accept NaN.
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalRange_2,
+                    new Latitude(southBoundLatitude), new Latitude(northBoundLatitude)));
+        }
+    }
+
+    /**
+     * Clamps the latitudes and normalizes the longitudes.
+     *
+     * @see #denormalize(double, double)
+     */
+    private void normalize() {
+        southBoundLatitude = Latitude.clamp(southBoundLatitude);
+        northBoundLatitude = Latitude.clamp(northBoundLatitude);
+        final double span = eastBoundLongitude - westBoundLongitude;
+        if (!(span >= (Longitude.MAX_VALUE - Longitude.MIN_VALUE))) { // 'span' may be NaN.
+            westBoundLongitude = Longitude.normalize(westBoundLongitude);
+            eastBoundLongitude = Longitude.normalize(eastBoundLongitude);
+            if (span != 0) {
+                /*
+                 * This is the usual case where east and west longitudes are different.
+                 * Since -180° and +180° are equivalent, always use -180° for the west
+                 * bound (this was ensured by the call to Longitude.normalize(…)), and
+                 * always use +180° for the east bound. So we get for example [5 … 180]
+                 * instead of [5 … -180] even if both are the same box.
+                 */
+                if (eastBoundLongitude == Longitude.MIN_VALUE) {
+                    eastBoundLongitude = Longitude.MAX_VALUE;
+                }
+                return;
+            }
+            /*
+             * If the longitude range is anything except [+0 … -0], we are done. Only in the
+             * particular case of [+0 … -0] we will replace the range by [-180 … +180].
+             */
+            if (!MathFunctions.isPositiveZero(westBoundLongitude) ||
+                !MathFunctions.isNegativeZero(eastBoundLongitude))
+            {
+                return;
+            }
+        }
+        /*
+         * If we reach this point, the longitude range is either [+0 … -0] or anything
+         * spanning 360° or more. Normalize the range to [-180 … +180].
+         */
+        westBoundLongitude = Longitude.MIN_VALUE;
+        eastBoundLongitude = Longitude.MAX_VALUE;
     }
 
     /**
@@ -317,8 +421,7 @@ public class DefaultGeographicBoundingBo
      * @param southBoundLatitude The minimal φ value.
      * @param northBoundLatitude The maximal φ value.
      *
-     * @throws IllegalArgumentException If (<var>west bound</var> &gt; <var>east bound</var>)
-     *         or (<var>south bound</var> &gt; <var>north bound</var>).
+     * @throws IllegalArgumentException If (<var>south bound</var> &gt; <var>north bound</var>).
      *         Note that {@linkplain Double#NaN NaN} values are allowed.
      */
     public void setBounds(final double westBoundLongitude,
@@ -328,23 +431,12 @@ public class DefaultGeographicBoundingBo
             throws IllegalArgumentException
     {
         checkWritePermission();
-        final Angle min, max;
-        if (westBoundLongitude > eastBoundLongitude) {
-            min = new Longitude(westBoundLongitude);
-            max = new Longitude(eastBoundLongitude);
-            // Exception will be thrown below.
-        } else if (southBoundLatitude > northBoundLatitude) {
-            min = new Latitude(southBoundLatitude);
-            max = new Latitude(northBoundLatitude);
-            // Exception will be thrown below.
-        } else {
-            this.westBoundLongitude = westBoundLongitude;
-            this.eastBoundLongitude = eastBoundLongitude;
-            this.southBoundLatitude = southBoundLatitude;
-            this.northBoundLatitude = northBoundLatitude;
-            return;
-        }
-        throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalRange_2, min, max));
+        verifyBounds(southBoundLatitude, northBoundLatitude);
+        this.westBoundLongitude = westBoundLongitude;
+        this.eastBoundLongitude = eastBoundLongitude;
+        this.southBoundLatitude = southBoundLatitude;
+        this.northBoundLatitude = northBoundLatitude;
+        normalize();
     }
 
     /**
@@ -353,11 +445,11 @@ public class DefaultGeographicBoundingBo
      * is assumed already in appropriate CRS.
      *
      * <p>When coordinate transformation is required, the target geographic CRS is not necessarily
-     * {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS#WGS84 WGS84}. This method
-     * preserves the same {@linkplain org.opengis.referencing.datum.Ellipsoid ellipsoid} than
-     * in the envelope CRS when possible. This is because geographic bounding box are only
-     * approximative and the ISO specification do not mandates a particular CRS, so we avoid
-     * transformations that are not strictly necessary.</p>
+     * {@linkplain org.apache.sis.referencing.GeodeticObjects#WGS84 WGS84}. This method preserves
+     * the same {@linkplain org.apache.sis.referencing.datum.DefaultEllipsoid ellipsoid} than in
+     * the envelope CRS when possible. This is because geographic bounding box are only approximative
+     * and the ISO specification do not mandates a particular CRS,
+     * so we avoid transformations that are not strictly necessary.</p>
      *
      * <p><b>Note:</b> This method is available only if the referencing module is on the classpath.</p>
      *
@@ -389,6 +481,56 @@ public class DefaultGeographicBoundingBo
     }
 
     /**
+     * Returns ±1 if the given range of longitudes spans the anti-meridian, or 0 otherwise.
+     * If this method returns a non-zero value, then the sign indicates whether the caller
+     * should add 360° to {@code λmin} (+1) or subtract 360° to {@code λmax} (-1).
+     *
+     * @see #normalize()
+     */
+    private int denormalize(final double λmin, final double λmax) {
+        if (!(λmin < λmax) || westBoundLongitude > eastBoundLongitude) {
+            return 0;
+        }
+        /*
+         * If we were computing the union between this bounding box and the other box,
+         * by how much the width would be increased on the left side and on the right
+         * side? (ignore negative values for this first part). What we may get:
+         *
+         *   (+1) Left side is positive            (-1) Right side is positive
+         *
+         *          W┌──────────┐                     ┌──────────┐E
+         *   ──┐  ┌──┼──────────┼──                 ──┼──────────┼──┐  ┌──
+         *     │  │  └──────────┘                     └──────────┘  │  │
+         *   ──┘  └────────────────                 ────────────────┘  └──
+         *       λmin                                              λmax
+         *
+         * For each of the above case, if we apply the translation in the opposite way,
+         * the result would be much wort (for each lower rectangle, imagine translating
+         * the longuest part in the opposite direction instead than the shortest one).
+         *
+         * Note that only one of 'left' and 'right' can be positive, otherwise we would
+         * not be in the case where one box is spanning the anti-meridian while the other
+         * box does not.
+         */
+        final double left  = westBoundLongitude - λmin; if (left  >= 0) return +1;
+        final double right = λmax - eastBoundLongitude; if (right >= 0) return -1;
+        /*
+         * Both 'left' and 'right' are negative. For each alternatives (translating λmin
+         * or translating λmax), we will choose the one which give the closest result to
+         * a bound of this box:
+         *
+         *        W┌──────────┐E         Changes in width of the union compared to this box:
+         *   ───┐  │        ┌─┼──          ∙ if we move λmax to the right:  Δ = (λmax + 360) - E
+         *      │  └────────┼─┘            ∙ if we move λmin to the left:   Δ = W - (λmin + 360)
+         *   ───┘           └────
+         *    λmax         λmin
+         *
+         * We want the smallest option, se we get the condition below after cancelation of both "+ 360" terms.
+         */
+        return (left < right) ? -1 : +1;
+    }
+
+    /**
      * Adds a geographic bounding box to this box. If the {@linkplain #getInclusion() inclusion}
      * status is the same for this box and the box to be added, then the resulting bounding box
      * is the union of the two boxes. If the inclusion status are opposite (<cite>exclusion</cite>),
@@ -408,9 +550,9 @@ public class DefaultGeographicBoundingBo
          * valid metadata object.  If the metadata object is invalid, it is better to get a
          * an exception than having a code doing silently some inappropriate work.
          */
-        if (MetadataUtilities.getInclusion(    getInclusion()) ==
-            MetadataUtilities.getInclusion(box.getInclusion()))
-        {
+        final boolean i1 = MetadataUtilities.getInclusion(this.getInclusion());
+        final boolean i2 = MetadataUtilities.getInclusion(box. getInclusion());
+        if (i1 == i2) {
             if (λmin < westBoundLongitude) westBoundLongitude = λmin;
             if (λmax > eastBoundLongitude) eastBoundLongitude = λmax;
             if (φmin < southBoundLatitude) southBoundLatitude = φmin;
@@ -425,6 +567,7 @@ public class DefaultGeographicBoundingBo
                 if (φmax < northBoundLatitude) northBoundLatitude = φmax;
             }
         }
+        normalize();
     }
 
     /**
@@ -457,6 +600,7 @@ public class DefaultGeographicBoundingBo
         if (southBoundLatitude > northBoundLatitude) {
             southBoundLatitude = northBoundLatitude = 0.5 * (southBoundLatitude + northBoundLatitude);
         }
+        normalize();
     }
 
     /**

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/Extents.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -113,8 +113,11 @@ public final class Extents extends Stati
 
     /**
      * Returns the intersection of the given geographic bounding boxes. If any of the arguments is {@code null},
-     * then this method returns the other argument. If both argument are non-null, then this method returns a
-     * new box which is the intersection of the two given boxes.
+     * then this method returns the other argument (which may be null). Otherwise this method returns a box which
+     * is the intersection of the two given boxes.
+     *
+     * <p>This method never modify the given boxes, but may return directly one of the given arguments if it
+     * already represents the intersection result.</p>
      *
      * @param  b1 The first bounding box, or {@code null}.
      * @param  b2 The second bounding box, or {@code null}.
@@ -129,7 +132,7 @@ public final class Extents extends Stati
      */
     public static GeographicBoundingBox intersection(final GeographicBoundingBox b1, final GeographicBoundingBox b2) {
         if (b1 == null) return b2;
-        if (b2 == null) return b1;
+        if (b2 == null || b2 == b1) return b1;
         final DefaultGeographicBoundingBox box = new DefaultGeographicBoundingBox(b1);
         box.intersect(b2);
         return box;

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/package-info.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/package-info.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/package-info.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -56,13 +56,11 @@
  *       <li>{@link org.apache.sis.metadata.iso.extent.Extents#getGeographicBoundingBox
  *       getGeographicBoundingBox(Extent)}
  *       for extracting a global geographic bounding box.</li>
- *     </ul>
- *     <ul>
+ *
  *       <li>{@link org.apache.sis.metadata.iso.extent.Extents#intersection
  *       intersection(GeographicBoundingBox, GeographicBoundingBox)}
  *       for computing the intersection of two geographic bounding boxes, which may be null.</li>
- *     </ul>
- *     <ul>
+ *
  *       <li>{@link org.apache.sis.metadata.iso.extent.Extents#area
  *       area(GeographicBoundingBox)}
  *       for estimating the area of a geographic bounding box.</li>
@@ -74,19 +72,19 @@
  *       setBounds(double, double, double, double)}
  *       for setting the extent from (λ,φ) values.</li>
  *
- *   <li>{@link org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#setBounds(org.opengis.geometry.Envelope)
+ *       <li>{@link org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#setBounds(org.opengis.geometry.Envelope)
  *       setBounds(Envelope)}
  *       for setting the extent from the given envelope.</li>
  *
- *   <li>{@link org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#setBounds(org.opengis.metadata.extent.GeographicBoundingBox)
+ *       <li>{@link org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#setBounds(org.opengis.metadata.extent.GeographicBoundingBox)
  *       setBounds(GeographicBoundingBox)}
  *       for setting the extent from an other bounding box.</li>
  *
- *   <li>{@link org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#add
+ *       <li>{@link org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#add
  *       add(GeographicBoundingBox)}
  *       for expanding this extent to include an other bounding box.</li>
  *
- *   <li>{@link org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#intersect
+ *       <li>{@link org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox#intersect
  *       intersect(GeographicBoundingBox)}
  *       for the intersection between the two bounding boxes.</li>
  *     </ul>

Added: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBoxTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBoxTest.java?rev=1535556&view=auto
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBoxTest.java (added)
+++ sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBoxTest.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.metadata.iso.extent;
+
+import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link DefaultGeographicBoundingBox}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.4
+ * @version 0.4
+ * @module
+ */
+public final strictfp class DefaultGeographicBoundingBoxTest extends TestCase {
+    /**
+     * Asserts that the given geographic bounding box is strictly equals to the given values.
+     */
+    private static void assertBoxEquals(final double westBoundLongitude,
+                                        final double eastBoundLongitude,
+                                        final double southBoundLatitude,
+                                        final double northBoundLatitude,
+                                        final GeographicBoundingBox box)
+    {
+        assertEquals("inclusion", Boolean.TRUE, box.getInclusion());
+        assertEquals("westBoundLongitude", westBoundLongitude, box.getWestBoundLongitude(), 0);
+        assertEquals("eastBoundLongitude", eastBoundLongitude, box.getEastBoundLongitude(), 0);
+        assertEquals("southBoundLatitude", southBoundLatitude, box.getSouthBoundLatitude(), 0);
+        assertEquals("northBoundLatitude", northBoundLatitude, box.getNorthBoundLatitude(), 0);
+    }
+
+    /**
+     * Tests construction with an invalid range of latitudes.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidLatitudeRange() {
+        new DefaultGeographicBoundingBox(-1, +1, 12, 10);
+    }
+
+    /**
+     * Tests {@link DefaultGeographicBoundingBox#normalize()}. This is also an indirect test of
+     * {@link DefaultGeographicBoundingBox#DefaultGeographicBoundingBox(double, double, double, double)} and
+     * {@link DefaultGeographicBoundingBox#setBounds(double, double, double, double)}, but those later methods
+     * are quite trivial except for the call to {@code normalize()}.
+     */
+    @Test
+    public void testNormalize() {
+        final DefaultGeographicBoundingBox box = new DefaultGeographicBoundingBox(-180, +180, -90, +90);
+        assertBoxEquals(-180, +180, -90, +90, box);
+        /*
+         * Span more than the whole Earth.
+         */
+        box.setBounds  (-200, +200, -100, +100);
+        assertBoxEquals(-180, +180,  -90,  +90, box);
+        /*
+         * Values in a shifted range, but without anti-meridian spanning.
+         */
+        box.setBounds  (380, 420, -8, 2);
+        assertBoxEquals( 20,  60, -8, 2, box);
+        /*
+         * Anti-meridian spanning, without change needed.
+         */
+        box.setBounds  ( 160, -170, -8, 2);
+        assertBoxEquals( 160, -170, -8, 2, box);
+        /*
+         * Anti-meridian spanning in the [0 … 360]° range.
+         */
+        box.setBounds  ( 160,  190, -8, 2);
+        assertBoxEquals( 160, -170, -8, 2, box);
+        /*
+         * Random anti-meridian spanning outside of range.
+         */
+        box.setBounds  (-200, +20, -8, 2);
+        assertBoxEquals( 160, +20, -8, 2, box);
+        /*
+         * Special care for the ±180° longitude bounds.
+         */
+        box.setBounds  (-180,  +20, -8, 2); assertBoxEquals(-180,  +20, -8, 2, box); // Already normalized.
+        box.setBounds  ( +20, +180, -8, 2); assertBoxEquals( +20, +180, -8, 2, box); // Already normalized.
+        box.setBounds  (+180,  +20, -8, 2); assertBoxEquals(-180,  +20, -8, 2, box); // Normalize west bound so we don't need to span anti-meridian.
+        box.setBounds  ( +20, -180, -8, 2); assertBoxEquals( +20, +180, -8, 2, box); // Normalize east bound so we don't need to span anti-meridian.
+        box.setBounds  (-180, -180, -8, 2); assertBoxEquals(-180, -180, -8, 2, box); // Empty box shall stay empty.
+        box.setBounds  (+180, +180, -8, 2); assertBoxEquals(-180, -180, -8, 2, box); // Empty box shall stay empty.
+        /*
+         * Special care for the ±0° longitude bounds.
+         */
+        box.setBounds  (+0.0, +0.0, -8, 2); assertBoxEquals(+0.0, +0.0, -8, 2, box);
+        box.setBounds  (-0.0, +0.0, -8, 2); assertBoxEquals(-0.0, +0.0, -8, 2, box);
+        box.setBounds  (-0.0, -0.0, -8, 2); assertBoxEquals(-0.0, -0.0, -8, 2, box);
+        box.setBounds  (+0.0, -0.0, -8, 2); assertBoxEquals(-180, +180, -8, 2, box);
+    }
+
+
+}

Propchange: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBoxTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/DefaultGeographicBoundingBoxTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ExtentsTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ExtentsTest.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ExtentsTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/extent/ExtentsTest.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -17,6 +17,7 @@
 package org.apache.sis.metadata.iso.extent;
 
 import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
@@ -32,6 +33,7 @@ import static org.junit.Assert.*;
  * @version 0.4
  * @module
  */
+@DependsOn(DefaultGeographicBoundingBoxTest.class)
 public final strictfp class ExtentsTest extends TestCase {
     /**
      * One minute of angle, in degrees.

Modified: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -40,6 +40,7 @@ import org.junit.BeforeClass;
     org.apache.sis.metadata.iso.spatial.DefaultGeorectifiedTest.class,
     org.apache.sis.metadata.iso.maintenance.DefaultScopeDescriptionTest.class,
     org.apache.sis.metadata.iso.quality.AbstractElementTest.class,
+    org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBoxTest.class,
     org.apache.sis.metadata.iso.extent.ExtentsTest.class,
 
     // Classes using Java reflection.

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -448,6 +448,8 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMinimum(int) minimal} ordinate value for dimension 0.
+     *
+     * @return The minimal ordinate value for dimension 0.
      */
     @Override
     public double getMinX() {
@@ -456,6 +458,8 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMinimum(int) minimal} ordinate value for dimension 1.
+     *
+     * @return The minimal ordinate value for dimension 1.
      */
     @Override
     public double getMinY() {
@@ -464,6 +468,8 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMaximum(int) maximal} ordinate value for dimension 0.
+     *
+     * @return The maximal ordinate value for dimension 0.
      */
     @Override
     public double getMaxX() {
@@ -472,6 +478,8 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMaximum(int) maximal} ordinate value for dimension 1.
+     *
+     * @return The maximal ordinate value for dimension 1.
      */
     @Override
     public double getMaxY() {
@@ -480,6 +488,8 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMedian(int) median} ordinate value for dimension 0.
+     *
+     * @return The median ordinate value for dimension 0.
      */
     @Override
     public double getCenterX() {
@@ -488,6 +498,8 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMedian(int) median} ordinate value for dimension 1.
+     *
+     * @return The median ordinate value for dimension 1.
      */
     @Override
     public double getCenterY() {
@@ -496,6 +508,8 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getSpan(int) span} for dimension 0.
+     *
+     * @return The span for dimension 0.
      */
     @Override
     public double getWidth() {
@@ -504,6 +518,8 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getSpan(int) span} for dimension 1.
+     *
+     * @return The span for dimension 1.
      */
     @Override
     public double getHeight() {
@@ -520,6 +536,8 @@ public class Envelope2D extends Rectangl
      * {@link java.lang.Double#NaN NaN}, then the envelope is considered empty.
      * This is different than the default {@link java.awt.geom.Rectangle2D.Double#isEmpty()}
      * implementation, which doesn't check for {@code NaN} values.</p>
+     *
+     * @return {@code true} if this envelope is empty.
      */
     @Override
     public boolean isEmpty() {
@@ -822,8 +840,7 @@ public class Envelope2D extends Rectangl
             double min = Math.max(min0, min1);
             double max = Math.min(max0, max1);
             /*
-             * See GeneralEnvelope.intersect(Envelope) for an explanation of the algorithm applied
-             * below.
+             * See GeneralEnvelope.intersect(Envelope) for an explanation of the algorithm applied below.
              */
             if (isSameSign(span0, span1)) { // Always 'false' if any value is NaN.
                 if ((min1 > max0 || max1 < min0) && !isNegativeUnsafe(span0)) {

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -585,7 +585,7 @@ public class GeneralEnvelope extends Arr
                  * given envelope spans to infinities.
                  */
                 if (max0 <= max1 || min0 >= min1) {
-                    ordinates[iLower]   = min1;
+                    ordinates[iLower] = min1;
                     ordinates[iUpper] = max1;
                     continue;
                 }

Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -18,6 +18,7 @@ package org.apache.sis.geometry;
 
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
+import org.apache.sis.math.MathFunctions;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.DependsOnMethod;
@@ -27,7 +28,6 @@ import org.junit.Test;
 import static java.lang.Double.NaN;
 import static org.opengis.test.Validators.*;
 import static org.apache.sis.referencing.Assert.*;
-import static org.apache.sis.math.MathFunctions.isNegative;
 import static org.apache.sis.geometry.AbstractEnvelopeTest.WGS84;
 
 
@@ -95,7 +95,7 @@ public strictfp class GeneralEnvelopeTes
             final double xLower, final double ymin, final double xUpper, final double ymax)
     {
         final double xmin, xmax;
-        if (isNegative(xUpper - xLower)) { // Check for anti-meridian spanning.
+        if (MathFunctions.isNegative(xUpper - xLower)) { // Check for anti-meridian spanning.
             xmin = -180;
             xmax = +180;
         } else {
@@ -395,8 +395,8 @@ public strictfp class GeneralEnvelopeTes
 
         e = create(0, 10, 360, 20);
         assertTrue(e.normalize());
-        assertEquals("Expect positive zero", Double.doubleToLongBits(+0.0), Double.doubleToLongBits(e.getLower(0)));
-        assertEquals("Expect negative zero", Double.doubleToLongBits(-0.0), Double.doubleToLongBits(e.getUpper(0)));
+        assertTrue("Expect positive zero", MathFunctions.isPositiveZero(e.getLower(0)));
+        assertTrue("Expect negative zero", MathFunctions.isNegativeZero(e.getUpper(0)));
         verifyInvariants(e);
     }
 

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -523,12 +523,36 @@ public final class MathFunctions extends
      *
      * @param  value The value to test.
      * @return {@code true} if the given value is positive, excluding negative zero.
+     *
+     * @see #isPositiveZero(double)
+     * @see #isNegative(double)
      */
     public static boolean isPositive(final double value) {
         return (doubleToRawLongBits(value) & SIGN_BIT_MASK) == 0 && !Double.isNaN(value);
     }
 
     /**
+     * Returns {@code true} if the given value is the positive zero ({@code +0.0}).
+     * This method returns {@code false} for the negative zero ({@code -0.0}).
+     * This method is equivalent to the following code, but potentially faster:
+     *
+     * {@preformat java
+     *   return (value == 0) && isPositive(value);
+     * }
+     *
+     * @param  value The value to test.
+     * @return {@code true} if the given value is +0.0 (not -0.0).
+     *
+     * @see #isPositive(double)
+     * @see #isNegativeZero(double)
+     *
+     * @since 0.4
+     */
+    public static boolean isPositiveZero(final double value) {
+        return doubleToRawLongBits(value) == 0L;
+    }
+
+    /**
      * Returns {@code true} if the given value is negative, <em>including</em> negative zero.
      * Special cases:
      *
@@ -544,12 +568,36 @@ public final class MathFunctions extends
      *
      * @param  value The value to test.
      * @return {@code true} if the given value is negative, including negative zero.
+     *
+     * @see #isNegativeZero(double)
+     * @see #isPositive(double)
      */
     public static boolean isNegative(final double value) {
         return (doubleToRawLongBits(value) & SIGN_BIT_MASK) != 0 && !Double.isNaN(value);
     }
 
     /**
+     * Returns {@code true} if the given value is the negative zero ({@code -0.0}).
+     * This method returns {@code false} for the positive zero ({@code +0.0}).
+     * This method is equivalent to the following code, but potentially faster:
+     *
+     * {@preformat java
+     *   return (value == 0) && isNegative(value);
+     * }
+     *
+     * @param  value The value to test.
+     * @return {@code true} if the given value is -0.0 (not +0.0).
+     *
+     * @see #isNegative(double)
+     * @see #isPositiveZero(double)
+     *
+     * @since 0.4
+     */
+    public static boolean isNegativeZero(final double value) {
+        return doubleToRawLongBits(value) == SIGN_BIT_MASK;
+    }
+
+    /**
      * Returns {@code true} if the given values have the same sign, differentiating positive
      * and negative zeros.
      * Special cases:

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/Latitude.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -25,7 +25,7 @@ import org.apache.sis.util.Immutable;
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3 (derived from geotk-1.0)
- * @version 0.3
+ * @version 0.4
  * @module
  *
  * @see Longitude
@@ -98,4 +98,29 @@ public final class Latitude extends Angl
     final double maximum() {
         return 90;
     }
+
+    /**
+     * Returns the given latitude value clamped to the [{@linkplain #MIN_VALUE -90} … {@linkplain #MAX_VALUE 90}]° range.
+     * If the given value is outside the latitude range, then this method replaces it by ±90° with the same sign than the
+     * given φ value.
+     *
+     * <p>Special cases:</p>
+     * <ul>
+     *   <li>{@linkplain Double#NaN NaN} values are returned unchanged</li>
+     *   <li>±∞ are mapped to ±90° (with the same sign)</li>
+     *   <li>±0 are returned unchanged (i.e. the sign of negative and positive zero is preserved)</li>
+     * </ul>
+     *
+     * @param  φ The latitude value in decimal degrees.
+     * @return The given value clamped to the [-90 … 90]° range, or NaN if the given value was NaN.
+     *
+     * @see Longitude#normalize(double)
+     *
+     * @since 0.4
+     */
+    public static double clamp(final double φ) {
+        if (φ < MIN_VALUE) return MIN_VALUE;
+        if (φ > MAX_VALUE) return MAX_VALUE;
+        return φ;
+    }
 }

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/Longitude.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -25,7 +25,7 @@ import org.apache.sis.util.Immutable;
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3 (derived from geotk-1.0)
- * @version 0.3
+ * @version 0.4
  * @module
  *
  * @see Latitude
@@ -54,6 +54,7 @@ public final class Longitude extends Ang
 
     /**
      * Construct a new longitude with the specified angular value.
+     * This constructor does <strong>not</strong> {@linkplain #normalize(double) normalize} the given value.
      *
      * @param λ Longitude value in decimal degrees.
      */
@@ -89,4 +90,28 @@ public final class Longitude extends Ang
     final char hemisphere(final boolean negative) {
         return negative ? 'W' : 'E';
     }
+
+    /**
+     * 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.
+     *
+     * <p>Special cases:</p>
+     * <ul>
+     *   <li>{@linkplain Double#NaN NaN} values are returned unchanged</li>
+     *   <li>±∞ are mapped to NaN</li>
+     *   <li>±180° are both mapped to -180° (i.e. the range is from -180° inclusive to +180° <em>exclusive</em>)</li>
+     *   <li>±0 are returned unchanged (i.e. the sign of negative and positive zero is preserved)</li>
+     * </ul>
+     *
+     * @param  λ The longitude value in decimal degrees.
+     * @return The given value normalized to the [-180 … 180)° range, or NaN if the given value was NaN of infinite.
+     *
+     * @see Latitude#clamp(double)
+     *
+     * @since 0.4
+     */
+    public static double normalize(final double λ) {
+        return λ - Math.floor((λ - MIN_VALUE) / (MAX_VALUE - MIN_VALUE)) * (MAX_VALUE - MIN_VALUE);
+    }
 }

Modified: sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -80,15 +80,16 @@ public final strictfp class MathFunction
      * Tests {@link MathFunctions#truncate(double)}.
      */
     @Test
+    @DependsOnMethod({"testIsPositiveZero", "testIsNegativeZero"})
     public void testTruncate() {
         assertEquals(+4.0, truncate(+4.9), 0);
         assertEquals(-4.0, truncate(-4.9), 0);
         assertEquals(+0.0, truncate(+0.1), 0);
         assertEquals(-0.0, truncate(-0.1), 0);
-        assertEquals("Positive zero", doubleToLongBits(+0.0), doubleToLongBits(truncate(+0.5)));
-        assertEquals("Negative zero", doubleToLongBits(-0.0), doubleToLongBits(truncate(-0.5)));
-        assertEquals("Positive zero", doubleToLongBits(+0.0), doubleToLongBits(truncate(+0.0)));
-        assertEquals("Negative zero", doubleToLongBits(-0.0), doubleToLongBits(truncate(-0.0)));
+        assertTrue("Positive zero", isPositiveZero(truncate(+0.5)));
+        assertTrue("Negative zero", isNegativeZero(truncate(-0.5)));
+        assertTrue("Positive zero", isPositiveZero(truncate(+0.0)));
+        assertTrue("Negative zero", isNegativeZero(truncate(-0.0)));
     }
 
     /**
@@ -259,6 +260,26 @@ public final strictfp class MathFunction
     }
 
     /**
+     * Tests {@link MathFunctions#isPositiveZero(double)}.
+     */
+    @Test
+    public void testIsPositiveZero() {
+        assertTrue (isPositiveZero(+0.0));
+        assertFalse(isPositiveZero(-0.0));
+        assertFalse(isPositiveZero( NaN));
+    }
+
+    /**
+     * Tests {@link MathFunctions#isNegativeZero(double)}.
+     */
+    @Test
+    public void testIsNegativeZero() {
+        assertTrue (isNegativeZero(-0.0));
+        assertFalse(isNegativeZero(+0.0));
+        assertFalse(isNegativeZero( NaN));
+    }
+
+    /**
      * Tests the {@link MathFunctions#xorSign(double, double)} method.
      */
     @Test

Modified: sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/measure/AngleTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/measure/AngleTest.java?rev=1535556&r1=1535555&r2=1535556&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/measure/AngleTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/measure/AngleTest.java [UTF-8] Thu Oct 24 21:23:54 2013
@@ -21,11 +21,13 @@ import org.apache.sis.test.TestCase;
 import org.apache.sis.test.DependsOn;
 import org.junit.Test;
 
+import static java.lang.Double.NaN;
+import static java.lang.Double.doubleToLongBits;
 import static org.junit.Assert.*;
 
 
 /**
- * Tests the {@link Angle} class.
+ * Tests the {@link Angle}, {@link Longitude} and {@link Latitude} classes.
  *
  * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
  * @since   0.3 (derived from geotk-2.0)
@@ -88,4 +90,38 @@ public final strictfp class AngleTest ex
         assertEquals("N",         String.format(Locale.FRANCE,  "%1.1s", new Latitude(5.51)));
         assertEquals(" ",         String.format(Locale.FRANCE,  "%1.0s", new Latitude(5.51)));
     }
+
+    /**
+     * Tests {@link Latitude#clamp(double)}.
+     */
+    @Test
+    public void testClamp() {
+        assertEquals( 45, Latitude.clamp( 45), 0);
+        assertEquals(-45, Latitude.clamp(-45), 0);
+        assertEquals( 90, Latitude.clamp( 95), 0);
+        assertEquals(-90, Latitude.clamp(-95), 0);
+        assertEquals(NaN, Latitude.clamp(NaN), 0);
+        assertEquals( 90, Latitude.clamp(Double.POSITIVE_INFINITY), 0);
+        assertEquals(-90, Latitude.clamp(Double.NEGATIVE_INFINITY), 0);
+        assertEquals(doubleToLongBits(+0.0), doubleToLongBits(Latitude.clamp(+0.0)));
+        assertEquals(doubleToLongBits(-0.0), doubleToLongBits(Latitude.clamp(-0.0))); // Sign shall be preserved.
+    }
+
+    /**
+     * Tests {@link Longitude#normalize(double)}.
+     */
+    @Test
+    public void testNormalize() {
+        assertEquals( 120, Longitude.normalize( 120), 0);
+        assertEquals(-120, Longitude.normalize(-120), 0);
+        assertEquals(-160, Longitude.normalize( 200), 0);
+        assertEquals( 160, Longitude.normalize(-200), 0);
+        assertEquals(-180, Longitude.normalize(-180), 0);
+        assertEquals(-180, Longitude.normalize( 180), 0); // Upper value shall be exclusive.
+        assertEquals(NaN,  Longitude.normalize( NaN), 0);
+        assertEquals(NaN,  Longitude.normalize(Double.POSITIVE_INFINITY), 0);
+        assertEquals(NaN,  Longitude.normalize(Double.NEGATIVE_INFINITY), 0);
+        assertEquals(doubleToLongBits(+0.0), doubleToLongBits(Longitude.normalize(+0.0)));
+        assertEquals(doubleToLongBits(-0.0), doubleToLongBits(Longitude.normalize(-0.0))); // Sign shall be preserved.
+    }
 }



Mime
View raw message