You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2017/03/13 19:06:47 UTC
svn commit: r1786763 [1/2] - in /sis/branches/JDK7: ./
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/...
Author: desruisseaux
Date: Mon Mar 13 19:06:47 2017
New Revision: 1786763
URL: http://svn.apache.org/viewvc?rev=1786763&view=rev
Log:
Merge the completion of MGRS codes from JDK8 branch.
Added:
sis/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
- copied, changed from r1786756, sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/IntervalRectangle.java
- copied unchanged from r1786756, sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/IntervalRectangle.java
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/Spliterators.java (with props)
Modified:
sis/branches/JDK7/ (props changed)
sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java
sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java
sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
sis/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java
sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/jdk8/Spliterator.java
sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
Propchange: sis/branches/JDK7/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Mon Mar 13 19:06:47 2017
@@ -1,5 +1,5 @@
/sis/branches/Android:1430670-1480699
/sis/branches/JDK6:1394913-1508480
-/sis/branches/JDK8:1584960-1785861
+/sis/branches/JDK8:1584960-1786756
/sis/branches/JDK9:1773327-1773512
/sis/trunk:1394364-1508466,1519089-1519674
Modified: sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java?rev=1786763&r1=1786762&r2=1786763&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/AbstractLocation.java [UTF-8] Mon Mar 13 19:06:47 2017
@@ -18,20 +18,22 @@ package org.apache.sis.referencing.gazet
import java.util.Collection;
import java.util.Collections;
-import org.opengis.metadata.citation.Party;
import org.opengis.metadata.extent.TemporalExtent;
import org.opengis.metadata.extent.GeographicExtent;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.coordinate.Position;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
import org.opengis.util.InternationalString;
import org.apache.sis.util.iso.Types;
import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.geometry.GeneralDirectPosition;
import org.apache.sis.util.Debug;
+// Branch-dependent imports
+import org.opengis.metadata.citation.Party;
+import org.opengis.referencing.gazetteer.Location;
+import org.opengis.referencing.gazetteer.LocationType;
+
/**
* Identifiable geographic place. A geographic place may be identified by a name (for example \u201cEiffel Tower\u201d),
Modified: sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java?rev=1786763&r1=1786762&r2=1786763&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/LocationFormat.java [UTF-8] Mon Mar 13 19:06:47 2017
@@ -30,15 +30,12 @@ import java.text.ParsePosition;
import javax.measure.Unit;
import org.opengis.util.FactoryException;
import org.opengis.util.InternationalString;
-import org.opengis.referencing.gazetteer.Location;
-import org.opengis.referencing.gazetteer.LocationType;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.metadata.extent.Extent;
-import org.opengis.metadata.citation.Party;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.coordinate.Position;
@@ -63,6 +60,11 @@ import org.apache.sis.util.ArgumentCheck
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.resources.Vocabulary;
+// Branch-dependent imports
+import org.opengis.metadata.citation.Party;
+import org.opengis.referencing.gazetteer.Location;
+import org.opengis.referencing.gazetteer.LocationType;
+
/**
* Formats {@link Location} instances in a tabular format.
Modified: sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java?rev=1786763&r1=1786762&r2=1786763&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java [UTF-8] Mon Mar 13 19:06:47 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.metadata.sql.MetadataSource;
import org.apache.sis.metadata.sql.MetadataStoreException;
@@ -47,9 +54,13 @@ 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.logging.Logging;
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.internal.system.Modules;
import org.apache.sis.measure.Longitude;
@@ -57,6 +68,11 @@ 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.opengis.metadata.citation.Party;
import org.opengis.referencing.gazetteer.Location;
import org.opengis.referencing.gazetteer.LocationType;
@@ -372,6 +388,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;
@@ -385,16 +408,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\u2009Q\u2009FJ\u200912345\u200967890" 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\u2009Q\u2009FJ\u200912345\u200967890" 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;
}
/**
@@ -448,6 +485,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\u2009000-metres square identifier and numerical values.
* By default the separator is an empty string, which produce references like "4QFJ12345678".
@@ -477,6 +521,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.
*/
@@ -486,12 +553,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));
}
@@ -517,13 +585,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.
*
@@ -535,6 +657,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(\u2026) 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 \u03c6min = geographicArea.getMinimum(1);
+ final double \u03c6max = geographicArea.getMaximum(1);
+ boolean southPole = (\u03c6min < TransverseMercator.Zoner.SOUTH_BOUNDS);
+ boolean northPole = (\u03c6max > 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 (\u03c6max >= TransverseMercator.Zoner.SOUTH_BOUNDS && \u03c6min < TransverseMercator.Zoner.NORTH_BOUNDS) {
+ southUTM = (\u03c6min < 0);
+ northUTM = (\u03c6max >= 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 \u03c6 value for the purpose of CommonCRS.universal(\u2026) 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 \u03bb = ZONER.centralMeridian((zone % zoneCount) + 1);
+ final double \u03c6;
+ if (southPole) {
+ \u03c6 = Latitude.MIN_VALUE;
+ southPole = false; // For next iteration.
+ } else if (southUTM) {
+ \u03c6 = -1;
+ if (++zone >= zoneEnd) {
+ zone = zoneStart;
+ southUTM = false; // For next iteration.
+ }
+ } else if (northUTM) {
+ \u03c6 = +1;
+ if (++zone >= zoneEnd) {
+ northUTM = false; // For next iteration.
+ }
+ } else if (northPole) {
+ \u03c6 = Latitude.MAX_VALUE;
+ northPole = false; // For loop termination.
+ } else {
+ throw new AssertionError();
+ }
+ final ProjectedCRS targetCRS = datum.universal(\u03c6, \u03bb);
+ 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 \u2026 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
+ * (\u03c6,\u03bb\u2080). 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 \u03bbmin, \u03bbmax, \u03c6min, \u03c6max;
+ final int zone = Math.abs(encoder.crsZone);
+ if (zone == Encoder.POLE) {
+ xCenter = PolarStereographicA.UPS_SHIFT;
+ if (encoder.crsZone < 0) {
+ \u03c6min = Latitude.MIN_VALUE;
+ \u03c6max = TransverseMercator.Zoner.SOUTH_BOUNDS;
+ } else {
+ \u03c6min = TransverseMercator.Zoner.NORTH_BOUNDS;
+ \u03c6max = Latitude.MAX_VALUE;
+ }
+ \u03bbmin = Longitude.MIN_VALUE;
+ \u03bbmax = Longitude.MAX_VALUE;
+ } else {
+ xCenter = (int) ZONER.easting;
+ if (encoder.crsZone < 0) {
+ \u03c6min = TransverseMercator.Zoner.SOUTH_BOUNDS;
+ \u03c6max = 0;
+ } else {
+ \u03c6min = 0;
+ \u03c6max = TransverseMercator.Zoner.NORTH_BOUNDS;
+ }
+ final double \u03bb0 = ZONER.centralMeridian(zone);
+ \u03bbmin = \u03bb0 - ZONER.width / 2;
+ \u03bbmax = \u03bb0 + ZONER.width / 2;
+ }
+ double t;
+ boolean clip = false;
+ if ((t = geographicArea.getMinimum(1)) >= \u03c6min) \u03c6min = t; else clip = true;
+ if ((t = geographicArea.getMaximum(1)) <= \u03c6max) \u03c6max = t; else clip = true;
+ if ((t = geographicArea.getMinimum(0)) >= \u03bbmin) \u03bbmin = t; else clip = true;
+ if ((t = geographicArea.getMaximum(0)) <= \u03bbmax) \u03bbmax = t; else clip = true;
+ boolean isSpecialCase = ZONER.isSpecialCase(\u03c6min, \u03c6max, \u03bbmin, \u03bbmax);
+ 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(\u03bbmin, \u03c6min, \u03bbmax, \u03c6max);
+ 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 \u2026 180]�, in which case intersection
+ * with [\u03bbmin \u2026 \u03bbmax] is [\u03bbmin \u2026 \u03bbmax] itself.
+ */
+ if (r.xmax < r.xmin) {
+ r.xmin = \u03bbmin;
+ r.xmax = \u03bbmax;
+ }
+ 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(\u2026)' below.
+ * Condition #2 is verified indirectly by the call to 'encoder.encode(\u2026)', which return null
+ * if the given point is outside the area of validity.
+ *
+ * Since MGRS references are created by calls to Encoder.encode(\u2026, DirectPosition, \u2026), we need
+ * to select the corner closest to projection center. Otherwise Encoder.encode(\u2026) 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);
+ }
}
@@ -572,10 +1325,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,
@@ -611,6 +1365,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,
@@ -700,12 +1460,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) {
@@ -729,11 +1491,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(\u03c6, \u03bb), null).getMathTransform();
actualZone = signedZone;
}
+ geographic.setOrdinate(1, Longitude.normalize(\u03bb));
owner.normalized = position = toActualZone.transform(geographic, owner.normalized);
}
/*
@@ -741,12 +1507,13 @@ public class MilitaryGridReferenceSystem
*/
buffer.setLength(0);
if (isUTM) {
- buffer.append(zone).append(separator).append(latitudeBand(\u03c6));
+ buffer.append(zone).append(separator);
+ latitudeBand = latitudeBand(\u03c6);
} else {
- char z = (signedZone < 0) ? 'A' : 'Y';
- if (\u03bb >= 0) z++;
- buffer.append(z);
+ latitudeBand = (signedZone < 0) ? 'A' : 'Y';
+ if (\u03bb >= 0) latitudeBand++;
}
+ buffer.append(latitudeBand);
/*
* 100 kilometres square identification.
*/
@@ -795,7 +1562,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 (!(\u03bb >= 0)) { // Same condition than in GZD block. Use of ! is for NaN.
col += columns.length; // Letters Z to A from right to left.
}
@@ -989,7 +1756,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;
}
@@ -1246,15 +2013,10 @@ parse: switch (part) {
* if the given 100 kilometres square identification is consistent with grid zone designation.
* We verify both \u03c6 and \u03bb, but the verification of \u03c6 is actually redundant with the check of
* 100 km square validity that we did previously with the help of ROW_RESOLVER bitmask.
- * We check \u03c6 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 \u03c6 anyway in case of bug.
*/
if (isValid && zone != 0) {
- final double \u03bb = (westBoundLongitude + eastBoundLongitude) / 2;
- final double \u03c6 = (southBoundLatitude + northBoundLatitude) / 2;
- isValid = (\u03c6 >= \u03c6s - LATITUDE_BAND_HEIGHT/2) && (\u03c6 < upperBound(\u03c6s)); // See above comment.
+ isValid = (northBoundLatitude >= \u03c6s) && (southBoundLatitude < upperBound(\u03c6s));
if (isValid) {
/*
* Verification of UTM zone. We allow a tolerance for latitudes close to a pole because
@@ -1262,12 +2024,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 \u03bb = (westBoundLongitude + eastBoundLongitude) / 2;
+ final double \u03c6 = (southBoundLatitude + northBoundLatitude) / 2;
int zoneError = ZONER.zone(\u03c6, \u03bb) - 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, \u03c6)) {
- 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\u03bb = Math.IEEEremainder(\u03bb - ZONER.origin, ZONER.width); // Distance to closest zone change, in degrees of longitude.
final double cv = (minX - ZONER.easting) / (\u03bb - \u03bb0); // Approximative conversion factor from degrees to metres.
@@ -1284,7 +2049,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;
@@ -1306,7 +2071,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/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java?rev=1786763&r1=1786762&r2=1786763&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing-by-identifiers/src/main/java/org/apache/sis/referencing/gazetteer/ModifiableLocationType.java [UTF-8] Mon Mar 13 19:06:47 2017
@@ -159,7 +159,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/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java (from r1786756, 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/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java?p2=sis/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java&p1=sis/branches/JDK8/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java&r1=1786756&r2=1786763&rev=1786763&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/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/LocationViewer.java [UTF-8] Mon Mar 13 19:06:47 2017
@@ -32,7 +32,6 @@ import javax.swing.JPanel;
import org.opengis.geometry.Envelope;
import org.opengis.util.FactoryException;
import org.opengis.referencing.crs.SingleCRS;
-import org.opengis.referencing.gazetteer.Location;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.internal.referencing.j2d.IntervalRectangle;
@@ -42,6 +41,10 @@ import org.apache.sis.referencing.Common
import org.apache.sis.referencing.CRS;
import org.apache.sis.util.Debug;
+// Branch-dependent imports
+import org.apache.sis.internal.jdk8.JDK8;
+import org.opengis.referencing.gazetteer.Location;
+
/**
* A Swing panel drawing {@link Location} instances.
@@ -218,7 +221,7 @@ public final class LocationViewer extend
final MathTransform2D tr = (MathTransform2D) CRS.findOperation(
envelope.getCoordinateReferenceSystem(), displayCRS, null).getMathTransform();
final Shape shape = tr.createTransformedShape(new IntervalRectangle(envelope));
- if (locations.putIfAbsent(label, shape) != null) {
+ if (JDK8.putIfAbsent(locations, label, shape) != null) {
throw new IllegalArgumentException("A location is already defined for " + label);
}
final Rectangle2D b = shape.getBounds2D();
Modified: sis/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java?rev=1786763&r1=1786762&r2=1786763&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing-by-identifiers/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java [UTF-8] Mon Mar 13 19:06:47 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;
@@ -73,7 +80,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() {
@@ -195,7 +202,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();
}
@@ -313,36 +320,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 \u2026 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);
}
@@ -570,7 +613,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();
@@ -594,4 +640,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());
+ }
}