Skip to content

Commit c12a424

Browse files
committed
Fix overflow error in parsing of long geohashes (#29418)
Fixes a possible overflow error that geohashes longer than 12 characters can cause during parsing. Fixes #24616
1 parent 9cfc6ca commit c12a424

File tree

3 files changed

+40
-5
lines changed

3 files changed

+40
-5
lines changed

docs/reference/mapping/types/geo-point.asciidoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ format was changed early on to conform to the format used by GeoJSON.
9292
9393
==================================================
9494

95+
[NOTE]
96+
A point can be expressed as a http://en.wikipedia.org/wiki/Geohash[geohash].
97+
Geohashes are https://en.wikipedia.org/wiki/Base32[base32] encoded strings of
98+
the bits of the latitude and longitude interleaved. Each character in a geohash
99+
adds additional 5 bits to the precision. So the longer the hash, the more
100+
precise it is. For the indexing purposed geohashs are translated into
101+
latitude-longitude pairs. During this process only first 12 characters are
102+
used, so specifying more than 12 characters in a geohash doesn't increase the
103+
precision. The 12 characters provide 60 bits, which should reduce a possible
104+
error to less than 2cm.
95105

96106
[[geo-point-params]]
97107
==== Parameters for `geo_point` fields

server/src/main/java/org/elasticsearch/common/geo/GeoHashUtils.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,19 @@ public static final long longEncode(final double lon, final double lat, final in
7272
/**
7373
* Encode from geohash string to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
7474
*/
75-
public static final long longEncode(final String hash) {
76-
int level = hash.length()-1;
75+
private static long longEncode(final String hash, int length) {
76+
int level = length - 1;
7777
long b;
7878
long l = 0L;
7979
for(char c : hash.toCharArray()) {
8080
b = (long)(BASE_32_STRING.indexOf(c));
8181
l |= (b<<(level--*5));
82+
if (level < 0) {
83+
// We cannot handle more than 12 levels
84+
break;
85+
}
8286
}
83-
return (l<<4)|hash.length();
87+
return (l << 4) | length;
8488
}
8589

8690
/**
@@ -173,6 +177,10 @@ public static final long mortonEncode(final String hash) {
173177
for(char c : hash.toCharArray()) {
174178
b = (long)(BASE_32_STRING.indexOf(c));
175179
l |= (b<<((level--*5) + MORTON_OFFSET));
180+
if (level < 0) {
181+
// We cannot handle more than 12 levels
182+
break;
183+
}
176184
}
177185
return BitUtil.flipFlop(l);
178186
}
@@ -200,13 +208,14 @@ private static char encode(int x, int y) {
200208
public static Rectangle bbox(final String geohash) {
201209
// bottom left is the coordinate
202210
GeoPoint bottomLeft = GeoPoint.fromGeohash(geohash);
203-
long ghLong = longEncode(geohash);
211+
int len = Math.min(12, geohash.length());
212+
long ghLong = longEncode(geohash, len);
204213
// shift away the level
205214
ghLong >>>= 4;
206215
// deinterleave and add 1 to lat and lon to get topRight
207216
long lat = BitUtil.deinterleave(ghLong >>> 1) + 1;
208217
long lon = BitUtil.deinterleave(ghLong) + 1;
209-
GeoPoint topRight = GeoPoint.fromGeohash(BitUtil.interleave((int)lon, (int)lat) << 4 | geohash.length());
218+
GeoPoint topRight = GeoPoint.fromGeohash(BitUtil.interleave((int)lon, (int)lat) << 4 | len);
210219

211220
return new Rectangle(bottomLeft.lat(), topRight.lat(), bottomLeft.lon(), topRight.lon());
212221
}

server/src/test/java/org/elasticsearch/common/geo/GeoHashTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,20 @@ public void testGeohashExtremes() {
8282
assertEquals("xbpbpbpbpbpb", GeoHashUtils.stringEncode(180, 0));
8383
assertEquals("zzzzzzzzzzzz", GeoHashUtils.stringEncode(180, 90));
8484
}
85+
86+
public void testLongGeohashes() {
87+
for (int i = 0; i < 100000; i++) {
88+
String geohash = randomGeohash(12, 12);
89+
GeoPoint expected = GeoPoint.fromGeohash(geohash);
90+
// Adding some random geohash characters at the end
91+
String extendedGeohash = geohash + randomGeohash(1, 10);
92+
GeoPoint actual = GeoPoint.fromGeohash(extendedGeohash);
93+
assertEquals("Additional data points above 12 should be ignored [" + extendedGeohash + "]" , expected, actual);
94+
95+
Rectangle expectedBbox = GeoHashUtils.bbox(geohash);
96+
Rectangle actualBbox = GeoHashUtils.bbox(extendedGeohash);
97+
assertEquals("Additional data points above 12 should be ignored [" + extendedGeohash + "]" , expectedBbox, actualBbox);
98+
99+
}
100+
}
85101
}

0 commit comments

Comments
 (0)