工作需要打却,實現(xiàn)了一下Geo Hash算法杉适。
盡量直接使用位操作鄙麦,比網(wǎng)上常見的字符串判斷位值得寫法效率應(yīng)該高一點怕膛。
TODO:循環(huán)的寫法可以再優(yōu)雅一點;注釋可以再清晰一點匪凉。
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
/**
* GeoHash編碼
* 采用base32格式編碼捌肴,5位一編碼蹬叭,存在經(jīng)緯度精度不一致的情況,此時經(jīng)度的經(jīng)度優(yōu)先
*
* @author pengjz <br>
* @version 1.0 <br>
* @description GeoHash <br>
* @date 2021/9/24 16:50 <br>
*/
public class GeoHash {
public static final int BASE_BIT_NUM = 5;
public static final double MIN_LAT = -90;
public static final double MAX_LAT = 90;
public static final double MIN_LON = -180;
public static final double MAX_LON = 180;
private final int hashLength;
/**
* 二進制長
*/
private final int bitLength;
/**
* 單獨經(jīng)緯度的長(單數(shù)取維度優(yōu)先)
*/
private final int latLength;
/**
* 該精度下最小維度
*/
private double minLat;
/**
* 該精度下最小經(jīng)度
*/
private double minLon;
private final static char[] DIGITS = {'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'};
/**
* 定義編碼映射關(guān)系
*/
final static HashMap<Character, Integer> LOOKUP = new HashMap<>();
/*
初始化編碼映射內(nèi)容
*/
static {
int i = 0;
for (char c : DIGITS) {
LOOKUP.put(c, i++);
}
}
/**
* 構(gòu)造函數(shù)状知,參數(shù)為hash編碼長度具垫,長度越長經(jīng)度越高
* 經(jīng)度的經(jīng)度優(yōu)先
*
* @param hashLength hash編碼長度
*/
public GeoHash(int hashLength) {
this.hashLength = hashLength;
this.latLength = (int) ((hashLength * BASE_BIT_NUM + 0.5) / 2 + 0.5);
this.bitLength = latLength * 2;
/*
* Hash編碼長度,也就是hash表示的經(jīng)度
*/
int scaleTime = hashLength * BASE_BIT_NUM / 2;
minLat = MAX_LAT - MIN_LAT;
for (int i = 0; i < scaleTime; i++) {
minLat /= 2.0;
}
minLon = MAX_LON - MIN_LON;
for (int i = 0; i < latLength; i++) {
minLon /= 2.0;
}
}
/**
* hash編碼
*
* @param lat 緯度
* @param lon 經(jīng)度
* @return geo hash
*/
public String encode(double lat, double lon) {
// 經(jīng)緯度的位編碼
BitSet latBits = getBits(lat, MIN_LAT, MAX_LAT);
BitSet lonBits = getBits(lon, MIN_LON, MAX_LON);
BitSet gpsBits = new BitSet();
/*
* 把位編碼整合试幽,上一步低精度在低位,高精度在高位卦碾,這里需要翻轉(zhuǎn)
*/
for (int i = 0; i < latLength; i++) {
if (lonBits.get(i)) {
gpsBits.set(bitLength - i * 2 - 1);
}
if (latBits.get(i)) {
gpsBits.set(bitLength - i * 2 - 2);
}
}
// 對整合后的位進行編碼
return bsToBase32(gpsBits);
}
public String encode(Gps gps) {
return encode(gps.getLat(), gps.getLon());
}
/**
* 將二進制編碼轉(zhuǎn)換為hash字符串
*
* @param bitSet 二進制編碼
* @return hash字符串
*/
private String bsToBase32(BitSet bitSet) {
StringBuilder sb = new StringBuilder();
for (int i = bitLength; i >= BASE_BIT_NUM; i -= BASE_BIT_NUM) {
final BitSet aSet = bitSet.get(i - BASE_BIT_NUM, i);
final long[] longs = aSet.toLongArray();
if (longs.length > 0) {
sb.append(DIGITS[(int) longs[0]]);
} else {
sb.append(DIGITS[0]);
}
}
return sb.toString();
}
public Gps decode(String geoHash) {
/*
* 整合位編碼
* 上一步高精度在高位铺坞,需要翻轉(zhuǎn)
*/
BitSet bitSet = base32ToBs(geoHash);
BitSet latSet = new BitSet();
BitSet lonSet = new BitSet();
for (int i = 0; i < latLength; i++) {
// 偶數(shù)位,經(jīng)度
if (bitSet.get(hashLength * BASE_BIT_NUM - 1 - i * 2)) {
lonSet.set(i);
}
// 奇數(shù)位洲胖,維度
if (hashLength * BASE_BIT_NUM - 2 - i * 2 >= 0
&& bitSet.get(hashLength * BASE_BIT_NUM - 2 - i * 2)) {
latSet.set(i);
}
}
// 算出來是格子靠近0济榨,0的角,需要加挪到格子中間更準(zhǔn)
return new Gps(
decodeLat(latSet, MIN_LAT, MAX_LAT) + minLat / 2,
decodeLat(lonSet, MIN_LON, MAX_LON) + minLon / 2
);
}
private double decodeLat(BitSet bitSet, double minLat, double maxLat) {
double mid = 0;
for (int i = 0; i < bitSet.length(); i++) {
mid = (minLat + maxLat) / 2;
if (bitSet.get(i)) {
minLat = mid;
} else {
maxLat = mid;
}
}
return mid;
}
private BitSet base32ToBs(String base32) {
BitSet bitSet = new BitSet();
for (int i = 0; i < this.hashLength; i++) {
int int32 = LOOKUP.get(base32.charAt(i));
for (int j = 0; j < BASE_BIT_NUM; j++) {
final int i1 = 1 << j;
if ((int32 & i1) == i1) {
bitSet.set((this.hashLength - 1 - i) * BASE_BIT_NUM + j);
}
}
}
return bitSet;
}
/**
* 獲取經(jīng)緯度的二進制編碼
*
* @param lat 經(jīng)度/緯度
* @param minLat 最小經(jīng)度/維度
* @param maxLat 最大經(jīng)度/維度
* @return 二進制編碼
*/
private BitSet getBits(double lat, double minLat, double maxLat) {
BitSet bitSet = new BitSet(latLength);
for (int i = 0; i < latLength; i++) {
double mid = (minLat + maxLat) / 2;
if (lat >= mid) {
bitSet.set(i);
minLat = mid;
} else {
maxLat = mid;
}
}
return bitSet;
}
public String[][] getAroundGeoHash(Gps gps) {
return getAroundGeoHash(gps.getLat(), gps.getLon());
}
/**
* 根據(jù)hash code獲取周圍的hashcode
*
* @param hashCode 中心點的hashcode
* @return 以hashcode為中心點的周圍九個格子的hashcode
*/
public String[][] getAroundGeoHash(String hashCode) {
Gps gps = this.decode(hashCode);
return getAroundGeoHash(gps.getLat(), gps.getLon());
}
/**
* 獲取包含周圍格子的geoHash字符串
* 返回值為二維數(shù)組绿映,對應(yīng)其方位
* 西北 正北 東北
* 正西 中間 正東
* 西南 正南 東南
*
* @param lat 中心點維度
* @param lon 中心點經(jīng)度
* @return 周圍的geoHash字符串
*/
public String[][] getAroundGeoHash(double lat, double lon) {
// 結(jié)果矩陣
String[][] result = new String[3][3];
// 北邊格子的維度
double northLat = lat + minLat;
// 南邊格子的維度
double southLat = lat - minLat;
// 東邊各自的經(jīng)度
double eastLon = lon + minLon;
// 西邊各自的經(jīng)度
double westLon = lon - minLon;
// 西北
result[0][0] = encode(northLat, westLon);
// 正北
result[0][1] = encode(northLat, lon);
// 東北
result[0][2] = encode(northLat, eastLon);
// 正西
result[1][0] = encode(lat, westLon);
// 正中
result[1][1] = encode(lat, lon);
// 正東
result[1][2] = encode(lat, eastLon);
// 西南
result[2][0] = encode(southLat, westLon);
// 正南
result[2][1] = encode(southLat, lon);
// 東南
result[2][2] = encode(southLat, eastLon);
return result;
}
/**
* 驗證:http://www.geohash.cn/
*
* @param args 參數(shù)
*/
public static void main(String[] args) {
Gps gps = new Gps(37.384482, 76.649411);
System.out.println(gps.getLon() + "," + gps.getLat());
final GeoHash geoHash6 = new GeoHash(6);
String centerHash = geoHash6.encode(gps);
System.out.println(centerHash);
final String[][] aroundGeoHash6 = geoHash6.getAroundGeoHash(gps);
for (String[] geoHash : aroundGeoHash6) {
System.out.println(Arrays.toString(geoHash));
}
Gps gpsDecode = geoHash6.decode(centerHash);
System.out.println(gpsDecode.getLon() + "," + gpsDecode.getLat());
final GeoHash geoHash7 = new GeoHash(7);
centerHash = geoHash7.encode(gps);
System.out.println(centerHash);
final String[][] aroundGeoHash7 = geoHash7.getAroundGeoHash(gps);
for (String[] geoHash : aroundGeoHash7) {
System.out.println(Arrays.toString(geoHash));
}
gpsDecode = geoHash7.decode(centerHash);
System.out.println(gpsDecode.getLon() + "," + gpsDecode.getLat());
}
}
代碼中用到得Gps類:
public class Gps {
double lat;
double lon;
public Gps(double lat, double lon) {
this.lat = lat;
this.lon = lon;
}
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public double getLon() {
return lon;
}
public void setLon(double lon) {
this.lon = lon;
}
}