|
@@ -16,197 +16,52 @@
|
|
|
* specific language governing permissions and limitations
|
|
|
* under the License.
|
|
|
*/
|
|
|
-package org.elasticsearch.common.geo;
|
|
|
+package org.elasticsearch.geo.utils;
|
|
|
|
|
|
-import org.apache.lucene.geo.Rectangle;
|
|
|
-import org.apache.lucene.spatial.util.MortonEncoder;
|
|
|
-import org.apache.lucene.util.BitUtil;
|
|
|
+import org.elasticsearch.geo.geometry.Point;
|
|
|
+import org.elasticsearch.geo.geometry.Rectangle;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Collection;
|
|
|
|
|
|
-import static org.apache.lucene.geo.GeoUtils.MAX_LAT_INCL;
|
|
|
-
|
|
|
/**
|
|
|
* Utilities for converting to/from the GeoHash standard
|
|
|
*
|
|
|
* The geohash long format is represented as lon/lat (x/y) interleaved with the 4 least significant bits
|
|
|
* representing the level (1-12) [xyxy...xyxyllll]
|
|
|
*
|
|
|
- * This differs from a morton encoded value which interleaves lat/lon (y/x).*
|
|
|
+ * This differs from a morton encoded value which interleaves lat/lon (y/x).
|
|
|
+ *
|
|
|
+ * NOTE: this will replace {@code org.elasticsearch.common.geo.GeoHashUtils}
|
|
|
*/
|
|
|
-public class GeoHashUtils {
|
|
|
+public class Geohash {
|
|
|
private static final char[] BASE_32 = {'0', '1', '2', '3', '4', '5', '6',
|
|
|
'7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
|
|
|
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
|
|
|
|
|
|
private static final String BASE_32_STRING = new String(BASE_32);
|
|
|
-
|
|
|
/** maximum precision for geohash strings */
|
|
|
public static final int PRECISION = 12;
|
|
|
/** number of bits used for quantizing latitude and longitude values */
|
|
|
- public static final short BITS = 31;
|
|
|
- /** scaling factors to convert lat/lon into unsigned space */
|
|
|
- private static final double LAT_SCALE = (0x1L<<BITS)/180.0D;
|
|
|
- private static final double LON_SCALE = (0x1L<<BITS)/360.0D;
|
|
|
+ private static final short BITS = 32;
|
|
|
+ private static final double LAT_SCALE = (0x1L<<(BITS-1))/180.0D;
|
|
|
+ private static final double LAT_DECODE = 180.0D/(0x1L<<BITS);
|
|
|
+ private static final double LON_SCALE = (0x1L<<(BITS-1))/360.0D;
|
|
|
+ private static final double LON_DECODE = 360.0D/(0x1L<<BITS);
|
|
|
+
|
|
|
private static final short MORTON_OFFSET = (BITS<<1) - (PRECISION*5);
|
|
|
/** Bit encoded representation of the latitude of north pole */
|
|
|
private static final long MAX_LAT_BITS = (0x1L << (PRECISION * 5 / 2)) - 1;
|
|
|
|
|
|
- // No instance:
|
|
|
- private GeoHashUtils() {
|
|
|
- }
|
|
|
-
|
|
|
- /*************************
|
|
|
- * 31 bit encoding utils *
|
|
|
- *************************/
|
|
|
- public static long encodeLatLon(final double lat, final double lon) {
|
|
|
- return MortonEncoder.encode(lat, lon) >>> 2;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Encode lon/lat to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
|
|
|
- */
|
|
|
- public static final long longEncode(final double lon, final double lat, final int level) {
|
|
|
- // shift to appropriate level
|
|
|
- final short msf = (short)(((12 - level) * 5) + MORTON_OFFSET);
|
|
|
- return ((BitUtil.flipFlop(encodeLatLon(lat, lon)) >>> msf) << 4) | level;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Encode from geohash string to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
|
|
|
- */
|
|
|
- private static long longEncode(final String hash, int length) {
|
|
|
- int level = length - 1;
|
|
|
- long b;
|
|
|
- long l = 0L;
|
|
|
- for(char c : hash.toCharArray()) {
|
|
|
- b = (long)(BASE_32_STRING.indexOf(c));
|
|
|
- l |= (b<<(level--*5));
|
|
|
- if (level < 0) {
|
|
|
- // We cannot handle more than 12 levels
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- return (l << 4) | length;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Encode an existing geohash long to the provided precision
|
|
|
- */
|
|
|
- public static long longEncode(long geohash, int level) {
|
|
|
- final short precision = (short)(geohash & 15);
|
|
|
- if (precision == level) {
|
|
|
- return geohash;
|
|
|
- } else if (precision > level) {
|
|
|
- return ((geohash >>> (((precision - level) * 5) + 4)) << 4) | level;
|
|
|
- }
|
|
|
- return ((geohash >>> 4) << (((level - precision) * 5) + 4) | level);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Convert from a morton encoded long from a geohash encoded long
|
|
|
- */
|
|
|
- public static long fromMorton(long morton, int level) {
|
|
|
- long mFlipped = BitUtil.flipFlop(morton);
|
|
|
- mFlipped >>>= (((GeoHashUtils.PRECISION - level) * 5) + MORTON_OFFSET);
|
|
|
- return (mFlipped << 4) | level;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Encode to a geohash string from the geohash based long format
|
|
|
- */
|
|
|
- public static final String stringEncode(long geoHashLong) {
|
|
|
- int level = (int)geoHashLong&15;
|
|
|
- geoHashLong >>>= 4;
|
|
|
- char[] chars = new char[level];
|
|
|
- do {
|
|
|
- chars[--level] = BASE_32[(int) (geoHashLong&31L)];
|
|
|
- geoHashLong>>>=5;
|
|
|
- } while(level > 0);
|
|
|
|
|
|
- return new String(chars);
|
|
|
+ // no instance:
|
|
|
+ private Geohash() {
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Encode to a geohash string from full resolution longitude, latitude)
|
|
|
- */
|
|
|
- public static final String stringEncode(final double lon, final double lat) {
|
|
|
- return stringEncode(lon, lat, 12);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Encode to a level specific geohash string from full resolution longitude, latitude
|
|
|
- */
|
|
|
- public static final String stringEncode(final double lon, final double lat, final int level) {
|
|
|
- // convert to geohashlong
|
|
|
- final long ghLong = fromMorton(encodeLatLon(lat, lon), level);
|
|
|
- return stringEncode(ghLong);
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Encode to a full precision geohash string from a given morton encoded long value
|
|
|
- */
|
|
|
- public static final String stringEncodeFromMortonLong(final long hashedVal) throws Exception {
|
|
|
- return stringEncode(hashedVal, PRECISION);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Encode to a geohash string at a given level from a morton long
|
|
|
- */
|
|
|
- public static final String stringEncodeFromMortonLong(long hashedVal, final int level) {
|
|
|
- // bit twiddle to geohash (since geohash is a swapped (lon/lat) encoding)
|
|
|
- hashedVal = BitUtil.flipFlop(hashedVal);
|
|
|
-
|
|
|
- StringBuilder geoHash = new StringBuilder();
|
|
|
- short precision = 0;
|
|
|
- final short msf = (BITS<<1)-5;
|
|
|
- long mask = 31L<<msf;
|
|
|
- do {
|
|
|
- geoHash.append(BASE_32[(int)((mask & hashedVal)>>>(msf-(precision*5)))]);
|
|
|
- // next 5 bits
|
|
|
- mask >>>= 5;
|
|
|
- } while (++precision < level);
|
|
|
- return geoHash.toString();
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Encode to a morton long value from a given geohash string
|
|
|
- */
|
|
|
- public static final long mortonEncode(final String hash) {
|
|
|
- if (hash.isEmpty()) {
|
|
|
- throw new IllegalArgumentException("empty geohash");
|
|
|
- }
|
|
|
- int level = 11;
|
|
|
- long b;
|
|
|
- long l = 0L;
|
|
|
- for(char c : hash.toCharArray()) {
|
|
|
- b = (long)(BASE_32_STRING.indexOf(c));
|
|
|
- if (b < 0) {
|
|
|
- throw new IllegalArgumentException("unsupported symbol [" + c + "] in geohash [" + hash + "]");
|
|
|
- }
|
|
|
- l |= (b<<((level--*5) + MORTON_OFFSET));
|
|
|
- if (level < 0) {
|
|
|
- // We cannot handle more than 12 levels
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- return BitUtil.flipFlop(l);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Encode to a morton long value from a given geohash long value
|
|
|
- */
|
|
|
- public static final long mortonEncode(final long geoHashLong) {
|
|
|
- final int level = (int)(geoHashLong&15);
|
|
|
- final short odd = (short)(level & 1);
|
|
|
-
|
|
|
- return BitUtil.flipFlop(((geoHashLong >>> 4) << odd) << (((12 - level) * 5) + (MORTON_OFFSET - odd)));
|
|
|
- }
|
|
|
-
|
|
|
- private static char encode(int x, int y) {
|
|
|
- return BASE_32[((x & 1) + ((y & 1) * 2) + ((x & 2) * 2) + ((y & 2) * 4) + ((x & 4) * 4)) % 32];
|
|
|
+ /** Returns a {@link Point} instance from a geohash string */
|
|
|
+ public static Point toPoint(final String geohash) throws IllegalArgumentException {
|
|
|
+ final long hash = mortonEncode(geohash);
|
|
|
+ return new Point(decodeLatitude(hash), decodeLongitude(hash));
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -215,9 +70,9 @@ public class GeoHashUtils {
|
|
|
* @param geohash Geohash of the defined cell
|
|
|
* @return GeoRect rectangle defining the bounding box
|
|
|
*/
|
|
|
- public static Rectangle bbox(final String geohash) {
|
|
|
+ public static Rectangle toBoundingBox(final String geohash) {
|
|
|
// bottom left is the coordinate
|
|
|
- GeoPoint bottomLeft = GeoPoint.fromGeohash(geohash);
|
|
|
+ Point bottomLeft = toPoint(geohash);
|
|
|
int len = Math.min(12, geohash.length());
|
|
|
long ghLong = longEncode(geohash, len);
|
|
|
// shift away the level
|
|
@@ -225,15 +80,20 @@ public class GeoHashUtils {
|
|
|
// deinterleave
|
|
|
long lon = BitUtil.deinterleave(ghLong >>> 1);
|
|
|
long lat = BitUtil.deinterleave(ghLong);
|
|
|
+ final int shift = (12 - len) * 5 + 2;
|
|
|
if (lat < MAX_LAT_BITS) {
|
|
|
// add 1 to lat and lon to get topRight
|
|
|
- GeoPoint topRight = GeoPoint.fromGeohash(BitUtil.interleave((int)(lat + 1), (int)(lon + 1)) << 4 | len);
|
|
|
- return new Rectangle(bottomLeft.lat(), topRight.lat(), bottomLeft.lon(), topRight.lon());
|
|
|
+ ghLong = BitUtil.interleave((int)(lat + 1), (int)(lon + 1)) << 4 | len;
|
|
|
+ final long mortonHash = BitUtil.flipFlop((ghLong >>> 4) << shift);
|
|
|
+ Point topRight = new Point(decodeLatitude(mortonHash), decodeLongitude(mortonHash));
|
|
|
+ return new Rectangle(bottomLeft.getLat(), topRight.getLat(), bottomLeft.getLon(), topRight.getLon());
|
|
|
} else {
|
|
|
// We cannot go north of north pole, so just using 90 degrees instead of calculating it using
|
|
|
// add 1 to lon to get lon of topRight, we are going to use 90 for lat
|
|
|
- GeoPoint topRight = GeoPoint.fromGeohash(BitUtil.interleave((int)lat, (int)(lon + 1)) << 4 | len);
|
|
|
- return new Rectangle(bottomLeft.lat(), MAX_LAT_INCL, bottomLeft.lon(), topRight.lon());
|
|
|
+ ghLong = BitUtil.interleave((int)lat, (int)(lon + 1)) << 4 | len;
|
|
|
+ final long mortonHash = BitUtil.flipFlop((ghLong >>> 4) << shift);
|
|
|
+ Point topRight = new Point(decodeLatitude(mortonHash), decodeLongitude(mortonHash));
|
|
|
+ return new Rectangle(bottomLeft.getLat(), 90D, bottomLeft.getLon(), topRight.getLon());
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -243,8 +103,48 @@ public class GeoHashUtils {
|
|
|
* @param geohash Geohash of the defined cell
|
|
|
* @return geohashes of all neighbor cells
|
|
|
*/
|
|
|
- public static Collection<? extends CharSequence> neighbors(String geohash) {
|
|
|
- return addNeighbors(geohash, geohash.length(), new ArrayList<CharSequence>(8));
|
|
|
+ public static Collection<? extends CharSequence> getNeighbors(String geohash) {
|
|
|
+ return addNeighborsAtLevel(geohash, geohash.length(), new ArrayList<CharSequence>(8));
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Add all geohashes of the cells next to a given geohash to a list.
|
|
|
+ *
|
|
|
+ * @param geohash Geohash of a specified cell
|
|
|
+ * @param neighbors list to add the neighbors to
|
|
|
+ * @return the given list
|
|
|
+ */
|
|
|
+ public static final <E extends Collection<? super String>> E addNeighbors(String geohash, E neighbors) {
|
|
|
+ return addNeighborsAtLevel(geohash, geohash.length(), neighbors);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Add all geohashes of the cells next to a given geohash to a list.
|
|
|
+ *
|
|
|
+ * @param geohash Geohash of a specified cell
|
|
|
+ * @param level level of the given geohash
|
|
|
+ * @param neighbors list to add the neighbors to
|
|
|
+ * @return the given list
|
|
|
+ */
|
|
|
+ public static final <E extends Collection<? super String>> E addNeighborsAtLevel(String geohash,
|
|
|
+ int level, E neighbors) {
|
|
|
+ String south = getNeighbor(geohash, level, 0, -1);
|
|
|
+ String north = getNeighbor(geohash, level, 0, +1);
|
|
|
+ if (north != null) {
|
|
|
+ neighbors.add(getNeighbor(north, level, -1, 0));
|
|
|
+ neighbors.add(north);
|
|
|
+ neighbors.add(getNeighbor(north, level, +1, 0));
|
|
|
+ }
|
|
|
+
|
|
|
+ neighbors.add(getNeighbor(geohash, level, -1, 0));
|
|
|
+ neighbors.add(getNeighbor(geohash, level, +1, 0));
|
|
|
+
|
|
|
+ if (south != null) {
|
|
|
+ neighbors.add(getNeighbor(south, level, -1, 0));
|
|
|
+ neighbors.add(south);
|
|
|
+ neighbors.add(getNeighbor(south, level, +1, 0));
|
|
|
+ }
|
|
|
+
|
|
|
+ return neighbors;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -256,7 +156,7 @@ public class GeoHashUtils {
|
|
|
* @param dy delta of the second grid coordinate (must be -1, 0 or +1)
|
|
|
* @return geohash of the defined cell
|
|
|
*/
|
|
|
- public static final String neighbor(String geohash, int level, int dx, int dy) {
|
|
|
+ public static final String getNeighbor(String geohash, int level, int dx, int dy) {
|
|
|
int cell = BASE_32_STRING.indexOf(geohash.charAt(level -1));
|
|
|
|
|
|
// Decoding the Geohash bit pattern to determine grid coordinates
|
|
@@ -279,7 +179,7 @@ public class GeoHashUtils {
|
|
|
if ((dy < 0 && y == 0) || (dy > 0 && y == 3)) {
|
|
|
return null;
|
|
|
} else {
|
|
|
- return Character.toString(encode(x + dx, y + dy));
|
|
|
+ return Character.toString(encodeBase32(x + dx, y + dy));
|
|
|
}
|
|
|
} else {
|
|
|
// define grid coordinates for next level
|
|
@@ -293,79 +193,154 @@ public class GeoHashUtils {
|
|
|
// xLimit and YLimit must always be respectively 7 and 3
|
|
|
// since x and y semantics are swapping on each level.
|
|
|
if (nx >= 0 && nx <= 7 && ny >= 0 && ny <= 3) {
|
|
|
- return geohash.substring(0, level - 1) + encode(nx, ny);
|
|
|
+ return geohash.substring(0, level - 1) + encodeBase32(nx, ny);
|
|
|
} else {
|
|
|
- String neighbor = neighbor(geohash, level - 1, dx, dy);
|
|
|
- return (neighbor != null) ? neighbor + encode(nx, ny) : neighbor;
|
|
|
+ String neighbor = getNeighbor(geohash, level - 1, dx, dy);
|
|
|
+ return (neighbor != null) ? neighbor + encodeBase32(nx, ny) : neighbor;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Add all geohashes of the cells next to a given geohash to a list.
|
|
|
- *
|
|
|
- * @param geohash Geohash of a specified cell
|
|
|
- * @param neighbors list to add the neighbors to
|
|
|
- * @return the given list
|
|
|
+ * Encode lon/lat to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
|
|
|
*/
|
|
|
- public static final <E extends Collection<? super String>> E addNeighbors(String geohash, E neighbors) {
|
|
|
- return addNeighbors(geohash, geohash.length(), neighbors);
|
|
|
+ public static final long longEncode(final double lon, final double lat, final int level) {
|
|
|
+ // shift to appropriate level
|
|
|
+ final short msf = (short)(((12 - level) * 5) + (MORTON_OFFSET - 2));
|
|
|
+ return ((encodeLatLon(lat, lon) >>> msf) << 4) | level;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Add all geohashes of the cells next to a given geohash to a list.
|
|
|
- *
|
|
|
- * @param geohash Geohash of a specified cell
|
|
|
- * @param length level of the given geohash
|
|
|
- * @param neighbors list to add the neighbors to
|
|
|
- * @return the given list
|
|
|
+ * Encode to a geohash string from full resolution longitude, latitude)
|
|
|
*/
|
|
|
- public static final <E extends Collection<? super String>> E addNeighbors(String geohash, int length, E neighbors) {
|
|
|
- String south = neighbor(geohash, length, 0, -1);
|
|
|
- String north = neighbor(geohash, length, 0, +1);
|
|
|
- if (north != null) {
|
|
|
- neighbors.add(neighbor(north, length, -1, 0));
|
|
|
- neighbors.add(north);
|
|
|
- neighbors.add(neighbor(north, length, +1, 0));
|
|
|
- }
|
|
|
+ public static final String stringEncode(final double lon, final double lat) {
|
|
|
+ return stringEncode(lon, lat, 12);
|
|
|
+ }
|
|
|
|
|
|
- neighbors.add(neighbor(geohash, length, -1, 0));
|
|
|
- neighbors.add(neighbor(geohash, length, +1, 0));
|
|
|
+ /**
|
|
|
+ * Encode to a level specific geohash string from full resolution longitude, latitude
|
|
|
+ */
|
|
|
+ public static final String stringEncode(final double lon, final double lat, final int level) {
|
|
|
+ // convert to geohashlong
|
|
|
+ long interleaved = encodeLatLon(lat, lon);
|
|
|
+ interleaved >>>= (((PRECISION - level) * 5) + (MORTON_OFFSET - 2));
|
|
|
+ final long geohash = (interleaved << 4) | level;
|
|
|
+ return stringEncode(geohash);
|
|
|
+ }
|
|
|
|
|
|
- if (south != null) {
|
|
|
- neighbors.add(neighbor(south, length, -1, 0));
|
|
|
- neighbors.add(south);
|
|
|
- neighbors.add(neighbor(south, length, +1, 0));
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * Encode to a geohash string from the geohash based long format
|
|
|
+ */
|
|
|
+ public static final String stringEncode(long geoHashLong) {
|
|
|
+ int level = (int)geoHashLong&15;
|
|
|
+ geoHashLong >>>= 4;
|
|
|
+ char[] chars = new char[level];
|
|
|
+ do {
|
|
|
+ chars[--level] = BASE_32[(int) (geoHashLong&31L)];
|
|
|
+ geoHashLong>>>=5;
|
|
|
+ } while(level > 0);
|
|
|
|
|
|
- return neighbors;
|
|
|
+ return new String(chars);
|
|
|
}
|
|
|
|
|
|
- /** decode longitude value from morton encoded geo point */
|
|
|
- public static final double decodeLongitude(final long hash) {
|
|
|
- return unscaleLon(BitUtil.deinterleave(hash));
|
|
|
+ /** base32 encode at the given grid coordinate */
|
|
|
+ private static char encodeBase32(int x, int y) {
|
|
|
+ return BASE_32[((x & 1) + ((y & 1) * 2) + ((x & 2) * 2) + ((y & 2) * 4) + ((x & 4) * 4)) % 32];
|
|
|
}
|
|
|
|
|
|
- /** decode latitude value from morton encoded geo point */
|
|
|
- public static final double decodeLatitude(final long hash) {
|
|
|
- return unscaleLat(BitUtil.deinterleave(hash >>> 1));
|
|
|
+ /**
|
|
|
+ * Encode from geohash string to the geohash based long format (lon/lat interleaved, 4 least significant bits = level)
|
|
|
+ */
|
|
|
+ private static long longEncode(final String hash, int length) {
|
|
|
+ int level = length - 1;
|
|
|
+ long b;
|
|
|
+ long l = 0L;
|
|
|
+ for(char c : hash.toCharArray()) {
|
|
|
+ b = (long)(BASE_32_STRING.indexOf(c));
|
|
|
+ l |= (b<<(level--*5));
|
|
|
+ if (level < 0) {
|
|
|
+ // We cannot handle more than 12 levels
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return (l << 4) | length;
|
|
|
}
|
|
|
|
|
|
- private static double unscaleLon(final long val) {
|
|
|
- return (val / LON_SCALE) - 180;
|
|
|
+ /**
|
|
|
+ * Encode to a morton long value from a given geohash string
|
|
|
+ */
|
|
|
+ public static long mortonEncode(final String hash) {
|
|
|
+ if (hash.isEmpty()) {
|
|
|
+ throw new IllegalArgumentException("empty geohash");
|
|
|
+ }
|
|
|
+ int level = 11;
|
|
|
+ long b;
|
|
|
+ long l = 0L;
|
|
|
+ for(char c : hash.toCharArray()) {
|
|
|
+ b = (long)(BASE_32_STRING.indexOf(c));
|
|
|
+ if (b < 0) {
|
|
|
+ throw new IllegalArgumentException("unsupported symbol [" + c + "] in geohash [" + hash + "]");
|
|
|
+ }
|
|
|
+ l |= (b<<((level--*5) + (MORTON_OFFSET - 2)));
|
|
|
+ if (level < 0) {
|
|
|
+ // We cannot handle more than 12 levels
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return BitUtil.flipFlop(l);
|
|
|
}
|
|
|
|
|
|
- private static double unscaleLat(final long val) {
|
|
|
- return (val / LAT_SCALE) - 90;
|
|
|
+ private static long encodeLatLon(final double lat, final double lon) {
|
|
|
+ // encode lat/lon flipping the sign bit so negative ints sort before positive ints
|
|
|
+ final int latEnc = encodeLatitude(lat) ^ 0x80000000;
|
|
|
+ final int lonEnc = encodeLongitude(lon) ^ 0x80000000;
|
|
|
+ return BitUtil.interleave(latEnc, lonEnc) >>> 2;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /** encode latitude to integer */
|
|
|
+ public static int encodeLatitude(double latitude) {
|
|
|
+ // the maximum possible value cannot be encoded without overflow
|
|
|
+ if (latitude == 90.0D) {
|
|
|
+ latitude = Math.nextDown(latitude);
|
|
|
+ }
|
|
|
+ return (int) Math.floor(latitude / LAT_DECODE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** encode longitude to integer */
|
|
|
+ public static int encodeLongitude(double longitude) {
|
|
|
+ // the maximum possible value cannot be encoded without overflow
|
|
|
+ if (longitude == 180.0D) {
|
|
|
+ longitude = Math.nextDown(longitude);
|
|
|
+ }
|
|
|
+ return (int) Math.floor(longitude / LON_DECODE);
|
|
|
}
|
|
|
|
|
|
/** returns the latitude value from the string based geohash */
|
|
|
public static final double decodeLatitude(final String geohash) {
|
|
|
- return decodeLatitude(mortonEncode(geohash));
|
|
|
+ return decodeLatitude(Geohash.mortonEncode(geohash));
|
|
|
}
|
|
|
|
|
|
/** returns the latitude value from the string based geohash */
|
|
|
public static final double decodeLongitude(final String geohash) {
|
|
|
- return decodeLongitude(mortonEncode(geohash));
|
|
|
+ return decodeLongitude(Geohash.mortonEncode(geohash));
|
|
|
+ }
|
|
|
+
|
|
|
+ /** decode longitude value from morton encoded geo point */
|
|
|
+ public static double decodeLongitude(final long hash) {
|
|
|
+ return unscaleLon(BitUtil.deinterleave(hash));
|
|
|
+ }
|
|
|
+
|
|
|
+ /** decode latitude value from morton encoded geo point */
|
|
|
+ public static double decodeLatitude(final long hash) {
|
|
|
+ return unscaleLat(BitUtil.deinterleave(hash >>> 1));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static double unscaleLon(final long val) {
|
|
|
+ return (val / LON_SCALE) - 180;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static double unscaleLat(final long val) {
|
|
|
+ return (val / LAT_SCALE) - 90;
|
|
|
}
|
|
|
}
|