Skip to content

Commit eae31a2

Browse files
committed
moved all plus code into class, test
1 parent aef2af9 commit eae31a2

File tree

8 files changed

+216
-188
lines changed

8 files changed

+216
-188
lines changed

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

Lines changed: 0 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@
1818
*/
1919
package org.elasticsearch.common.geo;
2020

21-
import com.google.openlocationcode.OpenLocationCode;
2221
import org.apache.lucene.geo.Rectangle;
2322
import org.apache.lucene.spatial.util.MortonEncoder;
2423
import org.apache.lucene.util.BitUtil;
2524

2625
import java.util.ArrayList;
27-
import java.util.Arrays;
2826
import java.util.Collection;
2927

3028
/**
@@ -353,109 +351,4 @@ public static final double decodeLatitude(final String geohash) {
353351
public static final double decodeLongitude(final String geohash) {
354352
return decodeLongitude(mortonEncode(geohash));
355353
}
356-
357-
/* ************************************ plus code support ************************************ */
358-
359-
/**
360-
* Same as official plus code alphabet, but also includes "0" to preserve the code length
361-
*/
362-
private static final String PLUSCODE_EXT_ALPHABET = "023456789CFGHJMPQRVWX";
363-
364-
/**
365-
* Length of the extended alphabet (21)
366-
*/
367-
private static final int PLUSCODE_EXT_ALPHABET_SIZE = PLUSCODE_EXT_ALPHABET.length();
368-
369-
/**
370-
* Maximum plus code length (without the '+' symbol) that we support
371-
* 21^14 is the largest value that can fit within a long value
372-
*/
373-
public static final int PLUSCODE_MAX_LENGTH = 14;
374-
375-
private static final int[] PLUSCODE_ALPHABET_LOOKUP;
376-
377-
static {
378-
// Initialize PLUSCODE_ALPHABET_LOOKUP for quick O(1) lookup of alphabet letters -> int
379-
// There is some wasted space (first 32 values, and a few gaps), but results is slightly better perf
380-
int size = PLUSCODE_EXT_ALPHABET_SIZE;
381-
PLUSCODE_ALPHABET_LOOKUP = new int[PLUSCODE_EXT_ALPHABET.charAt(size - 1) + 1];
382-
Arrays.fill(PLUSCODE_ALPHABET_LOOKUP, -1);
383-
for (int i = 0; i < size; i++) {
384-
PLUSCODE_ALPHABET_LOOKUP[PLUSCODE_EXT_ALPHABET.charAt(i)] = i;
385-
}
386-
}
387-
388-
/**
389-
* Convert latitude+longitude to the plus code of a given length
390-
*/
391-
public static String latLngToPluscode(final double lon, final double lat, final int codeLength) {
392-
return new OpenLocationCode(lat, lon, codeLength).getCode();
393-
}
394-
395-
/**
396-
* Convert latitude+longitude to a hash value with a given precision.
397-
* Internally, the hash is created by converting plus code string into a base-21 number.
398-
* Plus codes use base 20, but they get appended with 0s if the precision is low.
399-
* Using base-21 allows us to preserve those zeroes
400-
*/
401-
public static long latLngToPluscodeHash(final double lon, final double lat, final int codeLength) {
402-
403-
String pluscode = latLngToPluscode(lon, lat, codeLength);
404-
405-
long result = 0;
406-
for (int i = 0; i < pluscode.length(); i++) {
407-
char ch = pluscode.charAt(i);
408-
if (ch == '+') continue;
409-
int pos = PLUSCODE_ALPHABET_LOOKUP[ch];
410-
if (pos < 0) {
411-
throw new IllegalArgumentException("Character '" + ch + "' is not a valid plus code");
412-
}
413-
result = result * PLUSCODE_EXT_ALPHABET_SIZE + pos;
414-
}
415-
return result;
416-
}
417-
418-
/**
419-
* Decode plus code hash back into a string
420-
*/
421-
public static String decodePluscode(final long hash) {
422-
423-
StringBuilder result = new StringBuilder(PLUSCODE_MAX_LENGTH + 1);
424-
425-
long rest = hash;
426-
while (rest > 0) {
427-
long val = rest % PLUSCODE_EXT_ALPHABET_SIZE;
428-
result.append(PLUSCODE_EXT_ALPHABET.charAt((int) val));
429-
rest = rest / PLUSCODE_EXT_ALPHABET_SIZE;
430-
}
431-
432-
result.reverse();
433-
result.insert(8, '+');
434-
435-
return result.toString();
436-
}
437-
438-
/**
439-
* Computes the bounding box coordinates from a given geohash
440-
*
441-
* @param hashcode Geohash of the defined cell
442-
* @return Rectangle rectangle defining the bounding box
443-
*/
444-
public static Rectangle bboxFromPluscode(final long hashcode) {
445-
return bboxFromPluscode(decodePluscode(hashcode));
446-
}
447-
448-
/**
449-
* Computes the bounding box coordinates from a given geohash
450-
*
451-
* @param pluscode Geohash of the defined cell
452-
* @return Rectangle rectangle defining the bounding box
453-
*/
454-
public static Rectangle bboxFromPluscode(final String pluscode) {
455-
OpenLocationCode.CodeArea area = new OpenLocationCode(pluscode).decode();
456-
457-
return new Rectangle(area.getSouthLatitude(), area.getNorthLatitude(),
458-
area.getWestLongitude(), area.getEastLongitude());
459-
}
460-
461354
}

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
package org.elasticsearch.common.geo;
2121

22-
import com.google.openlocationcode.OpenLocationCode;
2322
import org.apache.lucene.geo.Rectangle;
2423
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
2524
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
@@ -45,8 +44,6 @@
4544
import java.io.IOException;
4645
import java.io.InputStream;
4746

48-
import static org.elasticsearch.common.geo.GeoHashUtils.PLUSCODE_MAX_LENGTH;
49-
5047
public class GeoUtils {
5148

5249
/** Maximum valid latitude in degrees. */
@@ -545,12 +542,7 @@ public static int checkPrecisionRange(int precision, GeoHashType type) {
545542
}
546543
break;
547544
case pluscode:
548-
if ((precision < 4) || (precision > PLUSCODE_MAX_LENGTH) ||
549-
(precision < OpenLocationCode.CODE_PRECISION_NORMAL && precision % 2 == 1)
550-
) {
551-
throw new IllegalArgumentException("Invalid geohash pluscode aggregation precision of " + precision
552-
+ ". Must be between 4 and " + PLUSCODE_MAX_LENGTH + " , and must be even if less than 8.");
553-
}
545+
PluscodeHash.validatePrecision(precision);
554546
break;
555547
default:
556548
throw new IllegalArgumentException("Unknown type " + type.toString());
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.common.geo;
20+
21+
import com.google.openlocationcode.OpenLocationCode;
22+
import org.apache.lucene.geo.Rectangle;
23+
24+
import java.util.Arrays;
25+
26+
public class PluscodeHash {
27+
28+
/**
29+
* Maximum plus code length (without the '+' symbol) that we support
30+
* 21^14 is the largest value that can fit within a long value
31+
*/
32+
private static final int MAX_LENGTH = 14;
33+
34+
/**
35+
* Same as official plus code alphabet, but also includes "0" to preserve the code length
36+
*/
37+
private static final String ALPHABET0 = "023456789CFGHJMPQRVWX";
38+
39+
/**
40+
* Length of the extended alphabet (21)
41+
*/
42+
private static final int ALPHABET0_SIZE = ALPHABET0.length();
43+
44+
// Initialize ALPHABET0_LOOKUP table for quick O(1) lookup of alphabet letters -> int
45+
// There is some wasted space (first 32 values, and a few gaps), but results is slightly better perf
46+
static {
47+
int size = ALPHABET0_SIZE;
48+
int[] lookup = new int[ALPHABET0.charAt(size - 1) + 1];
49+
Arrays.fill(lookup, -1);
50+
for (int i = 0; i < size; i++) {
51+
lookup[ALPHABET0.charAt(i)] = i;
52+
}
53+
ALPHABET0_LOOKUP = lookup;
54+
}
55+
56+
private static final int[] ALPHABET0_LOOKUP;
57+
58+
/**
59+
* Convert latitude+longitude to the plus code of a given length
60+
*/
61+
public static String latLngToPluscode(final double lon, final double lat, final int codeLength) {
62+
return new OpenLocationCode(lat, lon, codeLength).getCode();
63+
}
64+
65+
/**
66+
* Convert latitude+longitude to a hash value with a given precision.
67+
* Internally, the hash is created by converting plus code string into a base-21 number.
68+
* Plus codes use base 20, but they get appended with 0s if the precision is low.
69+
* Using base-21 allows us to preserve those zeroes
70+
*/
71+
public static long latLngToPluscodeHash(final double lon, final double lat, final int codeLength) {
72+
73+
String pluscode = latLngToPluscode(lon, lat, codeLength);
74+
75+
long result = 0;
76+
for (int i = 0; i < pluscode.length(); i++) {
77+
char ch = pluscode.charAt(i);
78+
if (ch == '+') continue;
79+
int pos = ALPHABET0_LOOKUP[ch];
80+
if (pos < 0) {
81+
throw new IllegalArgumentException("Character '" + ch + "' is not a valid plus code");
82+
}
83+
result = result * ALPHABET0_SIZE + pos;
84+
}
85+
return result;
86+
}
87+
88+
/**
89+
* Decode plus code hash back into a string
90+
*/
91+
public static String decodePluscode(final long hash) {
92+
93+
StringBuilder result = new StringBuilder(MAX_LENGTH + 1);
94+
95+
long rest = hash;
96+
while (rest > 0) {
97+
long val = rest % ALPHABET0_SIZE;
98+
result.append(ALPHABET0.charAt((int) val));
99+
rest = rest / ALPHABET0_SIZE;
100+
}
101+
102+
result.reverse();
103+
result.insert(8, '+');
104+
105+
return result.toString();
106+
}
107+
108+
/**
109+
* Computes the bounding box coordinates from a given geohash
110+
*
111+
* @param hashcode Geohash of the defined cell
112+
* @return Rectangle rectangle defining the bounding box
113+
*/
114+
public static Rectangle bboxFromPluscode(final long hashcode) {
115+
final String pluscode = decodePluscode(hashcode);
116+
117+
OpenLocationCode.CodeArea area = new OpenLocationCode(pluscode).decode();
118+
119+
return new Rectangle(area.getSouthLatitude(), area.getNorthLatitude(),
120+
area.getWestLongitude(), area.getEastLongitude());
121+
}
122+
123+
/**
124+
* Validate precision parameter
125+
* @param precision as submitted by the user
126+
*/
127+
public static void validatePrecision(int precision) {
128+
if ((precision < 4) || (precision > MAX_LENGTH) ||
129+
(precision < OpenLocationCode.CODE_PRECISION_NORMAL && precision % 2 == 1)
130+
) {
131+
throw new IllegalArgumentException("Invalid geohash pluscode aggregation precision of " + precision
132+
+ ". Must be between 4 and " + MAX_LENGTH + " , and must be even if less than 8.");
133+
}
134+
}
135+
136+
private PluscodeHash() {
137+
}
138+
}

server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregationBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.common.geo.GeoHashUtils;
2626
import org.elasticsearch.common.geo.GeoPoint;
2727
import org.elasticsearch.common.geo.GeoUtils;
28+
import org.elasticsearch.common.geo.PluscodeHash;
2829
import org.elasticsearch.common.io.stream.StreamInput;
2930
import org.elasticsearch.common.io.stream.StreamOutput;
3031
import org.elasticsearch.common.xcontent.ObjectParser;
@@ -254,7 +255,7 @@ public boolean advanceExact(int docId) throws IOException {
254255
precision);
255256
break;
256257
case pluscode:
257-
values[i] = GeoHashUtils.latLngToPluscodeHash(target.getLon(), target.getLat(), precision);
258+
values[i] = PluscodeHash.latLngToPluscodeHash(target.getLon(), target.getLat(), precision);
258259
break;
259260
default:
260261
throw new IllegalArgumentException();

server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/InternalGeoHashGrid.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.apache.lucene.util.PriorityQueue;
2222
import org.elasticsearch.common.geo.GeoHashUtils;
2323
import org.elasticsearch.common.geo.GeoPoint;
24+
import org.elasticsearch.common.geo.PluscodeHash;
2425
import org.elasticsearch.common.io.stream.StreamInput;
2526
import org.elasticsearch.common.io.stream.StreamOutput;
2627
import org.elasticsearch.common.util.LongObjectPagedHashMap;
@@ -85,7 +86,7 @@ public String getKeyAsString() {
8586
case geohash:
8687
return GeoHashUtils.stringEncode(geohashAsLong);
8788
case pluscode:
88-
return GeoHashUtils.decodePluscode(geohashAsLong);
89+
return PluscodeHash.decodePluscode(geohashAsLong);
8990
default:
9091
throw new IllegalArgumentException();
9192
}
@@ -97,7 +98,7 @@ public Object getKey() {
9798
case geohash:
9899
return GeoPoint.fromGeohash(geohashAsLong);
99100
case pluscode:
100-
return GeoHashUtils.bboxFromPluscode(geohashAsLong);
101+
return PluscodeHash.bboxFromPluscode(geohashAsLong);
101102
default:
102103
throw new IllegalArgumentException();
103104
}

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

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -59,33 +59,6 @@ public void testGeohashAsLongRoutines() {
5959
}
6060
}
6161

62-
public void testPluscodeAsLongRoutines() {
63-
final GeoPoint expected = new GeoPoint();
64-
final GeoPoint actual = new GeoPoint();
65-
//Ensure that for all points at all supported levels of precision
66-
// that the long encoding of a geohash is compatible with its
67-
// String based counterpart
68-
for (double lat = -90; lat < 90; lat++) {
69-
for (double lng = -180; lng < 180; lng++) {
70-
for (int p = 4; p <= 14; p++) {
71-
// code must be even if less than 10 digits
72-
if (p < 10 && p % 2 == 1) continue;
73-
74-
long geoAsLong = GeoHashUtils.latLngToPluscodeHash(lng, lat, p);
75-
76-
// string encode from geohashlong encoded location
77-
String geohashFromLong = GeoHashUtils.decodePluscode(geoAsLong);
78-
79-
// string encode from full res lat lon
80-
String geohash = GeoHashUtils.latLngToPluscode(lng, lat, p);
81-
82-
// ensure both strings are the same
83-
assertEquals(geohash, geohashFromLong);
84-
}
85-
}
86-
}
87-
}
88-
8962
public void testBboxFromHash() {
9063
String hash = randomGeohash(1, 12);
9164
int level = hash.length();

0 commit comments

Comments
 (0)