Skip to content

Commit 1dda594

Browse files
2.1.1 Pure integer math high-precision and encode(decode(m))=m
1 parent 633652d commit 1dda594

File tree

4 files changed

+184
-159
lines changed

4 files changed

+184
-159
lines changed

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

Lines changed: 74 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -22,94 +22,34 @@
2222
import javax.annotation.Nonnull;
2323
import javax.annotation.Nullable;
2424

25+
// simple class to represent all the coordinates that would deliver a particular mapcode
2526
class DecodeLimits {
26-
27-
// construct empty
28-
public void DecodeLimits() {
29-
defined = false;
30-
}
27+
public Point min;
28+
public Point max;
3129

3230
// generate upper and lower limits based on x and y, and delta's
33-
public void setFromDelta(final double y, final double x, final double yDelta, final double xDelta) {
34-
if (xDelta < 0) {
35-
maxx = x;
36-
minx = x + xDelta;
37-
}
38-
else {
39-
minx = x;
40-
maxx = x + xDelta;
41-
}
31+
public void setFromFractions(final double y, final double x, final double yDelta, final double xDelta) {
32+
assert (xDelta >= 0);
4233
if (yDelta < 0) {
43-
maxy = y;
44-
miny = y + yDelta;
34+
min = Point.fromFractionDeg(y + yDelta, x);
35+
max = Point.fromFractionDeg(y, x + xDelta);
4536
}
4637
else {
47-
miny = y;
48-
maxy = y + yDelta;
38+
min = Point.fromFractionDeg(y, x);
39+
max = Point.fromFractionDeg(y + yDelta, x + xDelta);
4940
}
50-
defined = true;
51-
}
52-
53-
// get minimum y (inclusive)
54-
public double getMinY() {
55-
assert defined;
56-
return miny;
57-
}
58-
59-
// get maximum y (exclusive)
60-
public double getMaxY() {
61-
assert defined;
62-
return maxy;
63-
}
64-
65-
// get minimum x (inclusive)
66-
public double getMinX() {
67-
assert defined;
68-
return minx;
69-
}
70-
71-
// get maximum x (exclusive)
72-
public double getMaxX() {
73-
assert defined;
74-
return maxx;
75-
}
76-
77-
// cut X to specified maximum, if necessary. Returns false if cut results in an empty range
78-
public boolean cutMaxX(int thatMaxX) {
79-
if (maxx >= thatMaxX) { maxx = thatMaxX; }
80-
return (maxx > minx);
8141
}
8242

83-
// cut Y to specified maximum, if necessary. Returns false if cut results in an empty range
84-
public boolean cutMaxY(int thatMaxY) {
85-
if (maxy >= thatMaxY) { maxy = thatMaxY; }
86-
return (maxy > miny);
87-
}
88-
89-
// cut Y to specified minimum, if necessary. Returns false if cut results in an empty range
90-
public boolean cutMinY(int thatMinY) {
91-
if (miny <= thatMinY) { miny = thatMinY; }
92-
return (maxy >= miny);
93-
}
94-
95-
// cut X to specified minimum, if necessary. Returns false if cut results in an empty range
96-
public boolean cutMinX(int thatMinX) {
97-
if (minx <= thatMinX) { minx = thatMinX; }
98-
return (maxx >= minx);
43+
@Nonnull
44+
public Point midPoint() {
45+
return min.getMidPoint(max);
9946
}
10047

10148
@Nonnull
10249
@Override
10350
public String toString() {
104-
return defined ? ("(" + miny + ", " + minx + ")-(" + maxy + ", " + maxx + ")") : "undefined";
51+
return ("(" + min + ")-(" + max + ")");
10552
}
106-
107-
// private parts
108-
private boolean defined;
109-
private double miny;
110-
private double maxy;
111-
private double minx;
112-
private double maxx;
11353
}
11454

11555
class Decoder {
@@ -202,7 +142,7 @@ static Point decode(@Nonnull final String argMapcode,
202142
Data.getBoundaries(i).getMinX(), Data.getBoundaries(i).getMinY(),
203143
Data.getBoundaries(i).getMaxX(), Data.getBoundaries(i).getMaxY(),
204144
i, extrapostfix, decodeLimits);
205-
145+
206146
if (Data.isRestricted(i) && result.isDefined()) {
207147
boolean fitssomewhere = false;
208148
int j;
@@ -222,24 +162,28 @@ static Point decode(@Nonnull final String argMapcode,
222162
if (!Data.isRestricted(j)) {
223163
int minx = Data.getBoundaries(j).getMinX();
224164
int maxx = Data.getBoundaries(j).getMaxX();
225-
if ((maxx < 0) && (result.getLonDeg() > 0)) {
165+
if ((maxx < 0) && (result.getLonMicroDeg() > 1)) {
226166
minx += 360000000;
227167
maxx += 360000000;
228168
}
169+
else if ((maxx > 1) && (result.getLonMicroDeg() < 0)) {
170+
minx -= 360000000;
171+
maxx -= 360000000;
172+
}
229173
final int miny = Data.getBoundaries(j).getMinY();
230174
final int maxy = Data.getBoundaries(j).getMaxY();
231175

232176
// force pt within decodeLimits
233177
Point pt = Point.fromPoint(result);
234-
if (miny <= decodeLimits.getMaxY()) { pt.setMinLatToMicroDeg(miny); }
235-
if (maxy > decodeLimits.getMinY()) { pt.setMaxLatToMicroDeg(maxy); }
236-
if (minx <= decodeLimits.getMaxX()) { pt.setMinLonToMicroDeg(minx); }
237-
if (maxx > decodeLimits.getMinX()) { pt.setMaxLonToMicroDeg(maxx); }
238-
178+
if (miny * Point.MICROLAT_TO_FRACTIONS_FACTOR < decodeLimits.max.getLatFractions()) { pt.setMinLatToMicroDeg(miny); }
179+
if (maxy * Point.MICROLAT_TO_FRACTIONS_FACTOR > decodeLimits.min.getLatFractions()) { pt.setMaxLatToMicroDeg(maxy); }
180+
if (minx * Point.MICROLON_TO_FRACTIONS_FACTOR < decodeLimits.max.getLonFractions()) { pt.setMinLonToMicroDeg(minx); }
181+
if (maxx * Point.MICROLON_TO_FRACTIONS_FACTOR > decodeLimits.min.getLonFractions()) { pt.setMaxLonToMicroDeg(maxx); }
182+
239183
// better?
240-
if ( pt.getLatDeg()*1000000 > decodeLimits.getMinY() && pt.getLatDeg()*1000000 < decodeLimits.getMaxY() &&
241-
pt.getLonDeg()*1000000 > decodeLimits.getMinX() && pt.getLonDeg()*1000000 < decodeLimits.getMaxX() &&
242-
Data.getBoundaries(j).containsPoint(pt))
184+
if ( pt.getLatFractions() >= decodeLimits.min.getLatFractions() && pt.getLatFractions() < decodeLimits.max.getLatFractions() &&
185+
pt.getLonFractions() >= decodeLimits.min.getLonFractions() && pt.getLonFractions() < decodeLimits.max.getLonFractions() &&
186+
Data.getBoundaries(j).containsPoint(pt))
243187
{
244188
result = Point.fromPoint(pt);
245189
fitssomewhere = true;
@@ -250,6 +194,7 @@ static Point decode(@Nonnull final String argMapcode,
250194
} //FORCE_RECODE
251195

252196
if (!fitssomewhere) {
197+
LOG.info("FAILED {}-{} decode {} fits no smaller rectangle",mapcode,extrapostfix,result);
253198
result.setUndefined();
254199
}
255200
}
@@ -277,11 +222,8 @@ static Point decode(@Nonnull final String argMapcode,
277222
}
278223

279224
if (result.isDefined()) {
280-
if (result.getLonMicroDeg() > 180000000) {
281-
result = Point.fromMicroDeg(result.getLatMicroDeg(), result.getLonMicroDeg() - 360000000);
282-
} else if (result.getLonMicroDeg() < -180000000) {
283-
result = Point.fromMicroDeg(result.getLatMicroDeg(), result.getLonMicroDeg() + 360000000);
284-
}
225+
226+
result.wrap();
285227

286228
// LIMIT_TO_OUTRECT : make sure it fits the country
287229
if (ccode != CCODE_EARTH) {
@@ -291,6 +233,7 @@ static Point decode(@Nonnull final String argMapcode,
291233
final int xdiv8 = Common.xDivider(miny, maxy) / 4;
292234
// should be /8 but there's some extra margin
293235
if (!mapcoderRect.extendBounds(xdiv8, 60).containsPoint(result)) {
236+
LOG.info("FAILED {}-{}: result {} not in encompassing {}",mapcode,extrapostfix,result,mapcoderRect);
294237
result.setUndefined(); // decodes outside the official territory
295238
// limit
296239
}
@@ -406,7 +349,7 @@ public Unicode2Ascii(final char min, final char max, @Nonnull final String conve
406349

407350
@Nonnull
408351
private static Point decodeGrid(final String str, final int minx, final int miny, final int maxx, final int maxy,
409-
final int m, final String extrapostfix, DecodeLimits decodeLimits) {
352+
final int m, final String extrapostfix, @Nullable DecodeLimits decodeLimits) {
410353
// for a well-formed result, and integer variables
411354
String result = str;
412355
int relx;
@@ -483,7 +426,7 @@ private static Point decodeGrid(final String str, final int minx, final int miny
483426

484427
Point pt = Point.fromMicroDeg(cornery,cornerx);
485428
if (!(Data.getBoundaries(m).containsPoint(pt))) {
486-
LOG.info("*** decodeGrid({}) : {} not in {}", str, pt, Data.getBoundaries(m));
429+
LOG.info("FAILED decodeGrid({}) : {} not in {}", str, pt, Data.getBoundaries(m));
487430
return Point.undefined(); // already out of range
488431
}
489432

@@ -588,7 +531,7 @@ private static Point decodeNameless(final String str, final int firstrec, final
588531

589532
if (dx >= xSIDE) // else out-of-range!
590533
{
591-
LOG.error("*** decodeNameless({}) : dx {} > xSIDE {}", str, dx, xSIDE);
534+
LOG.error("FAILED decodeNameless({}) : dx {} > xSIDE {}", str, dx, xSIDE);
592535
return Point.undefined(); // return undefined (out of range!)
593536
}
594537

@@ -616,7 +559,7 @@ private static Point decodeAutoHeader(final String input, final int m, final Str
616559
i = m;
617560
while (true) {
618561
if ((Data.recType(i)<2) || (Data.calcCodex(i) != codexm)) {
619-
LOG.error("*** decodeAutoHeader({}) : out of {} records", input, codexm);
562+
LOG.error("FAILED decodeAutoHeader({}) : out of {} records", input, codexm);
620563
return Point.undefined(); // return undefined
621564
}
622565

@@ -655,7 +598,7 @@ private static Point decodeAutoHeader(final String input, final int m, final Str
655598
final int cornerx = minx + (vx * dividerx);
656599

657600
if (cornerx < minx || cornerx >= maxx || cornery < miny || cornery > maxy) {
658-
LOG.error("*** decodeAutoHeader({}) : corner {}, {} out of bounds", input, cornery, cornerx);
601+
LOG.error("FAILED decodeAutoHeader({}) : corner {}, {} out of bounds", input, cornery, cornerx);
659602
return Point.undefined(); // corner out of bounds
660603
}
661604

@@ -871,10 +814,10 @@ private static int decodeBase31(final String code) {
871814
}
872815

873816
@Nonnull
874-
private static Point decodeExtension(@Nullable DecodeLimits decodeLimits, final int y, final int x, final int dividerx4, final int dividery0,
875-
final String extrapostfix, final int lon_offset4, final int extremeLat32, final int maxLon32) {
876-
if (decodeLimits==null) decodeLimits = new DecodeLimits();
877-
final double dividerx = dividerx4 / 4.0, dividery = (double) dividery0;
817+
private static Point decodeExtension(@Nullable DecodeLimits decodeLimits, final int y, final int x, final int dividerx0, final int dividery0,
818+
final String extrapostfix, final int lon_offset4, final int extremeLatMicroDeg, final int maxLonMicroDeg) {
819+
if (decodeLimits == null) { decodeLimits = new DecodeLimits(); }
820+
double dividerx4 = (double) dividerx0, dividery = (double) dividery0;
878821
double processor = 1;
879822
int lon32 = 0;
880823
int lat32 = 0;
@@ -886,7 +829,7 @@ private static Point decodeExtension(@Nullable DecodeLimits decodeLimits, final
886829
int c1 = (int) extrapostfix.charAt(idx++);
887830
c1 = DECODE_CHARS[c1];
888831
if (c1 < 0 || c1 == 30) {
889-
LOG.error("*** decodeExtension({}) : illegal c1 {}", extrapostfix, c1);
832+
LOG.error("FAILED decodeExtension({}) : illegal c1 {}", extrapostfix, c1);
890833
return Point.undefined();
891834
}
892835
final int y1 = c1 / 5;
@@ -897,7 +840,7 @@ private static Point decodeExtension(@Nullable DecodeLimits decodeLimits, final
897840
int c2 = (int) extrapostfix.charAt(idx++);
898841
c2 = DECODE_CHARS[c2];
899842
if (c2 < 0 || c2 == 30) {
900-
LOG.error("*** decodeExtension({}) : illegal c2 {}", extrapostfix, c2);
843+
LOG.error("FAILED decodeExtension({}) : illegal c2 {}", extrapostfix, c2);
901844
return Point.undefined();
902845
}
903846
y2 = c2 / 6;
@@ -913,35 +856,50 @@ private static Point decodeExtension(@Nullable DecodeLimits decodeLimits, final
913856
lat32 = lat32 * 30 + (y1 * 5) + y2;
914857
}
915858

916-
double lat = y + ((lat32 * dividery) / processor);
917-
double lon = x + ((lon32 * dividerx) / processor) + ( lon_offset4 / 4.0 );
859+
while (processor < Point.MAX_PRECISION_FACTOR) {
860+
dividerx4 *= 30;
861+
dividery *= 30;
862+
processor *= 30;
863+
}
864+
865+
double lon4 = (x * 4 * Point.MAX_PRECISION_FACTOR) + ((lon32 * dividerx4)) + (lon_offset4 * Point.MAX_PRECISION_FACTOR);
866+
double lat1 = (y * Point.MAX_PRECISION_FACTOR) + ((lat32 * dividery ));
918867

919868
// determine the range of coordinates that are encode to this mapcode
920869
if (odd) { // odd
921-
decodeLimits.setFromDelta(lat, lon, (dividery / (processor / 5)), (dividerx / (processor / 6)) );
870+
decodeLimits.setFromFractions(lat1, lon4, 5 * dividery, 6 * dividerx4);
922871
} else { // not odd
923-
decodeLimits.setFromDelta(lat, lon, (dividery / processor), (dividerx / processor) );
872+
decodeLimits.setFromFractions(lat1, lon4, dividery, dividerx4);
924873
} // not odd
925874

926875
// FORCE_RECODE - restrict the coordinate range to the extremes that were provided
927-
if (decodeLimits.cutMaxX(maxLon32)==false) {
928-
LOG.error("*** decodeExtension({}) : cutMaxX {}", extrapostfix, maxLon32);
929-
return Point.undefined();
930-
}
931-
if (dividery >= 0 )
932-
{
933-
if (decodeLimits.cutMaxY(extremeLat32)==false) {
934-
LOG.error("*** decodeExtension({}) : cutMaxY {}", extrapostfix, extremeLat32);
876+
if (decodeLimits.max.getLonFractions() > (maxLonMicroDeg * Point.MICROLON_TO_FRACTIONS_FACTOR)) {
877+
decodeLimits.max.setLonMicroDeg(maxLonMicroDeg);
878+
if (decodeLimits.max.getLonFractions() <= decodeLimits.min.getLonFractions()) {
879+
LOG.error("FAILED decodeExtension({}) : cutMaxX {}", extrapostfix, maxLonMicroDeg);
935880
return Point.undefined();
936881
}
882+
}
883+
884+
if (dividery >= 0 ) {
885+
if (decodeLimits.max.getLatFractions() > (extremeLatMicroDeg * Point.MICROLAT_TO_FRACTIONS_FACTOR)) {
886+
decodeLimits.max.setLatMicroDeg(extremeLatMicroDeg);
887+
if (decodeLimits.max.getLatFractions() <= decodeLimits.min.getLatFractions()) {
888+
LOG.error("FAILED decodeExtension({}) : cutMaxY {}", extrapostfix, extremeLatMicroDeg);
889+
return Point.undefined();
890+
}
891+
}
937892
} else { // dividery < 0
938-
if (decodeLimits.cutMinY(extremeLat32)==false) {
939-
LOG.error("*** decodeExtension({}) : cutMinY {}", extrapostfix, extremeLat32);
940-
return Point.undefined();
893+
if (decodeLimits.min.getLatFractions() < (extremeLatMicroDeg * Point.MICROLAT_TO_FRACTIONS_FACTOR)) {
894+
decodeLimits.min.setLatMicroDeg(extremeLatMicroDeg);
895+
if (decodeLimits.max.getLatFractions() < decodeLimits.min.getLatFractions()) {
896+
LOG.error("FAILED decodeExtension({}) : cutMinY {}", extrapostfix, extremeLatMicroDeg);
897+
return Point.undefined();
898+
}
941899
}
942900
}
943901

944-
// return a coordinate exactly in the middle of the range
945-
return Point.fromDeg( (decodeLimits.getMinY() + decodeLimits.getMaxY()) / 2000000.0, (decodeLimits.getMinX() + decodeLimits.getMaxX()) / 2000000.0 );
902+
// return the coordinate in the center of the mapcode-defined zone
903+
return decodeLimits.midPoint();
946904
}
947905
}

src/main/java/com/mapcode/Encoder.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ private static String encodeExtension(final Point pointToEncode, final int extra
143143

144144
double factorx = Point.MAX_PRECISION_FACTOR * dividerx4;
145145
double factory = Point.MAX_PRECISION_FACTOR * dividery;
146-
double valx = (Point.MAX_PRECISION_FACTOR * extrax4) + pointToEncode.getLonFractions();
147-
double valy = (Point.MAX_PRECISION_FACTOR * extray ) + (ydirection * pointToEncode.getLatFractions());
146+
double valx = (Point.MAX_PRECISION_FACTOR * extrax4) + pointToEncode.getLonFractionsOnly();
147+
double valy = (Point.MAX_PRECISION_FACTOR * extray ) + (ydirection * pointToEncode.getLatFractionsOnly());
148148

149149
String s = "-";
150150

@@ -310,7 +310,7 @@ private static String encodeAutoHeader(final Point pointToEncode, final int this
310310
int extray = (maxy - pointToEncode.getLatMicroDeg()) % dividery;
311311

312312
int value = (vx / 168) * (h / 176);
313-
if ((extray==0) && (pointToEncode.getLatFractions() > 0)) {
313+
if ((extray==0) && (pointToEncode.getLatFractionsOnly() > 0)) {
314314
vy--;
315315
extray += dividery;
316316
}
@@ -377,7 +377,7 @@ private static String encodeNameless(final Point pointToEncode, final int index,
377377
final int miny = Data.getBoundaries(index).getMinY();
378378

379379
final int dividerx4 = xDivider(miny, maxy);
380-
final int xFracture = pointToEncode.getLonFractions() / 810000;
380+
final int xFracture = pointToEncode.getLonFractionsOnly() / 810000;
381381
final int dminx = pointToEncode.getLonMicroDeg() - minx;
382382
final int dx = ((4 * dminx) + xFracture) / dividerx4;
383383
final int extrax4 = (4 * dminx) - (dx * dividerx4); // like modulus, but with floating point value
@@ -387,7 +387,7 @@ private static String encodeNameless(final Point pointToEncode, final int index,
387387
int dy = dmaxy / dividery;
388388
int extray = dmaxy % dividery;
389389

390-
if ((extray == 0) && (pointToEncode.getLatFractions() > 0)) {
390+
if ((extray == 0) && (pointToEncode.getLatFractionsOnly() > 0)) {
391391
dy--;
392392
extray += dividery;
393393
}

0 commit comments

Comments
 (0)