sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1786708 - in /sis/branches/JDK8/core: sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/ sis-referencing/src/main/java/org/a...
Date Mon, 13 Mar 2017 15:22:48 GMT
Author: desruisseaux
Date: Mon Mar 13 15:22:48 2017
New Revision: 1786708

URL: http://svn.apache.org/viewvc?rev=1786708&view=rev
Log:
Support iterations on MGRS codes in an envelope spanning the anti-meridian.

Modified:
    sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
    sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
    sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/IntervalRectangle.java

Modified: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java?rev=1786708&r1=1786707&r2=1786708&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
[UTF-8] Mon Mar 13 15:22:48 2017
@@ -22,7 +22,6 @@ import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.ConcurrentModificationException;
 import java.util.Iterator;
-import java.awt.Shape;
 import java.awt.geom.Rectangle2D;
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.Envelope;
@@ -60,6 +59,7 @@ import org.apache.sis.util.resources.Err
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.geometry.Shapes2D;
 import org.apache.sis.geometry.Envelopes;
+import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.measure.Longitude;
@@ -592,6 +592,7 @@ public class MilitaryGridReferenceSystem
         /**
          * Returns an iterator over all MGRS references that intersect the given envelope.
          * The given envelope must have a Coordinate Reference System (CRS) associated to
it.
+         * If the CRS is geographic, the envelope is allowed to span the anti-meridian.
          * The MGRS references may be returned in any iteration order.
          *
          * @param  areaOfInterest  envelope of desired MGRS references.
@@ -610,6 +611,7 @@ public class MilitaryGridReferenceSystem
         /**
          * Returns a stream of all MGRS references that intersect the given envelope.
          * The given envelope must have a Coordinate Reference System (CRS) associated to
it.
+         * If the CRS is geographic, the envelope is allowed to span the anti-meridian.
          * The MGRS references may be returned in any order.
          *
          * @param  areaOfInterest  envelope of desired MGRS references.
@@ -680,24 +682,45 @@ public class MilitaryGridReferenceSystem
                     throw new GazetteerException(Errors.format(Errors.Keys.ValueOutOfRange_4,
                             "precision", 1, (int) GRID_SQUARE_SIZE, precision));
                 }
-                final IntervalRectangle aoi = new IntervalRectangle(areaOfInterest);
+                /*
+                 * Convert area of interest (AOI) from an envelope to a Rectangle2D for use
with
+                 * Envelope2D.intersect(Rectangle2D) during IteratorOneZone.advance(…)
execution.
+                 * We need to use the constructor expecting the two corners in order to preserve
+                 * envelope spanning the anti-meridian.
+                 */
+                final IntervalRectangle aoi = new IntervalRectangle(areaOfInterest.getLowerCorner(),
+                                                                    areaOfInterest.getUpperCorner());
+                /*
+                 * Project the area of interest (AOI) to normalized geographic coordinates
for computing UTM zones
+                 * and for IteratorOneZone construction. For computing UTM zones, envelopes
that cross the anti-
+                 * meridian should have a negative width. For IteratorOneZone construction,
it does not matter.
+                 */
                 final Envelope geographicArea = Envelopes.transform(areaOfInterest, datum.normalizedGeographic());
-                final double φmin = geographicArea.getMinimum(1);
-                final double φmax = geographicArea.getMaximum(1);
-                boolean southPole = (φmin < TransverseMercator.Zoner.SOUTH_BOUNDS);
-                boolean northPole = (φmax > TransverseMercator.Zoner.NORTH_BOUNDS);
-                boolean southUTM, northUTM;
-                int zoneStart, zoneEnd;
+                final double φmin   = geographicArea.getMinimum(1);
+                final double φmax   = geographicArea.getMaximum(1);
+                boolean   southPole = (φmin < TransverseMercator.Zoner.SOUTH_BOUNDS);
+                boolean   northPole = (φmax > TransverseMercator.Zoner.NORTH_BOUNDS);
+                boolean   southUTM  = false;
+                boolean   northUTM  = false;                    // Default value for UPS,
to be modified later if UTM.
+                int       zoneStart = 0;                        // Zero-based (i.e. standard
zone number minus 1).
+                int       zoneEnd   = 0;                        // Exclusive.
+                final int zoneCount = ZONER.zoneCount();
                 if (φmax >= TransverseMercator.Zoner.SOUTH_BOUNDS && φmin <
TransverseMercator.Zoner.NORTH_BOUNDS) {
-                    southUTM  = (φmin <  0);
-                    northUTM  = (φmax >= 0);
-                    zoneStart = ZONER.zone(0, geographicArea.getMinimum(0));            
       // Inclusive
-                    zoneEnd   = ZONER.zone(0, geographicArea.getMaximum(0)) + 1;        
       // Exclusive
-                } else {
-                    southUTM  = false;
-                    northUTM  = false;
-                    zoneStart = 1;              // Need a valid zone number, but we will
not iterate on them.
-                    zoneEnd   = 1;
+                    southUTM = (φmin <  0);
+                    northUTM = (φmax >= 0);
+                    if (geographicArea.getSpan(0) > (Longitude.MAX_VALUE - Longitude.MIN_VALUE)
- ZONER.width) {
+                        zoneEnd = zoneCount;
+                    } else {
+                        /*
+                         * Use of lower and upper corners below are not the same than calls
to Envelope.getMinimum(0)
+                         * or Envelope.getMaximum(0) if the envelope crosses the anti-meridian.
+                         */
+                        zoneStart = ZONER.zone(0, geographicArea.getLowerCorner().getOrdinate(0))
- 1;  // Inclusive.
+                        zoneEnd   = ZONER.zone(0, geographicArea.getUpperCorner().getOrdinate(0));
     // Exclusive.
+                        if (zoneEnd < zoneStart) {
+                            zoneEnd += zoneCount;                              // Envelope
crosses the anti-meridian.
+                        }
+                    }
                 }
                 /*
                  * At this point we finished collecting the information that we will need
(whether we will
@@ -713,7 +736,7 @@ public class MilitaryGridReferenceSystem
                 int zone = zoneStart;
                 upper = 0;
                 do {
-                    final double λ = ZONER.centralMeridian(zone);
+                    final double λ = ZONER.centralMeridian((zone % zoneCount) + 1);
                     final double φ;
                     if (southPole) {
                         φ = Latitude.MIN_VALUE;
@@ -836,10 +859,8 @@ public class MilitaryGridReferenceSystem
          * The region for which to return MGRS codes. This envelope can be in any CRS.
          * This shape shall not be modified since the same instance will be shared by
          * many {@code IteratorOneZone}s.
-         *
-         * <p><b>Note:</b> {@link #optimize} must be {@code false} if this
shape is not a rectangle.</p>
          */
-        private final Shape areaOfInterest;
+        private final Rectangle2D areaOfInterest;
 
         /**
          * The transform from the CRS of {@link #encoder} to the CRS of {@link #areaOfInterest}.
@@ -931,14 +952,20 @@ public class MilitaryGridReferenceSystem
         private String pending;
 
         /**
-         * Temporary rectangle for computation purpose.
+         * Temporary rectangle for computation purpose. Needs to be an implementation from
the
+         * {@link org.apache.sis.geometry} in order to support AOI spanning the anti-meridian.
          */
-        private final IntervalRectangle cell = new IntervalRectangle();
+        private final Envelope2D cell = new Envelope2D();
 
         /**
          * Returns a new iterator for creating MGRS codes in a single UTM or UPS zone.
          * The borders of the {@code areaOfInterest} rectangle are considered <strong>exclusive</strong>.
          *
+         * <p>For envelopes that cross the anti-meridian, it does not matter if {@code
geographicArea} uses
+         * the negative width convention or is expanded to the [-180 … 180]° of longitude
range, because it
+         * will be clipped to the projection domain of validity anyway. However the {@code
areaOfInterest}
+         * should use the negative width convention.</p>
+         *
          * @param areaOfInterest  the envelope for which to return MGRS codes. This envelope
can be in any CRS.
          * @param geographicArea  the area of interest transformed into a normalized geographic
CRS.
          * @param sourceCRS       the horizontal part of the {@code areaOfInterest} CRS.
@@ -958,36 +985,39 @@ public class MilitaryGridReferenceSystem
              * (φ,λ₀). We will need to clip the area of interest to those bounds before
to project that area, because
              * the UPS and UTM projections can not cover the whole world.
              */
+            double λmin, λmax, φmin, φmax;
             final int zone = Math.abs(encoder.crsZone);
             if (zone == Encoder.POLE) {
                 xCenter = PolarStereographicA.UPS_SHIFT;
                 if (encoder.crsZone < 0) {
-                    cell.ymin = Latitude.MIN_VALUE;
-                    cell.ymax = TransverseMercator.Zoner.SOUTH_BOUNDS;
+                    φmin = Latitude.MIN_VALUE;
+                    φmax = TransverseMercator.Zoner.SOUTH_BOUNDS;
                 } else {
-                    cell.ymin = TransverseMercator.Zoner.NORTH_BOUNDS;
-                    cell.ymax = Latitude.MAX_VALUE;
+                    φmin = TransverseMercator.Zoner.NORTH_BOUNDS;
+                    φmax = Latitude.MAX_VALUE;
                 }
-                cell.xmin = Longitude.MIN_VALUE;
-                cell.xmax = Longitude.MAX_VALUE;
+                λmin = Longitude.MIN_VALUE;
+                λmax = Longitude.MAX_VALUE;
             } else {
                 xCenter = (int) ZONER.easting;
                 if (encoder.crsZone < 0) {
-                    cell.ymin = TransverseMercator.Zoner.SOUTH_BOUNDS;
+                    φmin = TransverseMercator.Zoner.SOUTH_BOUNDS;
+                    φmax = 0;
                 } else {
-                    cell.ymax = TransverseMercator.Zoner.NORTH_BOUNDS;
+                    φmin = 0;
+                    φmax = TransverseMercator.Zoner.NORTH_BOUNDS;
                 }
                 final double λ0 = ZONER.centralMeridian(zone);
-                cell.xmin = λ0 - ZONER.width;
-                cell.xmax = λ0 + ZONER.width;
+                λmin = λ0 - ZONER.width / 2;
+                λmax = λ0 + ZONER.width / 2;
             }
             double t;
             boolean clip = false;
-            if ((t = geographicArea.getMinimum(1)) >= cell.ymin) cell.ymin = t; else clip
= true;
-            if ((t = geographicArea.getMaximum(1)) <= cell.ymax) cell.ymax = t; else clip
= true;
-            if ((t = geographicArea.getMinimum(0)) >= cell.xmin) cell.xmin = t; else clip
= true;
-            if ((t = geographicArea.getMaximum(0)) <= cell.xmax) cell.xmax = t; else clip
= true;
-            boolean isSpecialCase = ZONER.isSpecialCase(cell.ymin, cell.ymax, cell.xmin,
cell.xmax);
+            if ((t = geographicArea.getMinimum(1)) >= φmin) φmin = t; else clip = true;
+            if ((t = geographicArea.getMaximum(1)) <= φmax) φmax = t; else clip = true;
+            if ((t = geographicArea.getMinimum(0)) >= λmin) λmin = t; else clip = true;
+            if ((t = geographicArea.getMaximum(0)) <= λmax) λmax = t; else clip = true;
+            boolean isSpecialCase = ZONER.isSpecialCase(φmin, φmax, λmin, λmax);
             if (clip) {
                 /*
                  * If we detected that the given area of interest is larger than UPS or UTM
domain of validity
@@ -995,9 +1025,25 @@ public class MilitaryGridReferenceSystem
                  * to the envelope CRS so we can clip it. Here, "domain of validity" is relative
to MGRS grid,
                  * not necessarily to the ISO 19111 domain of validity.
                  */
-                final Rectangle2D bounds = Shapes2D.transform(CRS.findOperation(
-                        geographicArea.getCoordinateReferenceSystem(), sourceCRS, null),
cell, cell);
-                areaOfInterest = bounds.createIntersection(areaOfInterest);
+                final IntervalRectangle r = new IntervalRectangle(λmin, φmin, λmax, φmax);
+                r.setRect(Shapes2D.transform(CRS.findOperation(geographicArea.getCoordinateReferenceSystem(),
sourceCRS, null), r, r));
+                r.intersect(areaOfInterest);
+                /*
+                 * We need an envelope that do not cross the anti-meridian. If the specified
area of interest (AOI)
+                 * crosses the anti-meridian (which should happen only with geographic envelopes),
then the call to
+                 * r.intersect(areaOfInterest) may have overwritten our computation with
the AOI longitude range.
+                 * Overwrite again that range with the domain of validity of current UTM
zone,
+                 * based on the following assumptions:
+                 *
+                 *   1) 'sourceCRS' is geographic (otherwise the AOI should not cross the
anti-meridian).
+                 *   2) the "normalized" AOI longitude range is [-180 … 180]°, in which
case intersection
+                 *      with [λmin … λmax] is [λmin … λmax] itself.
+                 */
+                if (r.xmax < r.xmin) {
+                    r.xmin = λmin;
+                    r.xmax = λmax;
+                }
+                areaOfInterest = r;             // Never cross the anti-meridian, even if
the original AOI did.
             }
             /*
              * At this point, the area of interest has been clipped to the UPS or UTM domain
of validity.
@@ -1056,9 +1102,7 @@ public class MilitaryGridReferenceSystem
             yStart         = 0;                 // This iterator will be for the upper half.
             other.yEnd     = 0;                 // Other iterator will be for the lower half.
             downward       = false;
-            if (!other.downward) {
-                throw new AssertionError();
-            }
+            assert other.downward;              // Fail if the other iterator is not for
lower half.
         }
 
         /**
@@ -1147,7 +1191,8 @@ public class MilitaryGridReferenceSystem
                      * is on the left side.
                      */
                     cell.setRect(gridX, gridY, step, step);
-                    if (areaOfInterest.intersects(Shapes2D.transform(gridToAOI, cell, cell)))
{
+                    cell.setRect(Shapes2D.transform(gridToAOI, cell, cell));
+                    if (cell.intersects(areaOfInterest)) {          // Must be invoked on
Envelope2D implementation.
                         int x = gridX;
                         if (x < xCenter) {
                             // Use the 'x' value closest to projection center (see above
comment for explanation),
@@ -1442,6 +1487,7 @@ public class MilitaryGridReferenceSystem
                     toActualZone = CRS.findOperation(datum.geographic(), datum.universal(φ,
λ), null).getMathTransform();
                     actualZone   = signedZone;
                 }
+                geographic.setOrdinate(1, Longitude.normalize(λ));
                 owner.normalized = position = toActualZone.transform(geographic, owner.normalized);
             }
             /*
@@ -1971,7 +2017,8 @@ parse:                  switch (part) {
                         int zoneError = ZONER.zone(φ, λ) - zone;
                         if (zoneError != 0) {
                             final int zc = ZONER.zoneCount();
-                            if (zoneError > zc/2) zoneError -= zc;
+                            if (zoneError < zc/-2) zoneError += zc;         // If change
of zone crosses the anti-meridian.
+                            if (zoneError > zc/+2) zoneError -= zc;
                             if (ZONER.isSpecialCase(zone, φ)) {
                                 isValid = Math.abs(zoneError) <= 2;         // Tolerance
in zone numbers for high latitudes.
                             } else {

Modified: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java?rev=1786708&r1=1786707&r2=1786708&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
[UTF-8] Mon Mar 13 15:22:48 2017
@@ -36,6 +36,7 @@ import org.opengis.referencing.gazetteer
 import org.opengis.referencing.operation.MathTransform2D;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.referencing.j2d.IntervalRectangle;
+import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.CRS;
@@ -55,7 +56,7 @@ import org.apache.sis.util.Debug;
 @SuppressWarnings("serial")
 public final class LocationViewer extends JPanel {
     /**
-     * Shows the locations tested by {@link MilitaryGridReferenceSystemTest#testIterator()}.
+     * Shows the locations tested by {@link MilitaryGridReferenceSystemTest#testIteratorNorthUTM()}.
      *
      * @param  args  ignored.
      * @throws Exception if an error occurred while transforming an envelope to the display
CRS.
@@ -63,7 +64,40 @@ public final class LocationViewer extend
     public static void main(final String[] args) throws Exception {
         final MilitaryGridReferenceSystem.Coder coder = new MilitaryGridReferenceSystem().createCoder();
         coder.setPrecision(100000);
-        show(coder, new Envelope2D(CommonCRS.defaultGeographic(), 5, 47, 8, 10), CommonCRS.WGS84.universal(1,
9));
+        switch (1) {
+            /*
+             * UTM North: 3 zones (31, 32 and 33) and 3 latitude bands (T, U and V).
+             * Include the Norway special case: in latitude band V, zone 32 is widened at
the expense of zone 31.
+             */
+            case 1: {
+                show(coder, new Envelope2D(CommonCRS.defaultGeographic(), 5, 47, 8, 10),
CommonCRS.WGS84.universal(1, 9));
+                break;
+            }
+            /*
+             * UTM South: 3 zones (31, 32 and 33) and 2 latitude bands (G and H).
+             */
+            case 2: {
+                show(coder, new Envelope2D(CommonCRS.defaultGeographic(), 5, -42, 8, 4),
CommonCRS.WGS84.universal(1, 9));
+                break;
+            }
+            /*
+             * Crossing the anti-meridian. There is two columns of cells: on the west side
and on the east side.
+             */
+            case 3: {
+                final GeneralEnvelope ge = new GeneralEnvelope(CommonCRS.defaultGeographic());
+                ge.setRange(0, 170, -175);
+                ge.setRange(1,  40,   42);
+                show(coder, ge, null);
+                break;
+            }
+            /*
+             * Polar case.
+             */
+            case 4: {
+                show(coder, new Envelope2D(CommonCRS.defaultGeographic(), -180, 80, 360,
10), CommonCRS.WGS84.universal(90, 9));
+                break;
+            }
+        }
     }
 
     /**
@@ -137,7 +171,6 @@ public final class LocationViewer extend
         while (it.hasNext()) {
             final String code = it.next();
             addLocation(code, coder.decode(code));
-System.out.print("\"" + code + "\", ");
         }
         envelope = ((MathTransform2D) CRS.findOperation(areaOfInterest.getCoordinateReferenceSystem(),
displayCRS, null)
                         .getMathTransform()).createTransformedShape(new IntervalRectangle(areaOfInterest));

Modified: sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java?rev=1786708&r1=1786707&r2=1786708&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
[UTF-8] Mon Mar 13 15:22:48 2017
@@ -23,13 +23,16 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Random;
 import java.util.Iterator;
+import java.util.Collections;
 import java.lang.reflect.Field;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
+import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.Envelope2D;
+import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.internal.referencing.provider.TransverseMercator;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.test.DependsOnMethod;
@@ -694,9 +697,28 @@ public final strictfp class MilitaryGrid
     }
 
     /**
+     * Tests iteration spanning the anti-meridian.
+     *
+     * <div class="note"><b>Tip:</b> in case of test failure, see {@link
LocationViewer} as a debugging tool.</div>
+     *
+     * @throws TransformException if an error occurred while computing the coordinate.
+     */
+    @Test
+    public void testIteratorOverAntiMeridian() throws TransformException {
+        final GeneralEnvelope areaOfInterest = new GeneralEnvelope(CommonCRS.defaultGeographic());
+        areaOfInterest.setRange(0, 170, -175);
+        areaOfInterest.setRange(1,  40,  42);
+        testIterator(areaOfInterest, Arrays.asList(
+            "59SME", "59SNE", "59SPE", "59SQE", "60STK", "60SUK", "60SVK", "60SWK", "60SXK",
"60SYK", "1SBE", "1SCE", "1SDE", "1SEE", "1SFE",
+            "59TME", "59TNE", "59TPE", "59TQE", "60TTK", "60TUK", "60TVK", "60TWK", "60TXK",
"60TYK", "1TBE", "1TCE", "1TDE", "1TEE", "1TFE",
+            "59TMF", "59TNF", "59TPF", "59TQF", "60TTL", "60TUL", "60TVL", "60TWL", "60TXL",
"60TYL", "1TBF", "1TCF", "1TDF", "1TEF", "1TFF",
+            "59TMG", "59TNG", "59TPG", "59TQG", "60TTM", "60TUM", "60TVM", "60TWM", "60TXM",
"60TYM", "1TBG", "1TCG", "1TDG", "1TEG", "1TFG"));
+    }
+
+    /**
      * Implementation of {@link #testIteratorUTM()}.
      */
-    private static void testIterator(final Envelope2D areaOfInterest, final List<String>
expected) throws TransformException {
+    private static void testIterator(final Envelope areaOfInterest, final List<String>
expected) throws TransformException {
         final MilitaryGridReferenceSystem.Coder coder = coder();
         coder.setPrecision(100000);
         /*
@@ -713,8 +735,9 @@ public final strictfp class MilitaryGrid
          * Test parallel iteration using stream.
          */
         assertTrue(remaining.addAll(expected));
-        assertEquals("List of expected codes has duplicated values.", expected.size(), remaining.size());
-        coder.encode(areaOfInterest, true).forEach((code) -> assertTrue(code, remaining.remove(code)));
-        assertTrue(remaining.toString(), remaining.isEmpty());
+        final Set<String> sync = Collections.synchronizedSet(remaining);
+        assertEquals("List of expected codes has duplicated values.", expected.size(), sync.size());
+        coder.encode(areaOfInterest, true).forEach((code) -> assertTrue(code, sync.remove(code)));
+        assertTrue(sync.toString(), sync.isEmpty());
     }
 }

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java?rev=1786708&r1=1786707&r2=1786708&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
[UTF-8] Mon Mar 13 15:22:48 2017
@@ -98,13 +98,12 @@ import static org.apache.sis.geometry.Ab
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
  * @since   0.3
- * @version 0.4
+ * @version 0.8
  * @module
  *
  * @see GeneralEnvelope
  * @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox
  */
-@SuppressWarnings("CloneableClassWithoutClone")    // No additional fields compared to parent.
 public class Envelope2D extends Rectangle2D.Double implements Envelope, Emptiable, Cloneable
{
     /**
      * Serial number for inter-operability with different versions.
@@ -284,6 +283,29 @@ public class Envelope2D extends Rectangl
     }
 
     /**
+     * Sets this envelope to the given rectangle. If the given rectangle is also an instance
of {@link Envelope}
+     * (typically as another {@code Envelope2D}) and has a non-null Coordinate Reference
System (CRS), then the
+     * CRS of this envelope will be set to the CRS of the given envelope.
+     *
+     * @param rect  the rectangle to copy coordinates from.
+     *
+     * @since 0.8
+     */
+    @Override
+    public void setRect(final Rectangle2D rect) {
+        if (rect == this) {
+            return;         // Optimization for methods chaining like env.setRect(Shapes.transform(…,
env))
+        }
+        if (rect instanceof Envelope) {
+            final CoordinateReferenceSystem envelopeCRS = ((Envelope) rect).getCoordinateReferenceSystem();
+            if (envelopeCRS != null) {
+                setCoordinateReferenceSystem(envelopeCRS);
+            }
+        }
+        super.setRect(rect);
+    }
+
+    /**
      * Returns the number of dimensions, which is always 2.
      *
      * @return always 2 for bi-dimensional objects.
@@ -444,6 +466,9 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMinimum(int) minimal} ordinate value for dimension 0.
+     * The default implementation invokes <code>{@linkplain #getMinimum(int) getMinimum}(0)</code>.
+     * The result is the standard {@link Rectangle2D} value (namely {@linkplain #x x})
+     * only if the envelope is not spanning the anti-meridian.
      *
      * @return the minimal ordinate value for dimension 0.
      */
@@ -454,6 +479,9 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMinimum(int) minimal} ordinate value for dimension 1.
+     * The default implementation invokes <code>{@linkplain #getMinimum(int) getMinimum}(1)</code>.
+     * The result is the standard {@link Rectangle2D} value (namely {@linkplain #y y})
+     * only if the envelope is not spanning the anti-meridian.
      *
      * @return the minimal ordinate value for dimension 1.
      */
@@ -464,6 +492,9 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMaximum(int) maximal} ordinate value for dimension 0.
+     * The default implementation invokes <code>{@linkplain #getMaximum(int) getMinimum}(0)</code>.
+     * The result is the standard {@link Rectangle2D} value (namely {@linkplain #x x} + {@linkplain
#width width})
+     * only if the envelope is not spanning the anti-meridian.
      *
      * @return the maximal ordinate value for dimension 0.
      */
@@ -474,6 +505,9 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMaximum(int) maximal} ordinate value for dimension 1.
+     * The default implementation invokes <code>{@linkplain #getMaximum(int) getMinimum}(1)</code>.
+     * The result is the standard {@link Rectangle2D} value (namely {@linkplain #y y} + {@linkplain
#height height})
+     * only if the envelope is not spanning the anti-meridian.
      *
      * @return the maximal ordinate value for dimension 1.
      */
@@ -484,6 +518,9 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMedian(int) median} ordinate value for dimension 0.
+     * The default implementation invokes <code>{@linkplain #getMedian(int) getMedian}(0)</code>.
+     * The result is the standard {@link Rectangle2D} value (namely {@linkplain #x x} + {@linkplain
#width width}/2)
+     * only if the envelope is not spanning the anti-meridian.
      *
      * @return the median ordinate value for dimension 0.
      */
@@ -494,6 +531,9 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getMedian(int) median} ordinate value for dimension 1.
+     * The default implementation invokes <code>{@linkplain #getMedian(int) getMedian}(1)</code>.
+     * The result is the standard {@link Rectangle2D} value (namely {@linkplain #y y} + {@linkplain
#height height}/2)
+     * only if the envelope is not spanning the anti-meridian.
      *
      * @return the median ordinate value for dimension 1.
      */
@@ -504,6 +544,9 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getSpan(int) span} for dimension 0.
+     * The default implementation invokes <code>{@linkplain #getSpan(int) getSpan}(0)</code>.
+     * The result is the standard {@link Rectangle2D} value (namely {@linkplain #width width})
+     * only if the envelope is not spanning the anti-meridian.
      *
      * @return the span for dimension 0.
      */
@@ -514,6 +557,9 @@ public class Envelope2D extends Rectangl
 
     /**
      * Returns the {@linkplain #getSpan(int) span} for dimension 1.
+     * The default implementation invokes <code>{@linkplain #getSpan(int) getSpan}(1)</code>.
+     * The result is the standard {@link Rectangle2D} value (namely {@linkplain #height height})
+     * only if the envelope is not spanning the anti-meridian.
      *
      * @return the span for dimension 1.
      */
@@ -756,7 +802,7 @@ public class Envelope2D extends Rectangl
             final Envelope2D env = (Envelope2D) rect;
             return intersects(env.x, env.y, env.width, env.height);
         }
-        return super.contains(rect);
+        return super.intersects(rect);
     }
 
     /**
@@ -885,7 +931,7 @@ public class Envelope2D extends Rectangl
      */
     @Override
     public Envelope2D createUnion(final Rectangle2D rect) {
-        final Envelope2D union = (Envelope2D) clone();
+        final Envelope2D union = clone();
         union.add(rect);
         assert union.isEmpty() || (union.contains(this) && union.contains(rect))
: union;
         return union;
@@ -1090,6 +1136,16 @@ public class Envelope2D extends Rectangl
     }
 
     /**
+     * Returns a clone of this envelope.
+     *
+     * @return a clone of this envelope.
+     */
+    @Override
+    public Envelope2D clone() {
+        return (Envelope2D) super.clone();
+    }
+
+    /**
      * Formats this envelope as a "{@code BOX}" element.
      * The output is of the form "{@code BOX(}{@linkplain #getLowerCorner()
      * lower corner}{@code ,}{@linkplain #getUpperCorner() upper corner}{@code )}".

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java?rev=1786708&r1=1786707&r2=1786708&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
[UTF-8] Mon Mar 13 15:22:48 2017
@@ -391,6 +391,9 @@ public class GeneralEnvelope extends Arr
      *         the expected number of dimensions.
      */
     public void setEnvelope(final Envelope envelope) throws MismatchedDimensionException
{
+        if (envelope == this) {
+            return;     // Optimization for methods chaining like env.setEnvelope(Envelopes.transform(env,
crs))
+        }
         ensureNonNull("envelope", envelope);
         final int beginIndex = beginIndex();
         final int dimension = endIndex() - beginIndex;

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/IntervalRectangle.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/IntervalRectangle.java?rev=1786708&r1=1786707&r2=1786708&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/IntervalRectangle.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/IntervalRectangle.java
[UTF-8] Mon Mar 13 15:22:48 2017
@@ -18,6 +18,8 @@ package org.apache.sis.internal.referenc
 
 import java.awt.geom.Rectangle2D;
 import org.opengis.geometry.Envelope;
+import org.opengis.geometry.DirectPosition;
+import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.util.Classes;
 
 
@@ -25,7 +27,7 @@ import org.apache.sis.util.Classes;
  * Rectangle defines by intervals instead than by a size.
  * Instead of ({@code x}, {@code y}, {@code width}, {@code height}) values as do standard
Java2D implementations,
  * this class contains ({@link #xmin}, {@link #xmax}, {@link #ymin}, {@link #ymax}) values.
This choice provides
- * two benefits:
+ * three benefits:
  *
  * <ul>
  *   <li>Allows this class to work correctly with {@linkplain java.lang.Double#isInfinite()
infinite} and
@@ -33,8 +35,17 @@ import org.apache.sis.util.Classes;
  *       alternative is ambiguous.</li>
  *   <li>Slightly faster {@code contains(…)} and {@code intersects(…)} methods
since there is no addition or
  *       subtraction to perform.</li>
+ *   <li>Better inter-operability with {@link Envelope2D} when a rectangle spans the
anti-meridian.
+ *       This {@code IntervalRectangle} class does not support such envelopes by itself,
but it is
+ *       okay to create a rectangle with negative width and gives it in argument to
+ *       {@link Envelope2D#contains(Rectangle2D)} or {@link Envelope2D#intersects(Rectangle2D)}
methods.</li>
  * </ul>
  *
+ * This class does <strong>not</strong> support by itself rectangles spanning
the anti-meridian of a geographic CRS.
+ * However the {@link #getX()}, {@link #getY()}, {@link #getWidth()} and {@link #getHeight()}
methods are defined in
+ * the straightforward way expected by {@link Envelope2D#intersects(Rectangle2D)} and similar
methods for computing
+ * correct result if the given {@code Rectangle2D} crosses the anti-meridian.
+ *
  * <div class="note"><b>Internal usage of inheritance:</b>
  * this class may also be opportunistically extended by some Apache SIS internal classes
that need a rectangle in
  * addition of their own information. All {@code Rectangle2D} methods are declared final
for reducing the risk of
@@ -61,6 +72,19 @@ public class IntervalRectangle extends R
 
     /**
      * Constructs a rectangle initialized to the two first dimensions of the given envelope.
+     * If the given envelope crosses the anti-meridian, then the new rectangle will span
the
+     * full longitude range (i.e. this constructor does not preserve the convention of using
+     * negative width for envelopes crossing anti-meridian).
+     *
+     * <div class="note"><b>Note:</b> this constructor expands envelopes
that cross the anti-meridian
+     * because the methods defined in this class are not designed for handling such envelopes.
+     * If a rectangle with negative width is nevertheless desired for envelope spanning the
anti-meridian,
+     * one can use the following constructor:
+     *
+     * {@preformat java
+     *     new IntervalRectangle(envelope.getLowerCorner(), envelope.getUpperCorner());
+     * }
+     * </div>
      *
      * @param envelope  the envelope from which to copy the values.
      */
@@ -72,6 +96,27 @@ public class IntervalRectangle extends R
     }
 
     /**
+     * Constructs a rectangle initialized to the two first dimensions of the given corners.
+     * This constructor unconditionally assigns {@code lower} ordinates to {@link #xmin},
{@link #ymin} and
+     * {@code upper} ordinates to {@link #xmax}, {@link #ymax} regardless of their values;
this constructor
+     * does not verify if {@code lower} ordinates are smaller than {@code upper} ordinates.
+     * This is sometime useful for creating a rectangle spanning the anti-meridian,
+     * even if {@code IntervalRectangle} class does not support such rectangles by itself.
+     *
+     * @param lower  the limits in the direction of decreasing ordinate values for each dimension.
+     * @param upper  the limits in the direction of increasing ordinate values for each dimension.
+     *
+     * @see Envelope#getLowerCorner()
+     * @see Envelope#getUpperCorner()
+     */
+    public IntervalRectangle(final DirectPosition lower, final DirectPosition upper) {
+        xmin = lower.getOrdinate(0);
+        xmax = upper.getOrdinate(0);
+        ymin = lower.getOrdinate(1);
+        ymax = upper.getOrdinate(1);
+    }
+
+    /**
      * Creates a rectangle using maximal <var>x</var> and <var>y</var>
values rather than width and height.
      * This constructor avoid the problem of NaN values when extremum are infinite numbers.
      *
@@ -119,7 +164,9 @@ public class IntervalRectangle extends R
     }
 
     /**
-     * Returns the width of the rectangle.
+     * Returns the width of the rectangle. May be negative if the rectangle crosses the anti-meridian.
+     * This {@code IntervalRectangle} class does not support such envelopes itself, but other
classes
+     * like {@link Envelope2D} will handle correctly the negative width.
      *
      * @return the width of the rectangle.
      */
@@ -221,10 +268,12 @@ public class IntervalRectangle extends R
      */
     @Override
     public final void setRect(final Rectangle2D r) {
-        xmin = r.getMinX();
-        ymin = r.getMinY();
-        xmax = r.getMaxX();
-        ymax = r.getMaxY();
+        if (r != this) {        // Optimization for methods chaining like r.setRect(Shapes.transform(…,
r))
+            xmin = r.getMinX();
+            ymin = r.getMinY();
+            xmax = r.getMaxX();
+            ymax = r.getMaxY();
+        }
     }
 
     /**
@@ -358,6 +407,30 @@ public class IntervalRectangle extends R
     }
 
     /**
+     * Intersects a {@link Rectangle2D} object with this rectangle.
+     * The resulting* rectangle is the intersection of the two {@code Rectangle2D} objects.
+     * Invoking this method is equivalent to invoking the following code, except that this
+     * method behaves correctly with infinite values and {@link Envelope2D} implementation.
+     *
+     * {@preformat java
+     *     Rectangle2D.intersect(this, rect, this);
+     * }
+     *
+     * @param  rect  the {@code Rectangle2D} to intersect with this rectangle.
+     *
+     * @see #intersect(Rectangle2D, Rectangle2D, Rectangle2D)
+     * @see #createIntersection(Rectangle2D)
+     */
+    public final void intersect(final Rectangle2D rect) {
+        double t;
+        // Must use getMin/Max methods, not getX/Y/Width/Height, for inter-operability with
Envelope2D.
+        if ((t = rect.getMinX()) > xmin) xmin = t;
+        if ((t = rect.getMaxX()) < xmax) xmax = t;
+        if ((t = rect.getMinY()) > ymin) ymin = t;
+        if ((t = rect.getMaxY()) < ymax) ymax = t;
+    }
+
+    /**
      * Returns a new {@code Rectangle2D} object representing the intersection of this rectangle
with the specified one.
      *
      * @param  rect  the {@code Rectangle2D} to be intersected with this rectangle.



Mime
View raw message