Skip to content

Commit 6f64c8c

Browse files
2.1.0 Micrometer precision encoding and decoding
1 parent 26da1b5 commit 6f64c8c

File tree

4 files changed

+87
-89
lines changed

4 files changed

+87
-89
lines changed

src/main/java/com/mapcode/Decoder.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -717,11 +717,11 @@ private static int decodeBase31(final String code) {
717717
}
718718

719719
@Nonnull
720-
private static Point decodeExtension(final int y, final int x, final int dividerx4, final int dividery, final String extrapostfix, final int lon_offset4) {
721-
final double dividerx = dividerx4 / 4;
720+
private static Point decodeExtension(final int y, final int x, final int dividerx4, final int dividery0, final String extrapostfix, final int lon_offset4) {
721+
final double dividerx = dividerx4 / 4.0, dividery = (double) dividery0;
722722
double processor = 1;
723-
double lon32 = 0;
724-
double lat32 = 0;
723+
int lon32 = 0;
724+
int lat32 = 0;
725725
boolean odd = false;
726726
int idx = 0;
727727
// decode up to 8 characters

src/main/java/com/mapcode/Mapcode.java

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -399,19 +399,6 @@ public static boolean containsTerritory(@Nonnull final String mapcode) throws Il
399399
* location used for encoding the mapcode.
400400
*/
401401
private static final double[] PRECISION_0_MAX_OFFSET_METERS = {
402-
// @@@ problem: encode() rounds to millionths, introducing a consistent error !
403-
7.50,
404-
1.50,
405-
0.39,
406-
0.19,
407-
0.149,
408-
0.14,
409-
0.14,
410-
0.14,
411-
0.14,
412-
};
413-
414-
/* @@@ correct values TODO
415402
7.49, // PRECISION_0: 7.49 meters or less 7.5 m
416403
1.45, // PRECISION_1: 1.45 meters or less 1.5 m
417404
0.251, // PRECISION_2: 25.1 cm or less 25 cm
@@ -421,8 +408,7 @@ public static boolean containsTerritory(@Nonnull final String mapcode) throws Il
421408
0.000279, // PRECISION_6: 279 micrometer or less 1/3 mm
422409
0.0000514, // PRECISION_7: 51.4 micrometer or less 1/20 mm
423410
0.0000093 // PRECISION_8: 9.3 micrometer or less 1/100 mm
424-
*/
425-
411+
};
426412

427413
/**
428414
* Get a safe maximum for the distance between a decoded mapcode and its original

src/main/java/com/mapcode/Point.java

Lines changed: 63 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public static Point fromDeg(final double latDeg, final double lonDeg) {
7171
*/
7272
public double getLatDeg() {
7373
assert defined;
74-
return latDeg;
74+
return (lat32 / MICRODEG_TO_DEG_FACTOR) + (fraclat / MICROLAT_MAX_PRECISION_FACTOR);
7575
}
7676

7777
/**
@@ -81,7 +81,7 @@ public double getLatDeg() {
8181
*/
8282
public double getLonDeg() {
8383
assert defined;
84-
return lonDeg;
84+
return (lon32 / MICRODEG_TO_DEG_FACTOR) + (fraclon / MICROLON_MAX_PRECISION_FACTOR);
8585
}
8686

8787

@@ -90,14 +90,14 @@ public double getLonDeg() {
9090
*/
9191
public double LonFractions() {
9292
assert defined;
93-
return 0;
93+
return fraclon;
9494
}
9595
/**
9696
* Returns "fractions", which is a whole number of 1/MICROLAT_MAX_PRECISION_FACTORth degrees versus the millionths of degrees
9797
*/
9898
public double LatFractions() {
9999
assert defined;
100-
return 0;
100+
return fraclat;
101101
}
102102

103103

@@ -146,22 +146,19 @@ public static double distanceInMeters(@Nonnull final Point p1, @Nonnull final Po
146146
checkNonnull("p1", p1);
147147
checkNonnull("p2", p2);
148148

149-
final Point from;
150-
final Point to;
151-
if (p1.lonDeg <= p2.lonDeg) {
152-
from = p1;
153-
to = p2;
154-
} else {
155-
from = p2;
156-
to = p1;
157-
}
149+
final double latDeg1 = p1.getLatDeg();
150+
final double latDeg2 = p2.getLatDeg();
151+
double lonDeg1 = p1.getLonDeg();
152+
double lonDeg2 = p2.getLonDeg();
153+
154+
if (lonDeg1 < 0 && lonDeg2 > 1) { lonDeg1 += 360; }
155+
if (lonDeg2 < 0 && lonDeg1 > 1) { lonDeg2 += 360; }
158156

159157
// Calculate mid point of 2 latitudes.
160-
final double avgLat = from.latDeg + ((to.latDeg - from.latDeg) / 2.0);
158+
final double avgLat = (p1.getLatDeg() + p2.getLatDeg()) / 2.0;
161159

162-
final double deltaLonDeg360 = Math.abs(to.lonDeg - from.lonDeg);
163-
final double deltaLonDeg = ((deltaLonDeg360 <= 180.0) ? deltaLonDeg360 : (360.0 - deltaLonDeg360));
164-
final double deltaLatDeg = Math.abs(to.latDeg - from.latDeg);
160+
final double deltaLatDeg = latDeg1 - latDeg2;
161+
final double deltaLonDeg = lonDeg1 - lonDeg2;
165162

166163
// Meters per longitude is fixed; per latitude requires * cos(avg(lat)).
167164
final double deltaXMeters = degreesLonToMetersAtLat(deltaLonDeg, avgLat);
@@ -186,13 +183,13 @@ public static double metersToDegreesLonAtLat(final double eastMeters, final doub
186183
@Nonnull
187184
@Override
188185
public String toString() {
189-
return defined ? ("(" + latDeg + ", " + lonDeg + ')') : "undefined";
186+
return defined ? ("(" + getLatDeg() + ", " + getLonDeg() + ')') : "undefined";
190187
}
191188

192189
@SuppressWarnings("NonFinalFieldReferencedInHashCode")
193190
@Override
194191
public int hashCode() {
195-
return Arrays.hashCode(new Object[]{latDeg, lonDeg, defined});
192+
return Arrays.hashCode(new Object[]{getLatDeg(), getLonDeg(), defined});
196193
}
197194

198195
@SuppressWarnings("NonFinalFieldReferenceInEquals")
@@ -205,16 +202,20 @@ public boolean equals(final Object obj) {
205202
return false;
206203
}
207204
final Point that = (Point) obj;
208-
return (Double.compare(this.latDeg, that.latDeg) == 0) &&
209-
(Double.compare(this.lonDeg, that.lonDeg) == 0) &&
210-
(this.defined == that.defined);
205+
return (this.lat32 == that.lat32) &&
206+
(this.lon32 == that.lon32) &&
207+
(Double.compare(this.fraclat, that.fraclat) == 0) &&
208+
(Double.compare(this.fraclon, that.fraclon) == 0) &&
209+
(this.defined == that.defined);
211210
}
212211

213212
/**
214213
* Private data.
215214
*/
216-
private double latDeg; // Latitude, normal range -90..90, but not enforced.
217-
private double lonDeg; // Longitude, normal range -180..180, but not enforced.
215+
private int lat32; // whole nr of MICRODEG_TO_DEG_FACTOR
216+
private int lon32; // whole nr of MICRODEG_TO_DEG_FACTOR
217+
private double fraclat; // whole nr of MICROLAT_MAX_PRECISION_FACTOR, relative to lat32
218+
private double fraclon; // whole nr of MICROLON_MAX_PRECISION_FACTOR, relative to lon32
218219

219220
/**
220221
* Points can be "undefined" within the mapcode implementation, but never outside of that.
@@ -227,22 +228,30 @@ public boolean equals(final Object obj) {
227228
* Private constructors.
228229
*/
229230
private Point() {
230-
latDeg = Double.NaN;
231-
lonDeg = Double.NaN;
232231
defined = false;
233232
}
234233

235234
private Point(final double latDeg, final double lonDeg, final boolean wrap) {
236-
if (wrap) {
237-
this.latDeg = mapToLat(latDeg);
238-
this.lonDeg = mapToLon(lonDeg);
239-
assert (LON_DEG_MIN <= this.lonDeg) && (this.lonDeg <= LON_DEG_MAX) : "lon [-180..180]: " + this.lonDeg;
240-
assert (LAT_DEG_MIN <= this.latDeg) && (this.latDeg <= LAT_DEG_MAX) : "lat [-90..90]: " + this.latDeg;
241-
} else {
242-
this.latDeg = latDeg;
243-
this.lonDeg = lonDeg;
244-
}
245-
this.defined = true;
235+
236+
double lat = latDeg + 90;
237+
if (lat < 0) { lat = 0; } else if (lat > 180) { lat = 180; }
238+
// lat now [0..180]
239+
lat *= MICROLAT_MAX_PRECISION_FACTOR;
240+
fraclat = Math.floor(lat + 0.1);
241+
double f = fraclat / MAX_PRECISION_FACTOR;
242+
lat32 = (int) f;
243+
fraclat -= ((double) lat32 * MAX_PRECISION_FACTOR);
244+
lat32 -= 90000000;
245+
246+
double lon = lonDeg - (360.0 * Math.floor(lonDeg / 360)); // lon now in [0..360>
247+
lon *= MICROLON_MAX_PRECISION_FACTOR;
248+
fraclon = Math.floor(lon + 0.1);
249+
f = fraclon / FRACLON_PRECISION_FACTOR;
250+
lon32 = (int)f;
251+
fraclon -= ((double) lon32 * FRACLON_PRECISION_FACTOR);
252+
if (lon32 >= 180000000) { lon32 -= 360000000; }
253+
254+
defined = true;
246255
}
247256

248257
/**
@@ -255,7 +264,13 @@ private Point(final double latDeg, final double lonDeg, final boolean wrap) {
255264

256265
@Nonnull
257266
static Point fromMicroDeg(final int latMicroDeg, final int lonMicroDeg) {
258-
return new Point(microDegToDeg(latMicroDeg), microDegToDeg(lonMicroDeg), false);
267+
Point p = new Point();
268+
p.lat32 = latMicroDeg;
269+
p.lon32 = lonMicroDeg;
270+
p.fraclat = 0;
271+
p.fraclon = 0;
272+
p.defined = true;
273+
return p;
259274
}
260275

261276
/**
@@ -265,7 +280,7 @@ static Point fromMicroDeg(final int latMicroDeg, final int lonMicroDeg) {
265280
*/
266281
int getLatMicroDeg() {
267282
assert defined;
268-
return degToMicroDeg(latDeg);
283+
return lat32;
269284
}
270285

271286
/**
@@ -275,12 +290,12 @@ int getLatMicroDeg() {
275290
*/
276291
int getLonMicroDeg() {
277292
assert defined;
278-
return degToMicroDeg(lonDeg);
293+
return lon32;
279294
}
280295

281296
static int degToMicroDeg(final double deg) {
282297
//noinspection NumericCastThatLosesPrecision
283-
return (int) Math.round(deg * MICRODEG_TO_DEG_FACTOR);
298+
return (int) Math.floor(deg * MICRODEG_TO_DEG_FACTOR);
284299
}
285300

286301
static double microDegToDeg(final int microDeg) {
@@ -290,38 +305,18 @@ static double microDegToDeg(final int microDeg) {
290305
@Nonnull
291306
Point wrap() {
292307
if (defined) {
293-
this.latDeg = mapToLat(latDeg);
294-
this.lonDeg = mapToLon(lonDeg);
308+
// Cut latitude to [-90, 90].
309+
if (lat32 < -90000000) { lat32 = -90000000; fraclat=0; }
310+
if (lat32 > 90000000) { lat32 = 90000000; fraclat=0; }
311+
// Map longitude to [-180, 180). Values outside this range are wrapped to this range.
312+
if (lon32 < -180000000 || lon32 >= 180000000 ) {
313+
lon32 -= 360 * (lon32 / 360); // [0..360)
314+
if (lon32 >= 180000000) { lon32 -= 360000000; } // [-180,180)
315+
}
295316
}
296317
return this;
297318
}
298319

299-
/**
300-
* Map a longitude to [-90, 90]. Values outside this range are limited to this range.
301-
*
302-
* @param value Latitude, any range.
303-
* @return Limited to [-90, 90].
304-
*/
305-
static double mapToLat(final double value) {
306-
return (value < -90.0) ? -90.0 : ((value > 90.0) ? 90.0 : value);
307-
}
308-
309-
/**
310-
* Map a longitude to [-180, 180). Values outside this range are wrapped to this range.
311-
*
312-
* @param value Longitude, any range.
313-
* @return Mapped to [-180, 180).
314-
*/
315-
static double mapToLon(final double value) {
316-
if ( value > -180 && value < 180 )
317-
return value; // already in range
318-
double lon = (((((value >= 0) ? value : -value) + 180) % 360) - 180) * ((value >= 0) ? 1.0 : -1.0);
319-
if (Double.compare(lon, 180.0) == 0) {
320-
return -180;
321-
}
322-
return lon;
323-
}
324-
325320
/**
326321
* Create an undefined points. No latitude or longitude can be obtained from it.
327322
* Only within the mapcode implementation points can be undefined, so this methods is package private.
@@ -338,8 +333,6 @@ static Point undefined() {
338333
* Only within the mapcode implementation points can be undefined, so this methods is package private.
339334
*/
340335
void setUndefined() {
341-
latDeg = Double.NaN;
342-
lonDeg = Double.NaN;
343336
defined = false;
344337
}
345338

src/test/java/com/mapcode/EncoderTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,25 @@ public void encodeToShortest1() throws Exception {
170170

171171
final Mapcode result = MapcodeCodec.encodeToShortest(lat, lon, Territory.NLD);
172172
assertEquals("NLD 49.4V", result.getCodeWithTerritory());
173+
174+
// test extremely precise encoding
175+
{
176+
double lat2 = 52.3, lon2 = 4.908;
177+
Mapcode m = MapcodeCodec.encodeToShortest(lat2, lon2, Territory.fromString("NLD") );
178+
assertEquals("NLD GG.NBC-SHR33333", m.getCodeWithTerritory(8));
179+
lat2 = 52.3000004; lon2 = 4.9080004;
180+
m = MapcodeCodec.encodeToShortest(lat2, lon2, Territory.fromString("NLD") );
181+
assertEquals("NLD GG.NBC-SHSS1010", m.getCodeWithTerritory(8));
182+
lat2 = 52.299999999; lon2 = 4.907999999;
183+
m = MapcodeCodec.encodeToShortest(lat2, lon2, Territory.fromString("NLD") );
184+
assertEquals("NLD GG.NBC-SHLWXWQB", m.getCodeWithTerritory(8));
185+
lat2 = 52.29993200000; lon2 = 4.90786600000;
186+
m = MapcodeCodec.encodeToShortest(lat2, lon2, Territory.fromString("NLD") );
187+
assertEquals("NLD GG.NBC-00000000", m.getCodeWithTerritory(8));
188+
lat2 = 52.29993200000; lon2 = 4.9078659999999;
189+
m = MapcodeCodec.encodeToShortest(lat2, lon2, Territory.fromString("NLD") );
190+
assertEquals("NLD GG.N98-45454545", m.getCodeWithTerritory(8));
191+
}
173192
}
174193

175194
@Test

0 commit comments

Comments
 (0)