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 2013/10/24 23:23:55 UTC
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...
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> > <var>east bound</var>)
- * or (<var>south bound</var> > <var>north bound</var>).
+ * @throws IllegalArgumentException If (<var>south bound</var> > <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> > <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> > <var>east bound</var>)
- * or (<var>south bound</var> > <var>north bound</var>).
+ * @throws IllegalArgumentException If (<var>south bound</var> > <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.
+ }
}