1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.locationtech.spatial4j.io;
21
22 import org.locationtech.spatial4j.context.SpatialContext;
23 import org.locationtech.spatial4j.shape.Point;
24 import org.locationtech.spatial4j.shape.Rectangle;
25
26 import java.util.Arrays;
27
28
29
30
31
32
33
34
35
36
37
38
39 public class GeohashUtils {
40
41 private static final char[] BASE_32 = {'0', '1', '2', '3', '4', '5', '6',
42 '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
43 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
44
45 private static final int[] BASE_32_IDX;
46
47 public static final int MAX_PRECISION = 24;
48 private static final int[] BITS = {16, 8, 4, 2, 1};
49
50 static {
51 BASE_32_IDX = new int[BASE_32[BASE_32.length-1] - BASE_32[0] + 1];
52 assert BASE_32_IDX.length < 100;
53 Arrays.fill(BASE_32_IDX,-500);
54 for (int i = 0; i < BASE_32.length; i++) {
55 BASE_32_IDX[BASE_32[i] - BASE_32[0]] = i;
56 }
57 }
58
59 private GeohashUtils() {
60 }
61
62
63
64
65
66
67
68
69 public static String encodeLatLon(double latitude, double longitude) {
70 return encodeLatLon(latitude, longitude, 12);
71 }
72
73 public static String encodeLatLon(double latitude, double longitude, int precision) {
74 double[] latInterval = {-90.0, 90.0};
75 double[] lngInterval = {-180.0, 180.0};
76
77 final StringBuilder geohash = new StringBuilder(precision);
78 boolean isEven = true;
79
80 int bit = 0;
81 int ch = 0;
82
83 while (geohash.length() < precision) {
84 double mid = 0.0;
85 if (isEven) {
86 mid = (lngInterval[0] + lngInterval[1]) / 2D;
87 if (longitude > mid) {
88 ch |= BITS[bit];
89 lngInterval[0] = mid;
90 } else {
91 lngInterval[1] = mid;
92 }
93 } else {
94 mid = (latInterval[0] + latInterval[1]) / 2D;
95 if (latitude > mid) {
96 ch |= BITS[bit];
97 latInterval[0] = mid;
98 } else {
99 latInterval[1] = mid;
100 }
101 }
102
103 isEven = !isEven;
104
105 if (bit < 4) {
106 bit++;
107 } else {
108 geohash.append(BASE_32[ch]);
109 bit = 0;
110 ch = 0;
111 }
112 }
113
114 return geohash.toString();
115 }
116
117
118
119
120 public static Point decode(String geohash, SpatialContext ctx) {
121 Rectangle rect = decodeBoundary(geohash,ctx);
122 double latitude = (rect.getMinY() + rect.getMaxY()) / 2D;
123 double longitude = (rect.getMinX() + rect.getMaxX()) / 2D;
124 return ctx.makePoint(longitude,latitude);
125 }
126
127
128 public static Rectangle decodeBoundary(String geohash, SpatialContext ctx) {
129 double minY = -90, maxY = 90, minX = -180, maxX = 180;
130 boolean isEven = true;
131
132 for (int i = 0; i < geohash.length(); i++) {
133 char c = geohash.charAt(i);
134 if (c >= 'A' && c <= 'Z')
135 c -= ('A' - 'a');
136 final int cd = BASE_32_IDX[c - BASE_32[0]];
137
138 for (int mask : BITS) {
139 if (isEven) {
140 if ((cd & mask) != 0) {
141 minX = (minX + maxX) / 2D;
142 } else {
143 maxX = (minX + maxX) / 2D;
144 }
145 } else {
146 if ((cd & mask) != 0) {
147 minY = (minY + maxY) / 2D;
148 } else {
149 maxY = (minY + maxY) / 2D;
150 }
151 }
152 isEven = !isEven;
153 }
154
155 }
156 return ctx.makeRectangle(minX, maxX, minY, maxY);
157 }
158
159
160 public static String[] getSubGeohashes(String baseGeohash) {
161 String[] hashes = new String[BASE_32.length];
162 for (int i = 0; i < BASE_32.length; i++) {
163 char c = BASE_32[i];
164 hashes[i] = baseGeohash+c;
165 }
166 return hashes;
167 }
168
169 public static double[] lookupDegreesSizeForHashLen(int hashLen) {
170 return new double[]{hashLenToLatHeight[hashLen], hashLenToLonWidth[hashLen]};
171 }
172
173
174
175
176 public static int lookupHashLenForWidthHeight(double lonErr, double latErr) {
177
178 for(int len = 1; len < MAX_PRECISION; len++) {
179 double latHeight = hashLenToLatHeight[len];
180 double lonWidth = hashLenToLonWidth[len];
181 if (latHeight < latErr && lonWidth < lonErr)
182 return len;
183 }
184 return MAX_PRECISION;
185 }
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202