package org.vertexium.type;
import java.util.HashMap;
import java.util.Map;
public class GeoHash implements GeoShape {
private static final char[] BASE32 = {
'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 int[] BITS = {16, 8, 4, 2, 1};
private static final Map<Character, Integer> DECODE_MAP = new HashMap<>();
private final String hash;
static {
int sz = BASE32.length;
for (int i = 0; i < sz; i++) {
DECODE_MAP.put(BASE32[i], i);
}
}
public GeoHash(double latitude, double longitude, int precision) {
this.hash = encode(latitude, longitude, precision);
}
public GeoHash(String hash) {
this.hash = hash;
}
public String getHash() {
return hash;
}
@Override
public String toString() {
return "GeoHash{" +
"hash='" + hash + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GeoHash geoHash = (GeoHash) o;
if (!hash.equals(geoHash.hash)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return hash.hashCode();
}
private String encode(double latitude, double longitude, int precision) {
double[] latInterval = {-90.0, 90.0};
double[] lngInterval = {-180.0, 180.0};
final StringBuilder geohash = new StringBuilder();
boolean isEven = true;
int bit = 0;
int ch = 0;
while (geohash.length() < precision) {
double mid;
if (isEven) {
mid = (lngInterval[0] + lngInterval[1]) / 2D;
if (longitude > mid) {
ch |= BITS[bit];
lngInterval[0] = mid;
} else {
lngInterval[1] = mid;
}
} else {
mid = (latInterval[0] + latInterval[1]) / 2D;
if (latitude > mid) {
ch |= BITS[bit];
latInterval[0] = mid;
} else {
latInterval[1] = mid;
}
}
isEven = !isEven;
if (bit < 4) {
bit++;
} else {
geohash.append(BASE32[ch]);
bit = 0;
ch = 0;
}
}
return geohash.toString();
}
public GeoRect toGeoRect() {
final double[] latInterval = {-90.0, 90.0};
final double[] lngInterval = {-180.0, 180.0};
boolean isEven = true;
for (int i = 0; i < hash.length(); i++) {
final int cd = DECODE_MAP.get(hash.charAt(i));
for (int mask : BITS) {
if (isEven) {
if ((cd & mask) != 0) {
lngInterval[0] = (lngInterval[0] + lngInterval[1]) / 2D;
} else {
lngInterval[1] = (lngInterval[0] + lngInterval[1]) / 2D;
}
} else {
if ((cd & mask) != 0) {
latInterval[0] = (latInterval[0] + latInterval[1]) / 2D;
} else {
latInterval[1] = (latInterval[0] + latInterval[1]) / 2D;
}
}
isEven = !isEven;
}
}
GeoPoint nw = new GeoPoint(latInterval[1], lngInterval[0]);
GeoPoint se = new GeoPoint(latInterval[0], lngInterval[1]);
return new GeoRect(nw, se);
}
@Override
public boolean within(GeoShape geoShape) {
return toGeoRect().within(geoShape);
}
}