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/02/14 07:23:33 UTC
svn commit: r1782918 - in /sis/branches/JDK8/core:
sis-referencing/src/main/java/org/apache/sis/referencing/gazetteer/
sis-referencing/src/test/java/org/apache/sis/referencing/gazetteer/
sis-utility/src/main/java/org/apache/sis/util/ sis-utility/src/te...
Author: desruisseaux
Date: Tue Feb 14 07:23:33 2017
New Revision: 1782918
URL: http://svn.apache.org/viewvc?rev=1782918&view=rev
Log:
First working version of Military Grid Reference System encoding, UTM part only (not yet polar part).
Modified:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/gazetteer/MGRSEncoder.java
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/gazetteer/MGRSEncoderTest.java
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java
sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/StringBuildersTest.java
Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/gazetteer/MGRSEncoder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/gazetteer/MGRSEncoder.java?rev=1782918&r1=1782917&r2=1782918&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/gazetteer/MGRSEncoder.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/gazetteer/MGRSEncoder.java [UTF-8] Tue Feb 14 07:23:33 2017
@@ -30,7 +30,9 @@ import org.apache.sis.referencing.crs.De
import org.apache.sis.referencing.cs.AxesConvention;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.CRS;
+import org.apache.sis.math.MathFunctions;
import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.StringBuilders;
/**
@@ -69,7 +71,41 @@ final class MGRSEncoder {
/**
* Size of the 100,000-meter squares.
*/
- private static final double GRID_SQUARE_SIZE = 100_000;
+ static final double GRID_SQUARE_SIZE = 100_000;
+
+ /**
+ * The number of digits in a one-meter precision when formatting MGRS labels.
+ *
+ * <p><b>Invariant:</b> the following relationship must hold:
+ * {@code GRID_SQUARE_SIZE == Math.pow(10, METRE_PRECISION_DIGITS)}
+ */
+ static final int METRE_PRECISION_DIGITS = 5;
+
+ /**
+ * The first of the two letters ({@code 'I'} and {@code 'O'}) excluded in MGRS notation.
+ * This letter and all following letters shall be shifted by one character. Example:
+ *
+ * {@preformat java
+ * char band = ...;
+ * if (band >= EXCLUDE_I) {
+ * band++;
+ * if (band >= EXCLUDE_O) band++;
+ * }
+ * }
+ *
+ * or equivalently:
+ *
+ * {@preformat java
+ * char band = ...;
+ * if (band >= EXCLUDE_I && ++band >= EXCLUDE_O) band++;
+ * }
+ */
+ private static final char EXCLUDE_I = 'I';
+
+ /**
+ * The second of the two letters ({@code 'I'} and {@code 'O'}) excluded in MGRS notation.
+ */
+ private static final char EXCLUDE_O = 'O';
/**
* UTM zone of position CRS (negative for South hemisphere), or 0 if that CRS is not an UTM projection.
@@ -141,14 +177,8 @@ final class MGRSEncoder {
*/
static char latitudeBand(final double \u03c6) {
int band = 'C' + (int) ((\u03c6 - UTM_SOUTH_BOUNDS) / LATITUDE_BAND_HEIGHT);
- if (band >= 'N') {
- if (band == 'W') {
- band++; // Because the last latitude band ('X') is 12� height instead of 8�.
- } else {
- band += 2;
- }
- } else if (band >= 'I') {
- band++;
+ if (band >= EXCLUDE_I && ++band >= EXCLUDE_O && ++band == 'Y') {
+ band = 'X'; // Because the last latitude band ('X') is 12� height instead of 8�.
}
return (char) band;
}
@@ -194,11 +224,11 @@ final class MGRSEncoder {
* Encodes the given position into a MGRS label. It is caller responsibility to ensure that the
* position CRS is the same than the CRS specified at this {@code MGRSEncoder} creation time.
*
- * @param position the direct position to format as a MGRS label.
- * @param precision the precision as a power of 10.
- * @param buffer where to format the direct position.
+ * @param position the direct position to format as a MGRS label.
+ * @param digits number of digits to use for formatting the numerical part of a MGRS label.
+ * @param buffer where to format the direct position.
*/
- void encode(DirectPosition position, final double precision, final StringBuilder buffer) throws TransformException {
+ void encode(DirectPosition position, final int digits, final StringBuilder buffer) throws TransformException {
if (toNormalized != null) {
position = toNormalized.transform(position, null);
}
@@ -216,43 +246,71 @@ final class MGRSEncoder {
throw new IllegalArgumentException();
}
buffer.append(zone).append(band);
- /*
- * Columns in zone 1 are A-H, zone 2 are J-R (skipping O), zone 3 are S-Z,
- * then repeating every 3 zones. The zone number shall not take in account
- * the special cases done by the zone(\u2026) method.
- */
- final double x = position.getOrdinate(0);
- final double y = position.getOrdinate(1);
- final double cx = Math.floor(x / GRID_SQUARE_SIZE);
- final double cy = Math.floor(y / GRID_SQUARE_SIZE);
- int col = (int) cx;
- if (col < 0 || col >= 8) {
- throw new IllegalArgumentException("UTM coordinates out of range"); // TODO: localize
- }
- switch (utmZone % 3) {
- case 0: col += 'A'; break;
- case 1: col += 'J'; if (col >= 'O') col++; break;
- case 2: col += 'S'; break;
- }
- /*
- * Rows in even zones are ABCDEFGHJKLMNPQRSTUV
- * Rows in odd zones are FGHJKLMNPQRSTUVABCDE
- * Those 20 letters are repeated in a cycle.
- */
- int row = (int) cy;
- if ((zone & 1) != 0) {
- row += ('F' - 'A');
+ if (digits >= 0) {
+ /*
+ * Specification said that 100,000-meters columns are lettered from A through Z (omitting I and O)
+ * starting at the 180� meridian, proceeding easterly for 18�, and repeating for each 18� intervals.
+ * Since a UTM zone is 6� width, a 18� interval is exactly 3 standard UTM zones (not the zone number
+ * modified by the zone(\u2026) method). Columns in zone 1 are A-H, zone 2 are J-R (skipping O), zone 3
+ * are S-Z, then repeating every 3 zones.
+ */
+ final double x = position.getOrdinate(0);
+ final double y = position.getOrdinate(1);
+ final double cx = Math.floor(x / GRID_SQUARE_SIZE);
+ final double cy = Math.floor(y / GRID_SQUARE_SIZE);
+ int col = (int) cx;
+ if (col < 1 || col > 8) {
+ /*
+ * UTM northing values at the equator range from 166021 to 833979 meters approximatively
+ * (WGS84 ellipsoid). Consequently 'cx' ranges from approximatively 1.66 to 8.34, so 'c'
+ * should range from 1 to 8.
+ */
+ throw new TransformException(Errors.format(Errors.Keys.OutsideDomainOfValidity));
+ }
+ switch (utmZone % 3) { // First A-H sequence starts at zone number 1.
+ case 1: col += ('A' - 1); break;
+ case 2: col += ('J' - 1); if (col >= EXCLUDE_O) col++; break;
+ case 0: col += ('S' - 1); break;
+ }
+ /*
+ * Rows in odd zones are ABCDEFGHJKLMNPQRSTUV
+ * Rows in even zones are FGHJKLMNPQRSTUVABCDE
+ * Those 20 letters are repeated in a cycle.
+ */
+ int row = (int) cy;
+ if ((zone & 1) == 0) {
+ row += ('F' - 'A');
+ }
+ row = 'A' + (row % 20);
+ if (row >= EXCLUDE_I && ++row >= EXCLUDE_O) row++;
+ buffer.append((char) col).append((char) row);
+ /*
+ * Numerical location at the given precision.
+ * The specification requires us to truncate the number, not to round it.
+ */
+ if (digits > 0) {
+ final double precision = MathFunctions.pow10(METRE_PRECISION_DIGITS - digits);
+ append(buffer, (int) ((x - cx * GRID_SQUARE_SIZE) / precision), digits);
+ append(buffer, (int) ((y - cy * GRID_SQUARE_SIZE) / precision), digits);
+ }
}
- row = 'A' + (row % 20);
- if (row >= 'N') row += 2;
- else if (row >= 'I') row++;
- buffer.append((char) col).append((char) row);
- /*
- * Numerical location at the given precision.
- */
- final int rx = (int) Math.floor((x - cx * GRID_SQUARE_SIZE) / precision);
- final int ry = (int) Math.floor((y - cy * GRID_SQUARE_SIZE) / precision);
}
}
}
+
+ /**
+ * Appends the given value in the given buffer, padding with zero digits in order to get
+ * the specified total amount of digits.
+ */
+ private static void append(final StringBuilder buffer, final int value, int digits) throws TransformException {
+ if (value >= 0) {
+ final int p = buffer.length();
+ digits -= (buffer.append(value).length() - p);
+ if (digits >= 0) {
+ StringBuilders.repeat(buffer, p, '0', digits);
+ return;
+ }
+ }
+ throw new TransformException(Errors.format(Errors.Keys.OutsideDomainOfValidity));
+ }
}
Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java?rev=1782918&r1=1782917&r2=1782918&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystem.java [UTF-8] Tue Feb 14 07:23:33 2017
@@ -25,6 +25,8 @@ import org.opengis.geometry.DirectPositi
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.math.MathFunctions;
+import org.apache.sis.util.resources.Errors;
/**
@@ -66,9 +68,9 @@ public class MilitaryGridReferenceSystem
*/
public static class Coder {
/**
- * The precision as a power of 10.
+ * Number of digits to use for formatting the numerical part of a MGRS label.
*/
- private double precision;
+ private byte digits;
/**
* Cached information needed for building a MGRS label from a direct position in the given CRS.
@@ -84,12 +86,55 @@ public class MilitaryGridReferenceSystem
* Creates a new coder initialized to the default precision.
*/
public Coder() {
- precision = 1; // 1 meter precision.
+ digits = 5; // 1 meter precision.
buffer = new StringBuilder(12); // Length of "4QFJ12345678" sample value.
encoders = new IdentityHashMap<>();
}
/**
+ * Returns the precision of the labels formatted by this coder.
+ * This method returns one of the following values:
+ *
+ * <table class="sis">
+ * <caption>MGRS label precisions</caption>
+ * <tr><th>Precision (m)</th> <th>Label example</th></tr>
+ * <tr><td style="text-align:right">1</td> <td>4QFJ 12345 67890</td></tr>
+ * <tr><td style="text-align:right">10</td> <td>4QFJ 1234 6789</td></tr>
+ * <tr><td style="text-align:right">100</td> <td>4QFJ 123 678</td></tr>
+ * <tr><td style="text-align:right">1000</td> <td>4QFJ 12 67</td></tr>
+ * <tr><td style="text-align:right">10\u2009000</td> <td>4QFJ 1 6</td></tr>
+ * <tr><td style="text-align:right">100\u2009000</td> <td>4QFJ</td></tr>
+ * <tr><td style="text-align:right">(approximative) 1\u2009000\u2009000</td> <td>4Q</td></tr>
+ * </table>
+ *
+ * Values smaller than 1 (e.g. 0.01 for a centimetre precision) may also be returned
+ * if that value has been {@linkplain #setPrecision(double) explicitely set},
+ * but sub-metric precision are usually not used with MGRS.
+ *
+ * @return precision of formatted labels in metres.
+ */
+ public double getPrecision() {
+ return MathFunctions.pow10(MGRSEncoder.METRE_PRECISION_DIGITS - digits);
+ }
+
+ /**
+ * Sets the desired precision of the labels formatted by this coder.
+ * This method rounds the given precision to one of the power of 10
+ * documented in the {@link #getPrecision()} method.
+ *
+ * @param precision the desired precision in metres.
+ */
+ public void setPrecision(final double precision) {
+ final double p = Math.floor(Math.log10(precision));
+ if (!Double.isFinite(p)) {
+ throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "precision", precision));
+ }
+ // The -3 is an arbitrary limit to millimetre precision.
+ int n = Math.max(-3, Math.min(MGRSEncoder.METRE_PRECISION_DIGITS + 1, (int) p));
+ digits = (byte) (MGRSEncoder.METRE_PRECISION_DIGITS - n);
+ }
+
+ /**
* Encodes the given position into a MGRS label.
* The given position must have a CRS associated to it.
*
@@ -110,7 +155,7 @@ public class MilitaryGridReferenceSystem
throw new TransformException(e.toString(), e);
}
buffer.setLength(0);
- encoder.encode(position, precision, buffer);
+ encoder.encode(position, digits, buffer);
return buffer.toString();
}
Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/gazetteer/MGRSEncoderTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/gazetteer/MGRSEncoderTest.java?rev=1782918&r1=1782917&r2=1782918&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/gazetteer/MGRSEncoderTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/gazetteer/MGRSEncoderTest.java [UTF-8] Tue Feb 14 07:23:33 2017
@@ -33,6 +33,16 @@ import static org.apache.sis.internal.re
*/
public final strictfp class MGRSEncoderTest extends TestCase {
/**
+ * Verifies relationship between static fields documented in {@link MGRSEncoder}.
+ */
+ @Test
+ public void verifyInvariants() {
+ assertEquals("GRID_SQUARE_SIZE",
+ StrictMath.pow(10, MGRSEncoder.METRE_PRECISION_DIGITS),
+ MGRSEncoder.GRID_SQUARE_SIZE, STRICT);
+ }
+
+ /**
* Tests {@link MGRSEncoder#latitudeBand(double)}.
*/
@Test
@@ -52,7 +62,7 @@ public final strictfp class MGRSEncoderT
*/
@Test
public void testZone() {
- assertEquals( "4�E band T", 31, MGRSEncoder.zone(UTM.zone( 4), 4, 'T'));
+ assertEquals( "4�E band T", 31, MGRSEncoder.zone(UTM.zone( 4), 4, 'T'));
assertEquals( "4�E band V", 32, MGRSEncoder.zone(UTM.zone( 4), 4, 'V'));
assertEquals("20�E band W", 34, MGRSEncoder.zone(UTM.zone(20), 20, 'W'));
assertEquals("20�E band X", 33, MGRSEncoder.zone(UTM.zone(20), 20, 'X'));
Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java?rev=1782918&r1=1782917&r2=1782918&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/gazetteer/MilitaryGridReferenceSystemTest.java [UTF-8] Tue Feb 14 07:23:33 2017
@@ -42,12 +42,30 @@ public final strictfp class MilitaryGrid
* @throws TransformException if an error occurred while computing the MGRS label.
*/
@Test
- @org.junit.Ignore
public void testEncoding() throws TransformException {
final MilitaryGridReferenceSystem.Coder coder = new MilitaryGridReferenceSystem.Coder();
final DirectPosition2D position = new DirectPosition2D(CommonCRS.WGS84.UTM(13, 103));
- position.x = 377299;
+ position.x = 377299;
position.y = 1483035;
- assertEquals("48PUV7729883034", coder.encode(position));
+ assertEquals("precision", 1, coder.getPrecision(), STRICT);
+ assertEquals("48PUV7729983035", coder.encode(position));
+ coder.setPrecision(10);
+ assertEquals("precision", 10, coder.getPrecision(), STRICT);
+ assertEquals("48PUV77298303", coder.encode(position));
+ coder.setPrecision(304);
+ assertEquals("precision", 100, coder.getPrecision(), STRICT);
+ assertEquals("48PUV772830", coder.encode(position));
+ coder.setPrecision(1002);
+ assertEquals("precision", 1000, coder.getPrecision(), STRICT);
+ assertEquals("48PUV7783", coder.encode(position));
+ coder.setPrecision(10000);
+ assertEquals("precision", 10000, coder.getPrecision(), STRICT);
+ assertEquals("48PUV78", coder.encode(position));
+ coder.setPrecision(990004);
+ assertEquals("precision", 100000, coder.getPrecision(), STRICT);
+ assertEquals("48PUV", coder.encode(position));
+ coder.setPrecision(1000000);
+ assertEquals("precision", 1000000, coder.getPrecision(), STRICT);
+ assertEquals("48P", coder.encode(position));
}
}
Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java?rev=1782918&r1=1782917&r2=1782918&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/StringBuilders.java [UTF-8] Tue Feb 14 07:23:33 2017
@@ -161,6 +161,72 @@ public final class StringBuilders extend
}
/**
+ * Inserts the given character <var>n</var> time at the given position.
+ * This method does nothing if the given {@code count} is zero.
+ *
+ * @param buffer the buffer where to insert the character.
+ * @param offset position where to insert the characters.
+ * @param c the character to repeat.
+ * @param count number of time to repeat the given character.
+ * @throws NullPointerException if the given buffer is null.
+ * @throws IndexOutOfBoundsException if the given index is invalid.
+ * @throws IllegalArgumentException if the given count is negative.
+ *
+ * @since 0.8
+ */
+ public static void repeat(final StringBuilder buffer, final int offset, final char c, final int count) {
+ ArgumentChecks.ensureNonNull("buffer", buffer);
+ switch (count) {
+ case 0: break;
+ case 1: buffer.insert(offset, c); break;
+ default: {
+ ArgumentChecks.ensurePositive("count", count);
+ final CharSequence r;
+ switch (c) {
+ case ' ': r = CharSequences.spaces(count); break;
+ case '0': r = Repeat.ZERO; break;
+ default: r = new Repeat(c, count); break;
+ }
+ buffer.insert(offset, r, 0, count);
+ break;
+ }
+ }
+ }
+
+ /**
+ * A sequence of a constant character. This implementation does not perform any argument
+ * check since it is for {@link StringBuilder#append(CharSequence, int, int)} usage only.
+ * The intend is to allow {@code StringBuilder} to append the characters in one operation
+ * instead than looping on {@link StringBuilder#insert(int, char)} (which would require
+ * memory move on each call).
+ */
+ private static final class Repeat implements CharSequence {
+ /** An infinite sequence of {@code '0'} character. */
+ static final Repeat ZERO = new Repeat('0', Integer.MAX_VALUE);
+
+ /** The character to repeat. */
+ private final char c;
+
+ /** Number of times the character is repeated. */
+ private final int n;
+
+ /** Creates a new sequence of constant character. */
+ Repeat(final char c, final int n) {
+ this.c = c;
+ this.n = n;
+ }
+
+ /** Returns the number of times the character is repeated. */
+ @Override public int length() {return n;}
+
+ /** Returns the constant character, regardless the index. */
+ @Override public char charAt(int i) {return c;}
+
+ /** Returns a sequence of the same constant character but different length. */
+ @Override public CharSequence subSequence(int start, int end) {return new Repeat(c, end - start);}
+ }
+
+ /**
* Trims the fractional part of the given formatted number, provided that it doesn't change
* the value. This method assumes that the number is formatted in the US locale, typically
* by the {@link Double#toString(double)} method.
@@ -183,11 +249,9 @@ public final class StringBuilders extend
public static void trimFractionalPart(final StringBuilder buffer) {
ArgumentChecks.ensureNonNull ("buffer", buffer);
for (int i=buffer.length(); i > 0;) {
- final int c = buffer.codePointBefore(i);
- i -= charCount(c);
- switch (c) {
+ switch (buffer.charAt(--i)) { // No need to use Unicode code points here.
case '0': continue;
- case '.': buffer.setLength(i); // Fall through
+ case '.': buffer.setLength(i); // Fall through
default : return;
}
}
Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/StringBuildersTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/StringBuildersTest.java?rev=1782918&r1=1782917&r2=1782918&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/StringBuildersTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/util/StringBuildersTest.java [UTF-8] Tue Feb 14 07:23:33 2017
@@ -80,6 +80,26 @@ public final strictfp class StringBuilde
}
/**
+ * Tests the {@link StringBuilders#repeat(StringBuilder, int, char, int)} method.
+ *
+ * @since 0.8
+ */
+ @Test
+ public void testRepeat() {
+ final StringBuilder buffer = new StringBuilder("AB12");
+ repeat(buffer, 2, 'C', 0);
+ assertEquals("AB12", buffer.toString());
+ repeat(buffer, 2, 'C', 1);
+ assertEquals("ABC12", buffer.toString());
+ repeat(buffer, 3, '0', 4);
+ assertEquals("ABC000012", buffer.toString());
+ repeat(buffer, 6, ' ', 2);
+ assertEquals("ABC000 012", buffer.toString());
+ repeat(buffer, 6, '.', 3);
+ assertEquals("ABC000... 012", buffer.toString());
+ }
+
+ /**
* Tests the {@link StringBuilders#trimFractionalPart(StringBuilder)} method.
*/
@Test