sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1786765 [1/2] - in /sis/trunk: ./ core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/ core/sis-referencing/src/main...
Date Mon, 13 Mar 2017 19:14:25 GMT
Author: desruisseaux
Date: Mon Mar 13 19:14:25 2017
New Revision: 1786765

URL: http://svn.apache.org/viewvc?rev=1786765&view=rev
Log:
Merge the completion of MGRS support from JDK7 branch.

Added:
    sis/trunk/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
      - copied, changed from r1786763, sis/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/IntervalRectangle.java
      - copied unchanged from r1786763, sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/IntervalRectangle.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/Spliterators.java
      - copied unchanged from r1786763, sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/Spliterators.java
Modified:
    sis/trunk/   (props changed)
    sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
    sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
    sis/trunk/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/Spliterator.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java

Propchange: sis/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Mon Mar 13 19:14:25 2017
@@ -1,5 +1,5 @@
 /sis/branches/Android:1430670-1480699
 /sis/branches/JDK6:1394364-1758914
-/sis/branches/JDK7:1394913-1785895
-/sis/branches/JDK8:1584960-1785861
+/sis/branches/JDK7:1394913-1786763
+/sis/branches/JDK8:1584960-1786756
 /sis/branches/JDK9:1773327-1773512

Modified: sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java?rev=1786765&r1=1786764&r2=1786765&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java [UTF-8] Mon Mar 13 19:14:25 2017
@@ -21,12 +21,18 @@ import java.util.Map;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.awt.geom.Rectangle2D;
 import org.opengis.util.FactoryException;
+import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.ProjectedCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.MathTransform2D;
 import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.Projection;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.referencing.provider.TransverseMercator;
@@ -36,8 +42,9 @@ import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.NamedIdentifier;
 import org.apache.sis.referencing.IdentifiedObjects;
-import org.apache.sis.referencing.crs.DefaultProjectedCRS;
 import org.apache.sis.referencing.cs.AxesConvention;
+import org.apache.sis.referencing.crs.DefaultProjectedCRS;
+import org.apache.sis.internal.referencing.j2d.IntervalRectangle;
 import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.util.CharSequences;
@@ -45,14 +52,23 @@ import org.apache.sis.util.ArgumentCheck
 import org.apache.sis.util.StringBuilders;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Workaround;
+import org.apache.sis.util.Debug;
 import org.apache.sis.util.resources.Errors;
 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.measure.Longitude;
 import org.apache.sis.measure.Latitude;
 
 // Branch-dependent imports
 import org.apache.sis.internal.jdk8.JDK8;
+import org.apache.sis.internal.jdk8.Spliterator;
+import org.apache.sis.internal.jdk8.Spliterators;
+import org.apache.sis.internal.jdk8.Stream;
+import org.apache.sis.internal.jdk8.Consumer;
+import org.apache.sis.internal.jdk8.StreamSupport;
 import org.apache.sis.metadata.iso.citation.AbstractParty;
 
 
@@ -359,6 +375,13 @@ public class MilitaryGridReferenceSystem
         private String trimmedSeparator;
 
         /**
+         * Whether the decoded locations should be clipped to the valid area of MGRS cell.
+         *
+         * @see #getClipToValidArea()
+         */
+        private boolean clipToValidArea;
+
+        /**
          * Cached information needed for building a MGRS reference from a direct position in the given CRS.
          */
         private final Map<CoordinateReferenceSystem,Encoder> encoders;
@@ -372,16 +395,30 @@ public class MilitaryGridReferenceSystem
         /**
          * A buffer where to create reference, to be reused for each new reference.
          */
-        final StringBuilder buffer;
+        final StringBuilder buffer = new StringBuilder(18);      // Length of "4 Q FJ 12345 67890" sample value.
 
         /**
-         * Creates a new coder initialized to the default precision.
+         * Creates a new coder initialized to the default precision and separator.
          */
         protected Coder() {
-            digits     = METRE_PRECISION_DIGITS;     // 1 metre precision.
-            separator  = trimmedSeparator = "";
-            buffer     = new StringBuilder(18);      // Length of "4 Q FJ 12345 67890" sample value.
-            encoders   = new IdentityHashMap<>();
+            digits    = METRE_PRECISION_DIGITS;     // 1 metre precision.
+            separator = trimmedSeparator = "";
+            encoders  = new IdentityHashMap<>();
+            clipToValidArea = true;
+        }
+
+        /**
+         * Creates a new coder initialized to the same setting than the given separator.
+         * The new instance will share the same {@link #encoders} map than the original instance.
+         * This is okay only if all calls to {@link #encoder(CoordinateReferenceSystem)} are done
+         * in the same thread before any call to {@link IteratorAllZones#trySplit()}.
+         */
+        Coder(final Coder other) {
+            digits           = other.digits;
+            separator        = other.separator;
+            trimmedSeparator = other.trimmedSeparator;
+            clipToValidArea  = other.clipToValidArea;
+            encoders         = other.encoders;
         }
 
         /**
@@ -435,6 +472,13 @@ public class MilitaryGridReferenceSystem
         }
 
         /**
+         * For internal use by other internal classes in this Java source file.
+         */
+        final int digits() {
+            return digits;
+        }
+
+        /**
          * Returns the separator to insert between each component of the MGRS identifier.
          * Components are zone number, latitude band, 100 000-metres square identifier and numerical values.
          * By default the separator is an empty string, which produce references like "4QFJ12345678".
@@ -464,6 +508,29 @@ public class MilitaryGridReferenceSystem
         }
 
         /**
+         * Returns whether the decoded locations should be clipped to the valid area.
+         * The default value is {@code true}.
+         *
+         * @return {@code true} if decoded locations are clipped to the valid area.
+         */
+        public boolean getClipToValidArea() {
+            return clipToValidArea;
+        }
+
+        /**
+         * Sets whether the decoded locations should be clipped to the valid area.
+         * MGRS 100 km squares can actually be smaller than 100 km when the square overlaps two UTM zones or
+         * two latitude bands. We may have half of a square in a zone and the other half in the other zone.
+         * By default, the {@link #decode(CharSequence)} method clips the square to the zone where it belongs.
+         * Invoking this method with the {@code false} value disables this behavior.
+         *
+         * @param clip  whether the decoded locations should be clipped to the valid area.
+         */
+        public void setClipToValidArea(final boolean clip) {
+            clipToValidArea = clip;
+        }
+
+        /**
          * Bridge to {@link MilitaryGridReferenceSystem#datum}
          * for the {@link Encoder} and {@link Decoder} classes.
          */
@@ -473,12 +540,13 @@ public class MilitaryGridReferenceSystem
 
         /**
          * Returns the encoder for the given coordinate reference system.
+         * All calls to this method must be done in the same thread.
          *
          * @throws IllegalArgumentException if the given CRS do not use one of the supported datums.
          * @throws FactoryException if the creation of a coordinate operation failed.
          * @throws TransformException if the creation of an inverse operation failed.
          */
-        private Encoder encoder(final CoordinateReferenceSystem crs) throws FactoryException, TransformException {
+        final Encoder encoder(final CoordinateReferenceSystem crs) throws FactoryException, TransformException {
             if (crs == null) {
                 throw new GazetteerException(Errors.format(Errors.Keys.UnspecifiedCRS));
             }
@@ -504,13 +572,67 @@ public class MilitaryGridReferenceSystem
         public String encode(final DirectPosition position) throws TransformException {
             ArgumentChecks.ensureNonNull("position", position);
             try {
-                return encoder(position.getCoordinateReferenceSystem()).encode(this, position, separator, digits);
+                return encoder(position.getCoordinateReferenceSystem()).encode(this, position, true, getSeparator(), digits());
             } catch (IllegalArgumentException | FactoryException e) {
                 throw new GazetteerException(e.getLocalizedMessage(), e);
             }
         }
 
         /**
+         * 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.
+         *
+         * <div class="note"><b>Possible evolution:</b>
+         * current implementation does not clip the cells to UPS/UTM valid areas before to test for intersection
+         * with {@code areaOfInterest}. Consequently the iterator may return slightly more cells than expected.
+         * A future version may filter the cells more accurately. If an application needs the same set of cells
+         * than what current the implementation returns, it can invoke <code>{@linkplain #setClipToValidArea
+         * setClipToValidArea}(false)</code> for preserving current behavior in future Apache SIS versions.</div>
+         *
+         * @param  areaOfInterest  envelope of desired MGRS references.
+         * @return an iterator over MGRS references intersecting the given area of interest.
+         * @throws TransformException if an error occurred while transforming the area of interest.
+         */
+        public Iterator<String> encode(final Envelope areaOfInterest) throws TransformException {
+            ArgumentChecks.ensureNonNull("areaOfInterest", areaOfInterest);
+            try {
+                return Spliterators.iterator(new IteratorAllZones(areaOfInterest).simplify());
+            } catch (IllegalArgumentException | FactoryException e) {
+                throw new GazetteerException(e);
+            }
+        }
+
+        /**
+         * 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.
+         *
+         * <div class="note"><b>Possible evolution:</b>
+         * current implementation does not clip the cells to UPS/UTM valid areas before to test for intersection
+         * with {@code areaOfInterest}. Consequently the iterator may return slightly more cells than expected.
+         * A future version may filter the cells more accurately. If an application needs the same set of cells
+         * than what current the implementation returns, it can invoke <code>{@linkplain #setClipToValidArea
+         * setClipToValidArea}(false)</code> for preserving current behavior in future Apache SIS versions.</div>
+         *
+         * @param  areaOfInterest  envelope of desired MGRS references.
+         * @param  parallel        {@code true} for a parallel stream, or {@code false} for a sequential stream.
+         * @return a stream of MGRS references intersecting the given area of interest.
+         * @throws TransformException if an error occurred while transforming the area of interest.
+         */
+        // Public on the JDK8 branch only.
+        final Stream<String> encode(final Envelope areaOfInterest, final boolean parallel) throws TransformException {
+            ArgumentChecks.ensureNonNull("areaOfInterest", areaOfInterest);
+            try {
+                return StreamSupport.stream(new IteratorAllZones(areaOfInterest).simplify(), parallel);
+            } catch (IllegalArgumentException | FactoryException e) {
+                throw new GazetteerException(e);
+            }
+        }
+
+        /**
          * Decodes the given MGRS reference into a position and an envelope.
          * The Coordinate Reference System (CRS) associated to the returned position depends on the given reference.
          *
@@ -527,6 +649,637 @@ public class MilitaryGridReferenceSystem
             ArgumentChecks.ensureNonNull("reference", reference);
             return new Decoder(this, reference);
         }
+
+
+
+        /**
+         * Iterator over the cells inside all UPS and UTM zones inside a given area of interest.
+         * Each UPS or UTM zone is processed by a separated iterator, each of them with its own
+         * {@link Encoder} instance.
+         *
+         * @see IteratorOneZone
+         */
+        private final class IteratorAllZones implements Spliterator<String> {
+            /**
+             * The iterators over a single UTM zone.
+             */
+            private final Spliterator<String>[] iterators;
+
+            /**
+             * Index of the current iterator.
+             */
+            private int index;
+
+            /**
+             * Index after the last iterator to return.
+             */
+            private int upper;
+
+            /**
+             * Creates a new iterator over MGRS cells in the given area of interest.
+             * The borders of the given envelope are considered <strong>exclusive</strong>.
+             */
+            @SuppressWarnings({"unchecked", "rawtypes"})        // Generic array creation.
+            IteratorAllZones(final Envelope areaOfInterest) throws FactoryException, TransformException {
+                final SingleCRS sourceCRS = CRS.getHorizontalComponent(areaOfInterest.getCoordinateReferenceSystem());
+                if (sourceCRS == null) {
+                    throw new GazetteerException(org.apache.sis.internal.referencing.Resources.format(
+                            org.apache.sis.internal.referencing.Resources.Keys.NonHorizontalCRS_1, "areaOfInterest"));
+                }
+                final int precision = (int) getPrecision();
+                if (precision <= 0 || precision > (int) GRID_SQUARE_SIZE) {
+                    throw new GazetteerException(Errors.format(Errors.Keys.ValueOutOfRange_4,
+                            "precision", 1, (int) GRID_SQUARE_SIZE, precision));
+                }
+                /*
+                 * 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  = 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);
+                    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
+                 * iterate over the north or south pole, the range of UTM zones, etc). For each UPS or UTM
+                 * zone, we pick a representative φ value for the purpose of CommonCRS.universal(…) method
+                 * call and we prepare an iterator on which we will delegate computations.
+                 */
+                upper = zoneEnd - zoneStart;
+                if (southUTM & northUTM) upper *= 2;
+                if (southPole)           upper += 2;
+                if (northPole)           upper += 2;
+                iterators = new Spliterator[upper];
+                int zone = zoneStart;
+                upper = 0;
+                do {
+                    final double λ = ZONER.centralMeridian((zone % zoneCount) + 1);
+                    final double φ;
+                    if (southPole) {
+                        φ = Latitude.MIN_VALUE;
+                        southPole = false;              // For next iteration.
+                    } else if (southUTM) {
+                        φ = -1;
+                        if (++zone >= zoneEnd) {
+                            zone = zoneStart;
+                            southUTM = false;           // For next iteration.
+                        }
+                    } else if (northUTM) {
+                        φ = +1;
+                        if (++zone >= zoneEnd) {
+                            northUTM = false;           // For next iteration.
+                        }
+                    } else if (northPole) {
+                        φ = Latitude.MAX_VALUE;
+                        northPole = false;              // For loop termination.
+                    } else {
+                        throw new AssertionError();
+                    }
+                    final ProjectedCRS targetCRS = datum.universal(φ, λ);
+                    Spliterator<String> iter = new IteratorOneZone(Coder.this, aoi, geographicArea, sourceCRS, targetCRS, precision);
+                    do iterators[upper++] = iter;
+                    while ((iter = iter.trySplit()) != null);
+                } while (southPole | northPole | southUTM | northUTM);
+            }
+
+            /**
+             * Creates an iterator over the first half of the zones covered by the given iterator.
+             * After construction, the given iterator will cover the second half. This constructor
+             * is for {@link #trySplit()} method only.
+             */
+            private IteratorAllZones(final IteratorAllZones half) {
+                iterators  =  half.iterators;
+                index      =  half.index;
+                upper      = (half.upper + index) / 2;
+                half.index = upper;
+            }
+
+            /**
+             * If this iterator can be partitioned, returns an iterator covering approximatively
+             * the first half of MGRS references and update this iterator for covering the other
+             * half. Each iterator will use a disjoint set of projected CRS.
+             */
+            @Override
+            public Spliterator<String> trySplit() {
+                return (upper - index >= 2) ? new IteratorAllZones(this).simplify() : null;
+            }
+
+            /**
+             * If this iterator is backed by only one worker iterator, returns that worker iterator.
+             * Otherwise returns {@code this}. This method should be invoked after construction.
+             */
+            final Spliterator<String> simplify() {
+                return (upper - index == 1) ? iterators[index] : this;
+            }
+
+            /**
+             * Guess the number of elements to be returned. The value returned by this method is very approximative,
+             * and likely greater than the real amount of elements that will actually be returned.
+             */
+            @Override
+            public long estimateSize() {
+                long n = 0;
+                for (int i=index; i<upper; i++) {
+                    n += iterators[i].estimateSize();
+                }
+                return n;
+            }
+
+            /**
+             * Performs the given action on the remaining MGRS reference, if any.
+             */
+            @Override
+            public boolean tryAdvance(final Consumer<? super String> action) {
+                while (index < upper) {
+                    if (iterators[index].tryAdvance(action)) {
+                        return true;
+                    }
+                    index++;
+                }
+                return false;
+            }
+
+            /**
+             * Performs the given action on all remaining MGRS references.
+             */
+            @Override
+            public void forEachRemaining(final Consumer<? super String> action) {
+                while (index < upper) {
+                    iterators[index++].forEachRemaining(action);
+                }
+            }
+
+            /**
+             * Specifies that the list of elements is immutable, that all elements will be distinct
+             * and that this iterator never return {@code null} element.
+             */
+            @Override
+            public int characteristics() {
+                return IMMUTABLE | DISTINCT | NONNULL;
+            }
+        }
+    }
+
+    /**
+     * Iterator over the cells in a single UTM or UPS zone. There is exactly one {@code IteratorOneZone} instance
+     * for each Universal Polar Stereographic (UPS) or Universal Transverse Mercator (UTM) projection covered by
+     * the area of interest. A given {@code IteratorOneZone} instance use the same projection for all cells.
+     *
+     * <p>This class extends {@link Coder} in order to freeze the configuration (separator, precision, <i>etc</i>)
+     * to the values they have at iterator creation time, and because if we parallelize the iteration, each iterator
+     * will need its own {@link Coder#buffer}, {@link Coder#normalized} and {@link Coder#geographic} cache.</p>
+     *
+     * @see Coder.IteratorAllZones
+     */
+    private final class IteratorOneZone extends Coder implements Spliterator<String> {
+        /**
+         * 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.
+         */
+        private final Rectangle2D areaOfInterest;
+
+        /**
+         * The transform from the CRS of {@link #encoder} to the CRS of {@link #areaOfInterest}.
+         */
+        private final MathTransform2D gridToAOI;
+
+        /**
+         * The encoder to use for creating MGRS codes.
+         */
+        private final Encoder encoder;
+
+        /**
+         * The easting value of the projection natural origin. This information is used for determining
+         * whether {@link #gridX} is on the left side or on right side of the center of UPS or UTM zone.
+         * We use this information for choosing the cell corner which is closest to map projection origin.
+         */
+        private final int xCenter;
+
+        /**
+         * The {@link #gridX} value where to stop iteration, exclusive. This value is always greater than
+         * {@code gridX} (unless the iteration finished), but is not necessarily greater than {@link #xCenter}.
+         */
+        private final int xEnd;
+
+        /**
+         * The first <var>northing</var> value to use in iteration.
+         * The {@link #gridY} value will need to be reset to this value for each new column.
+         */
+        private int yStart;
+
+        /**
+         * The {@link #gridY} values where to stop iteration, exclusive. Invariants:
+         * <ul>
+         *   <li>{@code gridY < yEnd} shall be true in the North hemisphere, and
+         *       {@code gridY > yEnd} shall be true in the South hemisphere.</li>
+         * </ul>
+         *
+         * The reason of {@code gridY} relationship dependency to the hemisphere is because
+         * we try to iterate from equator to the pole.
+         */
+        private int yEnd;
+
+        /**
+         * Position of the next MGRS reference to encode. Position is composed of the latitude band number,
+         * the row and column indices of current 100 km square, and finally the grid coordinates inside the
+         * current 100 km square. All those components are inferred from the (easting, northing) values.
+         */
+        private int gridX, gridY;
+
+        /**
+         * The amount of metres to add to {@code gridX} and to add or subtract to {@code gridY} during iteration.
+         * The sign to use when updating the {@code gridY} value depends on whether we are in the North or South
+         * hemisphere.
+         */
+        private final int step;
+
+        /**
+         * Whether this iterator should iterates downward over rows. If {@code true}, then {@link #step} shall be
+         * subtracted to {@link #gridY} instead of added. We iterate downward in UTM south zones in order to go
+         * from equator to pole. This direction allows some optimizations.
+         */
+        private final boolean downward;
+
+        /**
+         * Whether this iterator is allowed to skip some cells when testing for inclusion in the area of interest.
+         * Since {@code IteratorOneZone} iterates in UTM zone from equator to pole, the range of longitude values
+         * will only decrease (the minimal longitude increase and the maximal longitude decrease). Consequently
+         * if we found a longitude out of range, we don't need to test that longitude again in next row.
+         *
+         * <p>This optimization is allowed only under the following conditions:</p>
+         * <ul>
+         *   <li>{@link #areaOfInterest} is a rectangle in geographic coordinates.</li>
+         *   <li>{@link #areaOfInterest} does not intersect Norway and Svalbard special cases.</li>
+         *   <li>We iterate in a UTM zone, or in a UPS zone contained fully in the upper half or fully in lower half.</li>
+         * </ul>
+         */
+        private final boolean optimize;
+
+        /**
+         * Latitude band of the last MGRS reference encoded by the iterator.
+         * This information is used for detecting when we moved to a new latitude band.
+         */
+        private char latitudeBand;
+
+        /**
+         * A MGRS reference which was pending return by {@link #tryAdvance(Consumer)} before to continue iteration.
+         * This field may be non-null immediately after a change of latitude band, and should be null otherwise.
+         */
+        private String pending;
+
+        /**
+         * 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 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.
+         * @param targetCRS       the UTM or UPS projected CRS of the zone for which to create MGRS references.
+         * @param step            the amount of metres to add or subtract to grid coordinates during iteration.
+         */
+        IteratorOneZone(final Coder coder, Rectangle2D areaOfInterest, final Envelope geographicArea,
+                        final SingleCRS sourceCRS, final ProjectedCRS targetCRS, final int step)
+                        throws FactoryException, TransformException
+        {
+            super(coder);
+            this.areaOfInterest = areaOfInterest;
+            this.encoder        = encoder(targetCRS);
+            this.step           = step;
+            /*
+             * Compute the geographic bounds of the UPS or UTM zone of validity, together with a representative point
+             * (φ,λ₀). 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) {
+                    φmin = Latitude.MIN_VALUE;
+                    φmax = TransverseMercator.Zoner.SOUTH_BOUNDS;
+                } else {
+                    φmin = TransverseMercator.Zoner.NORTH_BOUNDS;
+                    φmax = Latitude.MAX_VALUE;
+                }
+                λmin = Longitude.MIN_VALUE;
+                λmax = Longitude.MAX_VALUE;
+            } else {
+                xCenter = (int) ZONER.easting;
+                if (encoder.crsZone < 0) {
+                    φmin = TransverseMercator.Zoner.SOUTH_BOUNDS;
+                    φmax = 0;
+                } else {
+                    φmin = 0;
+                    φmax = TransverseMercator.Zoner.NORTH_BOUNDS;
+                }
+                final double λ0 = ZONER.centralMeridian(zone);
+                λmin = λ0 - ZONER.width / 2;
+                λmax = λ0 + ZONER.width / 2;
+            }
+            double t;
+            boolean clip = false;
+            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
+                 * (in which case above code clipped the geographic bounds), project the CRS domain of validity
+                 * 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 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.
+             * Now we can project that area to the CRS managed by this iterator. All values after projection
+             * should be positive (because UPS and UTM are designed that way).
+             */
+            final CoordinateOperation op = CRS.findOperation(sourceCRS, targetCRS, null);
+            final Rectangle2D bounds = Shapes2D.transform(op, areaOfInterest, null);
+            gridX = (((int)          (bounds.getMinX() / step))) * step;                    // Inclusive
+            gridY = (((int)          (bounds.getMinY() / step))) * step;
+            xEnd  = (((int) Math.ceil(bounds.getMaxX() / step))) * step;                    // Exclusive
+            yEnd  = (((int) Math.ceil(bounds.getMaxY() / step))) * step;
+            /*
+             * Determine if we should iterate on rows upward or downward. The intend is to iterate from equator to pole
+             * in UTM zones, or from projection center to projection border in UPS cases.  Those directions enable some
+             * optimizations.
+             */
+            if (zone != Encoder.POLE) {
+                downward = (encoder.crsZone < 0);           // Upward in UTM North zones, downward in UTM South zones.
+            } else {
+                downward = yEnd <= PolarStereographicA.UPS_SHIFT;  // Downward only if AOI is fully in the lower half.
+                isSpecialCase = (gridX < PolarStereographicA.UPS_SHIFT);         // Can not optimize left side of UPS.
+            }
+            if (downward) {
+                final int y = gridY;
+                gridY = yEnd - step;
+                yEnd  = y    - step;
+            }
+            yStart    = gridY;
+            gridToAOI = (MathTransform2D) op.getMathTransform().inverse();
+            /*
+             * To be strict, we should also test that the region of interest does not intersect both the upper half
+             * and lower half of Universal Polar Stereographic (UPS) projection domain. We do not check that because
+             * we need the 'optimize' flag to have the value that we get after 'trySplit()' execution. This implies
+             * that invoking 'trySplit()' is mandatory before using this iterator, but IteratorAllZones ensures that.
+             */
+            optimize = !isSpecialCase && Utilities.equalsIgnoreMetadata(geographicArea.getCoordinateReferenceSystem(), sourceCRS);
+        }
+
+        /**
+         * Creates an iterator for the lower half of a Universal Polar Stereographic (UPS) projection,
+         * and modifies the given iterator for restricting it to the upper half of UPS projection.
+         * This method is for {@link #trySplit()} usage only.
+         */
+        private IteratorOneZone(final IteratorOneZone other) {
+            super(other);
+            areaOfInterest = other.areaOfInterest;
+            gridToAOI      = other.gridToAOI;
+            encoder        = other.encoder;
+            optimize       = other.optimize;
+            step           = other.step;
+            gridX          = other.gridX;
+            xCenter        = other.xCenter;
+            xEnd           = other.xEnd;
+            yEnd           = other.yStart - step;                   // Bottom of the zone to iterate, exclusive.
+            yStart         = PolarStereographicA.UPS_SHIFT - step;  // Top of the zone to iterate, inclusive.
+            other.yStart   = PolarStereographicA.UPS_SHIFT;         // Other iterator will be for the upper half.
+            other.gridY    = other.yStart;
+            gridY          = yStart;
+            downward       = true;
+            assert !other.downward;                                 // Fail if the other iterator is not for upper half.
+        }
+
+        /**
+         * If this iterator intersects both the upper and lower half on UPS domain, returns an iterator for the
+         * lower half and modifies this iterator for the upper half. This method <strong>must</strong> be invoked
+         * before {@code IteratorOneZone} can be used.
+         */
+        @Override
+        public Spliterator<String> trySplit() {
+            if (!downward && Math.abs(encoder.crsZone) == Encoder.POLE && gridY < PolarStereographicA.UPS_SHIFT) {
+                return new IteratorOneZone(this);
+            }
+            return null;
+        }
+
+        /**
+         * Returns an estimation of the number of cells in the area covered by this iterator. The returned value
+         * may be greater than the real amount since we do not take in account the fact that the number of cells
+         * in a row become lower as we approach poles.
+         */
+        @Override
+        public long estimateSize() {
+            return (xEnd - (long) gridX) * Math.abs(yEnd - (long) yStart) / (step * (long) step);
+        }
+
+        /**
+         * Computes the next cell reference, if any. This method computes the bounding box in UPS or UTM
+         * coordinates, verifies if that box intersects the area of interest, and (if it intersects)
+         * delegates to {@link Encoder} for creating the MGRS reference.
+         */
+        @Override
+        public boolean tryAdvance(final Consumer<? super String> action) {
+            return advance(action, false);
+        }
+
+        /**
+         * Performs the given action for each remaining MGRS codes.
+         */
+        @Override
+        public void forEachRemaining(final Consumer<? super String> action) {
+            advance(action, true);
+            if (pending != null) {
+                action.accept(pending);
+                pending = null;
+            }
+        }
+
+        /**
+         * Implementation of {@link #tryAdvance(Consumer)} and {@link #forEachRemaining(Consumer)}.
+         * The {@code all} argument specifies whether this method is invoked for a single element
+         * or for all remaining ones.
+         */
+        private boolean advance(final Consumer<? super String> action, final boolean all) {
+            final int digits = digits();
+            final String separator = getSeparator();
+            if (normalized == null) {
+                normalized = new DirectPosition2D();
+            }
+            boolean found = false;
+            try {
+                do {
+                    if (pending != null) {
+                        action.accept(pending);
+                        pending = null;
+                        found = true;
+                        continue;
+                    }
+                    /*
+                     * Verifies if the current cell should be accepted.
+                     * To be accepted, the cell must complies with two conditions:
+                     *
+                     *   1) It must be inside the area of interest (AOI). Note that the AOI may be in any CRS.
+                     *   2) It must be inside the area of validity (AOV). Since the constructor clipped the AOI
+                     *      to the area of validity, this test #2 is redundant with test #1 if both AOI and AOV
+                     *      use the same CRS. However if AOI and AOV do not use the same CRS, then condition #1
+                     *      does not automatically implies condition #2, so we test both.
+                     *
+                     * Condition #1 is verified by the call to 'areaOfInterest.intersects(…)' below.
+                     * Condition #2 is verified indirectly by the call to 'encoder.encode(…)', which return null
+                     * if the given point is outside the area of validity.
+                     *
+                     * Since MGRS references are created by calls to Encoder.encode(…, DirectPosition, …), we need
+                     * to select the corner closest to projection center. Otherwise Encoder.encode(…) may consider
+                     * that a position is outside the domain of validity while another corner of the same cell would
+                     * have been ok. We resolve this issue by shifting 'gridX' toward projection center if 'gridX'
+                     * is on the left side.
+                     */
+                    cell.setRect(gridX, gridY, step, step);
+                    cell.setRect(Shapes2D.transform(gridToAOI, cell, cell));
+                    if (cell.intersects(areaOfInterest)) {          // Must be invoked on Envelope2D implementation.
+                        int x = gridX;
+                        int y = gridY;
+                        if (x < xCenter) x += step - 1;
+                        if (downward)    y += step - 1;
+                        normalized.setOrdinate(0, x);
+                        normalized.setOrdinate(1, y);
+                        String ref = encoder.encode(this, normalized, false, separator, digits);
+                        if (ref != null) {
+                            /*
+                             * If there is a change of latitude band, we may have missed a cell before this one.
+                             * We verify that by encoding the cell for the position just before current cell and
+                             * comparing the latitude band of the result with our previous latitude band.
+                             */
+                            char previous = latitudeBand;
+                            latitudeBand = encoder.latitudeBand;
+                            if (latitudeBand != previous && previous != 0) {
+                                pending = ref;
+                                normalized.setOrdinate(1, y + (downward ? +1 : -1));
+                                ref = encoder.encode(this, normalized, false, separator, digits);
+                                if (ref == null || encoder.latitudeBand == previous) {
+                                    ref = pending;  // No result or same result than previous iteration - cancel.
+                                    pending = null;
+                                }
+                            }
+                            action.accept(ref);
+                            found = true;
+                        }
+                    } else if (optimize) {
+                        /*
+                         * If this cell is not in the area of interest (AOI) when iterating away from projection
+                         * origin (from equator to pole in UTM case), then all other cells after this one in the
+                         * same column will not be inside the AOI neither. Set 'gridY' to the limit value so the
+                         * condition below will stop the iteration in this column and move to the next column.
+                         */
+                        gridY = yEnd;
+                    }
+                    /*
+                     * Move to the next cell. We need to do that regardless if the previous block found a cell or not.
+                     * We move from equator to pole (UTM case) or projection center to projection border (UPS case)
+                     * on the same column, than move one column on the right side when we have reached the last row.
+                     */
+                    final boolean end = downward ? ((gridY -= step) <= yEnd)        // UTM South or lower part of UPS.
+                                                 : ((gridY += step) >= yEnd);       // UTM North or upper part of UPS.
+                    if (end) {
+                        gridY = yStart;
+                        latitudeBand = 0;
+                        if ((gridX += step) >= xEnd) {
+                            break;
+                        }
+                    }
+                } while (all || !found);
+            } catch (FactoryException | TransformException e) {
+                // Should never happen since we clipped the area of interest to UPS or UTM domain of valididty.
+                throw (ArithmeticException) new ArithmeticException(Errors.format(Errors.Keys.OutsideDomainOfValidity)).initCause(e);
+            }
+            return found;
+        }
+
+        /**
+         * Specifies that the list of elements is immutable, that all elements will be distinct
+         * and that this iterator never return {@code null} element.
+         */
+        @Override
+        public int characteristics() {
+            return IMMUTABLE | DISTINCT | NONNULL;
+        }
+
+        /**
+         * Returns a string representation of this iterator for debugging purpose.
+         */
+        @Debug
+        @Override
+        public String toString() {
+            return org.apache.sis.internal.util.Utilities.toString(getClass(), "zone", encoder.crsZone,
+                    "downward", downward, "yStart", yStart, "yEnd", yEnd, "gridX", gridX, "xEnd", xEnd);
+        }
     }
 
 
@@ -564,10 +1317,11 @@ public class MilitaryGridReferenceSystem
         /**
          * UTM zone of position CRS (negative for South hemisphere), or {@value #POLE} (negative of positive)
          * if the CRS is a Universal Polar Stereographic projection, or 0 if the CRS is not a recognized projection.
-         * Note that this is not necessarily the same zone than the one to use for formatting any given coordinate in
-         * that projected CRS, since the {@link #zone(double, char)} method has special rules for some latitudes.
+         * Note that this is not necessarily the same zone than the one to use for formatting any given coordinate
+         * in that projected CRS, since the {@link TransverseMercator.Zoner#zone(double, double)} method has special
+         * rules for some latitudes.
          */
-        private final int crsZone;
+        final int crsZone;
 
         /**
          * Coordinate conversion from the position CRS to a CRS of the same type but with normalized axes,
@@ -603,6 +1357,12 @@ public class MilitaryGridReferenceSystem
         private int actualZone;
 
         /**
+         * The latitude band of the last encoded reference.
+         * This information is provided for {@link IteratorOneZone} purpose.
+         */
+        char latitudeBand;
+
+        /**
          * Creates a new converter from direct positions to MGRS references.
          *
          * @param  datum  the datum to which to transform the coordinate before formatting the MGRS reference,
@@ -692,12 +1452,14 @@ public class MilitaryGridReferenceSystem
          *
          * @param  owner      the {@code Coder} which own this {@code Encoder}.
          * @param  position   the direct position to format as a MGRS reference.
+         * @param  reproject  whether this method is allowed to reproject {@code position} when needed.
          * @param  separator  the separator to insert between each component of the MGRS identifier.
          * @param  digits     number of digits to use for formatting the numerical part of a MGRS reference.
-         * @return the value of {@code buffer.toString()}.
+         * @return the value of {@code buffer.toString()}, or {@code null} if a reprojection was necessary
+         *         but {@code reproject} is {@code false}.
          */
-        String encode(final Coder owner, DirectPosition position, final String separator, final int digits)
-                throws FactoryException, TransformException
+        String encode(final Coder owner, DirectPosition position, final boolean reproject,
+                final String separator, final int digits) throws FactoryException, TransformException
         {
             final StringBuilder buffer = owner.buffer;
             if (toNormalized != null) {
@@ -721,11 +1483,15 @@ public class MilitaryGridReferenceSystem
              * than the coordinate one, or because the coordinate is geographic instead than projected.
              */
             if (signedZone != crsZone) {
+                if (!reproject) {
+                    return null;
+                }
                 if (signedZone != actualZone) {
                     actualZone   = 0;                           // In case an exception is thrown on the next line.
                     toActualZone = CRS.findOperation(datum.geographic(), datum.universal(φ, λ), null).getMathTransform();
                     actualZone   = signedZone;
                 }
+                geographic.setOrdinate(1, Longitude.normalize(λ));
                 owner.normalized = position = toActualZone.transform(geographic, owner.normalized);
             }
             /*
@@ -733,12 +1499,13 @@ public class MilitaryGridReferenceSystem
              */
             buffer.setLength(0);
             if (isUTM) {
-                buffer.append(zone).append(separator).append(latitudeBand(φ));
+                buffer.append(zone).append(separator);
+                latitudeBand = latitudeBand(φ);
             } else {
-                char z = (signedZone < 0) ? 'A' : 'Y';
-                if (λ >= 0) z++;
-                buffer.append(z);
+                latitudeBand = (signedZone < 0) ? 'A' : 'Y';
+                if (λ >= 0) latitudeBand++;
             }
+            buffer.append(latitudeBand);
             /*
              * 100 kilometres square identification.
              */
@@ -787,7 +1554,7 @@ public class MilitaryGridReferenceSystem
                      * over with column letter A.
                      */
                     final byte[] columns = POLAR_COLUMNS;
-                    col -= (int) (PolarStereographicA.UPS_SHIFT / GRID_SQUARE_SIZE);
+                    col -= PolarStereographicA.UPS_SHIFT / GRID_SQUARE_SIZE;
                     if (!(λ >= 0)) {                    // Same condition than in GZD block. Use of ! is for NaN.
                         col += columns.length;          // Letters Z to A from right to left.
                     }
@@ -981,7 +1748,7 @@ parse:                  switch (part) {
                                 col = Arrays.binarySearch(POLAR_COLUMNS, (byte) c);
                                 if (col < 0) break;                                     // Invalid column letter.
                                 if (west) col -= POLAR_COLUMNS.length;
-                                col += (int) (PolarStereographicA.UPS_SHIFT / GRID_SQUARE_SIZE);
+                                col += PolarStereographicA.UPS_SHIFT / GRID_SQUARE_SIZE;
                                 i = nextComponent(owner, reference, base, ni, end);
                                 continue;
                             }
@@ -1238,15 +2005,10 @@ parse:                  switch (part) {
                  * if the given 100 kilometres square identification is consistent with grid zone designation.
                  * We verify both φ and λ, but the verification of φ is actually redundant with the check of
                  * 100 km square validity that we did previously with the help of ROW_RESOLVER bitmask.
-                 * We check φ anyway in case of bug, but we have to allow a tolerance threshold on the south
-                 * bound because the 100 km square may overlap two latitude bands. We do not need equivalent
-                 * tolerance threshold for the upper bound because the coordinate that we are testing is the
-                 * lower-left corner of the cell area.
+                 * We check φ anyway in case of bug.
                  */
                 if (isValid && zone != 0) {
-                    final double λ = (westBoundLongitude + eastBoundLongitude) / 2;
-                    final double φ = (southBoundLatitude + northBoundLatitude) / 2;
-                    isValid = (φ >= φs - LATITUDE_BAND_HEIGHT/2) && (φ < upperBound(φs));   // See above comment.
+                    isValid = (northBoundLatitude >= φs) && (southBoundLatitude < upperBound(φs));
                     if (isValid) {
                         /*
                          * Verification of UTM zone. We allow a tolerance for latitudes close to a pole because
@@ -1254,12 +2016,15 @@ parse:                  switch (part) {
                          * the neighbor zone at those high latitudes is less significant. For other latitudes,
                          * we allow a tolerance if the point is close to a line of zone change.
                          */
+                        final double λ = (westBoundLongitude + eastBoundLongitude) / 2;
+                        final double φ = (southBoundLatitude + northBoundLatitude) / 2;
                         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) == 1;         // Tolerance in zone numbers for high latitudes.
+                                isValid = Math.abs(zoneError) <= 2;         // Tolerance in zone numbers for high latitudes.
                             } else {
                                 final double rλ = Math.IEEEremainder(λ - ZONER.origin, ZONER.width);    // Distance to closest zone change, in degrees of longitude.
                                 final double cv = (minX - ZONER.easting) / (λ - λ0);                    // Approximative conversion factor from degrees to metres.
@@ -1276,7 +2041,7 @@ parse:                  switch (part) {
                  * MGRS reference. If the cell is valid, we can now check for cells that are on a zone border.
                  * Those cells will be clipped to the zone valid area.
                  */
-                if (isValid) {
+                if (isValid && owner.getClipToValidArea()) {
                     final boolean changed;
                     if (zone != 0) {
                         double width = ZONER.width;
@@ -1298,7 +2063,7 @@ parse:                  switch (part) {
             if (!isValid) {
                 final String gzd;
                 try {
-                    gzd = owner.encoder(crs).encode(owner, getDirectPosition(), "", 0);
+                    gzd = owner.encoder(crs).encode(owner, getDirectPosition(), true, "", 0);
                 } catch (IllegalArgumentException | FactoryException e) {
                     throw new GazetteerException(e.getLocalizedMessage(), e);
                 }

Modified: sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java?rev=1786765&r1=1786764&r2=1786765&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java [UTF-8] Mon Mar 13 19:14:25 2017
@@ -158,7 +158,7 @@ public class ModifiableLocationType exte
     /**
      * Creates a new location type of the given name.
      *
-     * @param name the location type name.
+     * @param name  the location type name.
      */
     public ModifiableLocationType(final CharSequence name) {
         ArgumentChecks.ensureNonNull("name", name);

Copied: sis/trunk/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java (from r1786763, sis/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java)
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java?p2=sis/trunk/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java&p1=sis/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java&r1=1786763&r2=1786765&rev=1786765&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java [UTF-8] Mon Mar 13 19:14:25 2017
@@ -43,7 +43,6 @@ import org.apache.sis.util.Debug;
 
 // Branch-dependent imports
 import org.apache.sis.internal.jdk8.JDK8;
-import org.opengis.referencing.gazetteer.Location;
 
 
 /**
@@ -216,7 +215,7 @@ public final class LocationViewer extend
      * @throws FactoryException if a transformation to the display CRS can not be obtained.
      * @throws TransformException if an error occurred while transforming an envelope.
      */
-    public void addLocation(final String label, final Location location) throws FactoryException, TransformException {
+    public void addLocation(final String label, final AbstractLocation location) throws FactoryException, TransformException {
         final Envelope envelope = location.getEnvelope();
         final MathTransform2D tr = (MathTransform2D) CRS.findOperation(
                 envelope.getCoordinateReferenceSystem(), displayCRS, null).getMathTransform();

Modified: sis/trunk/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java?rev=1786765&r1=1786764&r2=1786765&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java [UTF-8] Mon Mar 13 19:14:25 2017
@@ -16,15 +16,22 @@
  */
 package org.apache.sis.referencing.gazetteer;
 
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.List;
 import java.util.Locale;
 import java.util.Random;
+import java.util.Iterator;
 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;
@@ -69,7 +76,7 @@ public final strictfp class MilitaryGrid
     }
 
     /**
-     * Verifies relationship between static fields documented in {@link Encoder}.
+     * Verifies relationship between static fields documented in {@link MilitaryGridReferenceSystem}.
      */
     @Test
     public void verifyInvariants() {
@@ -191,7 +198,7 @@ public final strictfp class MilitaryGrid
     /**
      * Returns a coder instance to test.
      */
-    private MilitaryGridReferenceSystem.Coder coder() {
+    private static MilitaryGridReferenceSystem.Coder coder() {
         return new MilitaryGridReferenceSystem().createCoder();
     }
 
@@ -309,36 +316,72 @@ public final strictfp class MilitaryGrid
     public void testDecodeLimitCases() throws TransformException {
         final MilitaryGridReferenceSystem.Coder coder = coder();
         DirectPosition position;
+        ProjectedCRS crs;
         /*
          * Cell on the West border of a UTM zone in the South hemisphere.
-         * The Easting value would be 250000 if the cell was not clipped.
+         * Easting value before clipping: 250000
+         * Easting value after  clipping: 251256
          */
+        coder.setClipToValidArea(false);
         position = decode(coder, "19JBK");                                            // South hemisphere
-        assertSame("crs", CommonCRS.WGS84.universal(-10, -69), position.getCoordinateReferenceSystem());
+        crs = CommonCRS.WGS84.universal(-10, -69);
+        assertSame("crs", crs, position.getCoordinateReferenceSystem());
+        assertEquals("Easting",   250000, position.getOrdinate(0), 1);
+        assertEquals("Northing", 6950000, position.getOrdinate(1), STRICT);
+
+        coder.setClipToValidArea(true);
+        position = decode(coder, "19JBK");
+        assertSame("crs", crs, position.getCoordinateReferenceSystem());
         assertEquals("Easting",   251256, position.getOrdinate(0), 1);
         assertEquals("Northing", 6950000, position.getOrdinate(1), STRICT);
         /*
          * Easting range before clipping is [300000 … 400000] metres.
-         * The east boung become 343828 metres after clipping.
-         * The easting value would be 350000 if the cell was not clipped.
+         * The east bound become 343828 metres after clipping.
+         * Easting value before clipping: 350000
+         * Easting value after  clipping: 371914
          */
+        coder.setClipToValidArea(false);
         position = decode(coder, "1VCK");                                // North of Norway latitude band
-        assertSame("crs", CommonCRS.WGS84.universal(62, -180), position.getCoordinateReferenceSystem());
+        crs = CommonCRS.WGS84.universal(62, -180);
+        assertSame("crs", crs, position.getCoordinateReferenceSystem());
+        assertEquals("Easting",   350000, position.getOrdinate(0), 1);
+        assertEquals("Northing", 6950000, position.getOrdinate(1), STRICT);
+
+        coder.setClipToValidArea(true);
+        position = decode(coder, "1VCK");
+        assertSame("crs", crs, position.getCoordinateReferenceSystem());
         assertEquals("Easting",   371914, position.getOrdinate(0), 1);
         assertEquals("Northing", 6950000, position.getOrdinate(1), STRICT);
         /*
-         * Northing value would be 7350000 if the cell was not clipped.
+         * Northing value before clipping: 7350000
+         * Northing value after  clipping: 7371306
          */
+        coder.setClipToValidArea(false);
         position = decode(coder, "57KTP");
-        assertSame("crs", CommonCRS.WGS84.universal(-24, 156), position.getCoordinateReferenceSystem());
+        crs = CommonCRS.WGS84.universal(-24, 156);
+        assertSame("crs", crs, position.getCoordinateReferenceSystem());
+        assertEquals("Easting",   250000, position.getOrdinate(0), STRICT);
+        assertEquals("Northing", 7350000, position.getOrdinate(1), 1);
+
+        coder.setClipToValidArea(true);
+        position = decode(coder, "57KTP");
+        assertSame("crs", crs, position.getCoordinateReferenceSystem());
         assertEquals("Easting",   250000, position.getOrdinate(0), STRICT);
         assertEquals("Northing", 7371306, position.getOrdinate(1), 1);
         /*
-         * Easting  value would be  650000 if the cell was not clipped.
-         * Northing value would be 6250000 if the cell was not clipped.
+         * Easting and northing values before clipping:  650000   6250000
+         * Easting and northing values after  clipping:  643536   6253618
          */
+        coder.setClipToValidArea(false);
+        position = decode(coder, "56VPH");
+        crs = CommonCRS.WGS84.universal(55, 154);
+        assertSame("crs", crs, position.getCoordinateReferenceSystem());
+        assertEquals("Easting",   650000, position.getOrdinate(0), 1);
+        assertEquals("Northing", 6250000, position.getOrdinate(1), 1);
+
+        coder.setClipToValidArea(true);
         position = decode(coder, "56VPH");
-        assertSame("crs", CommonCRS.WGS84.universal(55, 154), position.getCoordinateReferenceSystem());
+        assertSame("crs", crs, position.getCoordinateReferenceSystem());
         assertEquals("Easting",   643536, position.getOrdinate(0), 1);
         assertEquals("Northing", 6253618, position.getOrdinate(1), 1);
     }
@@ -566,7 +609,10 @@ public final strictfp class MilitaryGrid
      * @throws TransformException if an error occurred while computing the coordinate.
      */
     @Test
-    @DependsOnMethod({"testEncodeUTM", "testDecodeUTM"})
+    @DependsOnMethod({
+        "testEncodeUTM", "testDecodeUTM",
+        "testEncodeUPS", "testDecodeUPS"
+    })
     public void verifyConsistency() throws TransformException {
         final Random random = TestUtilities.createRandomNumberGenerator();
         final MilitaryGridReferenceSystem.Coder coder = coder();
@@ -590,4 +636,146 @@ public final strictfp class MilitaryGrid
             }
         }
     }
+
+    /**
+     * Tests iteration over all codes in a given area of interest. The geographic area used for this test is based on
+     * <a href="https://www.ff-reichertshausen.de/cms/wp-content/uploads/2012/10/utmmeldegitter.jpg">this picture</a>
+     * (checked on March 2017).
+     *
+     * <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
+    @DependsOnMethod("testEncodeUTM")
+    public void testIteratorNorthUTM() throws TransformException {
+        /*
+         * Following is the list of MGRS references that we expect to find in the above area of interest.
+         * The references are distributed in 3 zones (31, 32 and 33) and 3 latitude bands (T, U and V).
+         * This test includes the Norway special case: between 56° and 64°N (latitude band V), zone 32
+         * is widened to 9° at the expense of zone 31. The test needs to be insensitive to iteration order.
+         */
+        testIterator(new Envelope2D(CommonCRS.defaultGeographic(), 5, 47, 8, 10), Arrays.asList(
+            "31TFN", "31TGN",    "32TKT", "32TLT", "32TMT", "32TNT", "32TPT", "32TQT",    "33TTN", "33TUN",
+            "31TFP", "31TGP",    "32TKU", "32TLU", "32TMU", "32TNU", "32TPU", "32TQU",    "33TTP", "33TUP",
+            "31UFP", "31UGP",    "32UKU", "32ULU", "32UMU", "32UNU", "32UPU", "32UQU",    "33UTP", "33UUP",
+            "31UFQ", "31UGQ",    "32UKV", "32ULV", "32UMV", "32UNV", "32UPV", "32UQV",    "33UTQ", "33UUQ",
+            "31UFR", "31UGR",    "32UKA", "32ULA", "32UMA", "32UNA", "32UPA", "32UQA",    "33UTR", "33UUR",
+            "31UFS", "31UGS",    "32UKB", "32ULB", "32UMB", "32UNB", "32UPB", "32UQB",    "33UTS", "33UUS",
+            "31UFT", "31UGT",    "32UKC", "32ULC", "32UMC", "32UNC", "32UPC", "32UQC",    "33UTT", "33UUT",
+            "31UFU", "31UGU",    "32UKD", "32ULD", "32UMD", "32UND", "32UPD", "32UQD",    "33UTU", "33UUU",
+            "31UFV", "31UGV",    "32UKE", "32ULE", "32UME", "32UNE", "32UPE", "32UQE",    "33UTV", "33UUV",
+            "31UFA",                      "32ULF", "32UMF", "32UNF", "32UPF",             "33UUA",
+            "31UFB",                      "32ULG", "32UMG", "32UNG", "32UPG",             "33UUB",
+            "31UFC",                      "32ULH", "32UMH", "32UNH", "32UPH",             "33UUC",
+            /* Norway case */    "32VKH", "32VLH", "32VMH", "32VNH", "32VPH",             "33VUC",
+            /* Norway case */    "32VKJ", "32VLJ", "32VMJ", "32VNJ", "32VPJ",             "33VUD"));
+    }
+
+    /**
+     * Tests iteration over codes in an area in South hemisphere.
+     *
+     * <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
+    @DependsOnMethod("testEncodeUTM")
+    public void testIteratorSouthUTM() throws TransformException {
+        testIterator(new Envelope2D(CommonCRS.defaultGeographic(), 5, -42, 8, 4), Arrays.asList(
+            "31HFT", "31HGT", "32HKC", "32HLC", "32HMC", "32HNC", "32HPC", "32HQC", "33HTT", "33HUT",
+            "31HFS", "31HGS", "32HKB", "32HLB", "32HMB", "32HNB", "32HPB", "32HQB", "33HTS", "33HUS",
+            "31HFR", "31HGR", "32HKA", "32HLA", "32HMA", "32HNA", "32HPA", "32HQA", "33HTR", "33HUR",
+            "31GFR", "31GGR", "32GKA", "32GLA", "32GMA", "32GNA", "32GPA", "32GQA", "33GTR", "33GUR",
+            "31GFQ", "31GGQ", "32GKV", "32GLV", "32GMV", "32GNV", "32GPV", "32GQV", "33GTQ", "33GUQ",
+            "31GFP", "31GGP", "32GKU", "32GLU", "32GMU", "32GNU", "32GPU", "32GQU", "33GTP", "33GUP"));
+    }
+
+    /**
+     * 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
+    @DependsOnMethod("testEncodeUTM")
+    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"));
+    }
+
+    /**
+     * Tests iterating over part of North pole, in an area between 10°W to 70°E.
+     * This area is restricted to the lower part of UPS projection, which allow
+     * {@code IteratorAllZones} to simplify to a single {@code IteratorOneZone}.
+     *
+     * <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
+    @DependsOnMethod("testEncodeUPS")
+    public void testIteratorNorthPole() throws TransformException {
+        testIterator(new Envelope2D(CommonCRS.defaultGeographic(), -10, 85, 80, 5), Arrays.asList(
+            "YZG", "ZAG", "ZBG", "ZCG",
+            "YZF", "ZAF", "ZBF", "ZCF", "ZFF", "ZGF", "ZHF",
+            "YZE", "ZAE", "ZBE", "ZCE", "ZFE", "ZGE", "ZHE",
+            "YZD", "ZAD", "ZBD", "ZCD", "ZFD", "ZGD",
+            "YZC", "ZAC", "ZBC", "ZCC", "ZFC",
+            "YZB", "ZAB", "ZBB", "ZCB"));
+    }
+
+    /**
+     * Tests iterating over part of South pole, both lower and upper parts of UPS projection
+     * together with some UTM zones. This is a test mixing a bit of everything together.
+     *
+     * <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
+    @DependsOnMethod({"testEncodeUPS", "testEncodeUTM"})
+    public void testIteratorSouthPole() throws TransformException {
+        testIterator(new Envelope2D(CommonCRS.defaultGeographic(), -120, -83, 50, 5), Arrays.asList(
+                   "AKR", "ALR", "APR",
+                   "AKQ", "ALQ", "APQ", "AQQ",
+            "AJP", "AKP", "ALP", "APP", "AQP",
+            "AJN", "AKN", "ALN", "APN", "AQN",
+            "AJM", "AKM", "ALM", "APM", "AQM",
+            "AJL", "AKL", "ALL", "APL", "AQL",
+                   "AKK", "ALK", "APK", "AQK",
+                   "AKJ", "ALJ", "APJ", "AQJ", "ARJ",
+                   "AKH", "ALH", "APH", "AQH", "ARH",
+                          "ALG", "APG",
+
+            "11CMP", "11CNP", "12CVU", "12CWU", "13CDP", "13CEP", "14CMU", "14CNU", "15CVP", "15CWP", "16CDU", "16CEU", "17CMP", "17CNP", "18CVU", "18CWU", "19CDP",
+            "11CMN", "11CNN", "12CVT", "12CWT", "13CDN", "13CEN", "14CMT", "14CNT", "15CVN", "15CWN", "16CDT", "16CET", "17CMN", "17CNN", "18CVT", "18CWT", "19CDN",
+            "11CMM", "11CNM", "12CVS", "12CWS", "13CDM", "13CEM", "14CMS", "14CNS", "15CVM", "15CWM", "16CDS", "16CES", "17CMM", "17CNM", "18CVS", "18CWS", "19CDM"));
+    }
+
+    /**
+     * Implementation of {@link #testIteratorUTM()}.
+     */
+    private static void testIterator(final Envelope areaOfInterest, final List<String> expected) throws TransformException {
+        final MilitaryGridReferenceSystem.Coder coder = coder();
+        coder.setClipToValidArea(false);
+        coder.setPrecision(100000);
+        /*
+         * Test sequential iteration using iterator.
+         */
+        final Set<String> remaining = new HashSet<>(expected);
+        assertEquals("List of expected codes has duplicated values.", expected.size(), remaining.size());
+        for (final Iterator<String> it = coder.encode(areaOfInterest); it.hasNext();) {
+            final String code = it.next();
+            assertTrue(code, remaining.remove(code));
+        }
+        assertTrue(remaining.toString(), remaining.isEmpty());
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java?rev=1786765&r1=1786764&r2=1786765&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java [UTF-8] Mon Mar 13 19:14:25 2017
@@ -97,13 +97,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.
@@ -283,6 +282,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.
@@ -443,6 +465,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.
      */
@@ -453,6 +478,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.
      */
@@ -463,6 +491,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.
      */
@@ -473,6 +504,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.
      */
@@ -483,6 +517,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.
      */
@@ -493,6 +530,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.
      */
@@ -503,6 +543,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.
      */
@@ -513,6 +556,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.
      */
@@ -755,7 +801,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);
     }
 
     /**
@@ -884,7 +930,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;
@@ -1089,6 +1135,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/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java?rev=1786765&r1=1786764&r2=1786765&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java [UTF-8] Mon Mar 13 19:14:25 2017
@@ -390,6 +390,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/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java?rev=1786765&r1=1786764&r2=1786765&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java [UTF-8] Mon Mar 13 19:14:25 2017
@@ -386,6 +386,7 @@ public final class Shapes2D extends Stat
      * @see #transform(MathTransform2D, Rectangle2D, Rectangle2D)
      * @see Envelopes#transform(CoordinateOperation, Envelope)
      */
+    @SuppressWarnings("null")
     public static Rectangle2D transform(final CoordinateOperation operation,
                                         final Rectangle2D         envelope,
                                               Rectangle2D         destination)



Mime
View raw message