17
17
18
18
package org .elasticsearch .common .geo ;
19
19
20
- import gnu .trove .map .hash .TIntIntHashMap ;
20
+ import org .elasticsearch .ElasticSearchIllegalArgumentException ;
21
+
22
+ import java .util .ArrayList ;
23
+ import java .util .List ;
24
+
21
25
22
26
/**
23
27
* Utilities for encoding and decoding geohashes. Based on
@@ -31,19 +35,9 @@ public class GeoHashUtils {
31
35
'7' , '8' , '9' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'j' , 'k' , 'm' , 'n' ,
32
36
'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' };
33
37
34
- // private final static Map<Character, Integer> DECODE_MAP = new HashMap<Character, Integer>();
35
-
36
- private final static TIntIntHashMap DECODE_MAP = new TIntIntHashMap ();
37
-
38
38
public static final int PRECISION = 12 ;
39
39
private static final int [] BITS = {16 , 8 , 4 , 2 , 1 };
40
40
41
- static {
42
- for (int i = 0 ; i < BASE_32 .length ; i ++) {
43
- DECODE_MAP .put (BASE_32 [i ], i );
44
- }
45
- }
46
-
47
41
private GeoHashUtils () {
48
42
}
49
43
@@ -112,6 +106,144 @@ public static String encode(double latitude, double longitude, int precision) {
112
106
return geohash .toString ();
113
107
}
114
108
109
+ private static final char encode (int x , int y ) {
110
+ return BASE_32 [((x &1 ) + ((y &1 )*2 ) + ((x &2 )*2 ) + ((y &2 )*4 ) + ((x &4 )*4 )) % 32 ];
111
+ }
112
+
113
+ /**
114
+ * Calculate all neighbors of a given geohash cell.
115
+ * @param geohash Geohash of the defines cell
116
+ * @return geohashes of all neighbor cells
117
+ */
118
+ public static String [] neighbors (String geohash ) {
119
+ List <String > neighbors = addNeighbors (geohash , geohash .length (), new ArrayList <String >(8 ));
120
+ return neighbors .toArray (new String [neighbors .size ()]);
121
+ }
122
+
123
+ /**
124
+ * Calculate the geohash of a neighbor of a geohash
125
+ *
126
+ * @param geohash the geohash of a cell
127
+ * @param level level of the geohash
128
+ * @param dx delta of the first grid coordinate (must be -1, 0 or +1)
129
+ * @param dy delta of the second grid coordinate (must be -1, 0 or +1)
130
+ * @return geohash of the defined cell
131
+ */
132
+ private final static String neighbor (String geohash , int level , int dx , int dy ) {
133
+ int cell = decode (geohash .charAt (level -1 ));
134
+
135
+ // Decoding the Geohash bit pattern to determine grid coordinates
136
+ int x0 = cell & 1 ; // first bit of x
137
+ int y0 = cell & 2 ; // first bit of y
138
+ int x1 = cell & 4 ; // second bit of x
139
+ int y1 = cell & 8 ; // second bit of y
140
+ int x2 = cell & 16 ; // third bit of x
141
+
142
+ // combine the bitpattern to grid coordinates.
143
+ // note that the semantics of x and y are swapping
144
+ // on each level
145
+ int x = x0 + (x1 /2 ) + (x2 / 4 );
146
+ int y = (y0 /2 ) + (y1 /4 );
147
+
148
+ if (level == 1 ) {
149
+ // Root cells at north (namely "bcfguvyz") or at
150
+ // south (namely "0145hjnp") do not have neighbors
151
+ // in north/south direction
152
+ if ((dy < 0 && y == 0 ) || (dy > 0 && y == 3 )) {
153
+ return null ;
154
+ } else {
155
+ return Character .toString (encode (x + dx , y + dy ));
156
+ }
157
+ } else {
158
+ // define grid coordinates for next level
159
+ final int nx = ((level % 2 ) == 1 ) ?(x + dx ) :(x + dy );
160
+ final int ny = ((level % 2 ) == 1 ) ?(y + dy ) :(y + dx );
161
+
162
+ // define grid limits for current level
163
+ final int xLimit = ((level % 2 ) == 0 ) ?7 :3 ;
164
+ final int yLimit = ((level % 2 ) == 0 ) ?3 :7 ;
165
+
166
+ // if the defined neighbor has the same parent a the current cell
167
+ // encode the cell direcly. Otherwise find the cell next to this
168
+ // cell recursively. Since encoding wraps around within a cell
169
+ // it can be encoded here.
170
+ if (nx >= 0 && nx <=xLimit && ny >=0 && ny <yLimit ) {
171
+ return geohash .substring (0 , level - 1 ) + encode (nx , ny );
172
+ } else {
173
+ return neighbor (geohash , level - 1 , dx , dy ) + encode (nx , ny );
174
+ }
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Add all geohashes of the cells next to a given geohash to a list.
180
+ *
181
+ * @param geohash Geohash of a specified cell
182
+ * @param length level of the given geohash
183
+ * @param neighbors list to add the neighbors to
184
+ * @return the given list
185
+ */
186
+ private static final List <String > addNeighbors (String geohash , int length , List <String > neighbors ) {
187
+ String south = neighbor (geohash , length , 0 , -1 );
188
+ String north = neighbor (geohash , length , 0 , +1 );
189
+
190
+ if (north != null ) {
191
+ neighbors .add (neighbor (north , length , -1 , 0 ));
192
+ neighbors .add (north );
193
+ neighbors .add (neighbor (north , length , +1 , 0 ));
194
+ }
195
+
196
+ neighbors .add (neighbor (geohash , length , -1 , 0 ));
197
+ neighbors .add (neighbor (geohash , length , +1 , 0 ));
198
+
199
+ if (south != null ) {
200
+ neighbors .add (neighbor (south , length , -1 , 0 ));
201
+ neighbors .add (south );
202
+ neighbors .add (neighbor (south , length , +1 , 0 ));
203
+ }
204
+
205
+ return neighbors ;
206
+ }
207
+
208
+ private static final int decode (char geo ) {
209
+ switch (geo ) {
210
+ case '0' : return 0 ;
211
+ case '1' : return 1 ;
212
+ case '2' : return 2 ;
213
+ case '3' : return 3 ;
214
+ case '4' : return 4 ;
215
+ case '5' : return 5 ;
216
+ case '6' : return 6 ;
217
+ case '7' : return 7 ;
218
+ case '8' : return 8 ;
219
+ case '9' : return 9 ;
220
+ case 'b' : return 10 ;
221
+ case 'c' : return 11 ;
222
+ case 'd' : return 12 ;
223
+ case 'e' : return 13 ;
224
+ case 'f' : return 14 ;
225
+ case 'g' : return 15 ;
226
+ case 'h' : return 16 ;
227
+ case 'j' : return 17 ;
228
+ case 'k' : return 18 ;
229
+ case 'm' : return 19 ;
230
+ case 'n' : return 20 ;
231
+ case 'p' : return 21 ;
232
+ case 'q' : return 22 ;
233
+ case 'r' : return 23 ;
234
+ case 's' : return 24 ;
235
+ case 't' : return 25 ;
236
+ case 'u' : return 26 ;
237
+ case 'v' : return 27 ;
238
+ case 'w' : return 28 ;
239
+ case 'x' : return 29 ;
240
+ case 'y' : return 30 ;
241
+ case 'z' : return 31 ;
242
+ default :
243
+ throw new ElasticSearchIllegalArgumentException ("the character '" +geo +"' is not a valid geohash character" );
244
+ }
245
+ }
246
+
115
247
public static GeoPoint decode (String geohash ) {
116
248
GeoPoint point = new GeoPoint ();
117
249
decode (geohash , point );
@@ -125,44 +257,48 @@ public static GeoPoint decode(String geohash) {
125
257
* @return Array with the latitude at index 0, and longitude at index 1
126
258
*/
127
259
public static void decode (String geohash , GeoPoint ret ) {
128
- // double[] latInterval = {-90.0, 90.0};
129
- // double[] lngInterval = {-180.0, 180.0};
130
- double latInterval0 = -90.0 ;
131
- double latInterval1 = 90.0 ;
132
- double lngInterval0 = -180.0 ;
133
- double lngInterval1 = 180.0 ;
260
+ double [] interval = decodeCell (geohash );
261
+ ret .reset ((interval [0 ] + interval [1 ]) / 2D , (interval [2 ] + interval [3 ]) / 2D );
262
+
263
+ }
264
+
265
+ /**
266
+ * Decodes the given geohash into a geohash cell defined by the points nothWest and southEast
267
+ *
268
+ * @param geohash Geohash to deocde
269
+ * @param northWest the point north/west of the cell
270
+ * @param southEast the point south/east of the cell
271
+ */
272
+ public static void decodeCell (String geohash , GeoPoint northWest , GeoPoint southEast ) {
273
+ double [] interval = decodeCell (geohash );
274
+ northWest .reset (interval [1 ], interval [2 ]);
275
+ southEast .reset (interval [0 ], interval [3 ]);
276
+ }
134
277
278
+ private static double [] decodeCell (String geohash ) {
279
+ double [] interval = {-90.0 , 90.0 , -180.0 , 180.0 };
135
280
boolean isEven = true ;
136
281
137
282
for (int i = 0 ; i < geohash .length (); i ++) {
138
- final int cd = DECODE_MAP . get (geohash .charAt (i ));
283
+ final int cd = decode (geohash .charAt (i ));
139
284
140
285
for (int mask : BITS ) {
141
286
if (isEven ) {
142
287
if ((cd & mask ) != 0 ) {
143
- // lngInterval[0] = (lngInterval[0] + lngInterval[1]) / 2D;
144
- lngInterval0 = (lngInterval0 + lngInterval1 ) / 2D ;
288
+ interval [2 ] = (interval [2 ] + interval [3 ]) / 2D ;
145
289
} else {
146
- // lngInterval[1] = (lngInterval[0] + lngInterval[1]) / 2D;
147
- lngInterval1 = (lngInterval0 + lngInterval1 ) / 2D ;
290
+ interval [3 ] = (interval [2 ] + interval [3 ]) / 2D ;
148
291
}
149
292
} else {
150
293
if ((cd & mask ) != 0 ) {
151
- // latInterval[0] = (latInterval[0] + latInterval[1]) / 2D;
152
- latInterval0 = (latInterval0 + latInterval1 ) / 2D ;
294
+ interval [0 ] = (interval [0 ] + interval [1 ]) / 2D ;
153
295
} else {
154
- // latInterval[1] = (latInterval[0] + latInterval[1]) / 2D;
155
- latInterval1 = (latInterval0 + latInterval1 ) / 2D ;
296
+ interval [1 ] = (interval [0 ] + interval [1 ]) / 2D ;
156
297
}
157
298
}
158
299
isEven = !isEven ;
159
300
}
160
-
161
301
}
162
- // latitude = (latInterval[0] + latInterval[1]) / 2D;
163
- // longitude = (lngInterval[0] + lngInterval[1]) / 2D;
164
-
165
- ret .reset ((latInterval0 + latInterval1 ) / 2D , (lngInterval0 + lngInterval1 ) / 2D );
166
- // return ret;
302
+ return interval ;
167
303
}
168
304
}
0 commit comments