兄弟篇:【搬磚筆記】 利用GeoHash爲地理位置編碼——理論篇。java
本篇介紹採用Java實現GeoHash算法,如有任何錯誤或建議,望不吝賜教,不勝感激。git
完整代碼見:GitHubgithub
先羅列下實現的功能:算法
getBinary
函數。getBase32
函數。getNeighborWithDirection
函數。getNeighbors
函數。getNeighborsByTable
函數。getNeighborsByTable
函數。實現代碼以下:數組
import java.util.HashMap;
import java.util.Map;
public class GeoHash {
/** * 經度最小值 */
private static final double MIN_LONGTITUDE = -180.0;
/** * 經度最大值 */
private static final double MAX_LONGTITUDE = 180.0;
/** * 緯度最小值 */
private static final double MIN_LATITUDE = -90.0;
/** * 緯度最大值 */
private static final double MAX_LATITUDE = 90.0;
/** * 二進制表達最長爲64位 */
private static final Integer MAX_BINARY_BITS = 64;
/** * base32編碼最長爲13位,由於 5*13 = 65 > 64 */
private static final Integer MAX_BASE32_LENGTH = 13;
/** * 每五個bit轉換爲base32編碼 */
private static final int ENCODE_EVERY_BITS = 5;
/** * base32 編碼表 */
private static final char[] BASE32_LOOKUP = {'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'};
/** * base32 反編碼對照表 */
private final static Map<Character, Integer> BASE32_DECODE_LOOKUP = new HashMap<>();
/** * 編碼 0 填充對照表 */
private static final String[] PADDING_LOOKUP = {"", "0", "00", "000", "0000"};
/** * 方向枚舉 */
private enum Direction {
Top,
Right,
Bottom,
Left;
}
/** * 奇數查表 */
private static Map<Direction, String> ODD_LOOKUP = new HashMap<>(4);
/** * 偶數查表 */
private static Map<Direction, String> EVEN_LOOKUP = new HashMap<>(4);
/** * 奇數查表邊界 */
private static Map<Direction, String> ODD_BORDERS = new HashMap<>(4);
/** * 偶數查表邊界 */
private static Map<Direction, String> EVEN_BORDERS = new HashMap<>(4);
/** * 64bit中第一位的標記 */
private static final long FIRST_BIT_FLAGGED = 0x8000000000000000L;
/** * 靜態塊 */
static {
for (int i = 0; i < BASE32_LOOKUP.length; i++) {
BASE32_DECODE_LOOKUP.put(BASE32_LOOKUP[i], i);
}
ODD_LOOKUP.put(Direction.Top, "238967debc01fg45kmstqrwxuvhjyznp");
ODD_LOOKUP.put(Direction.Right, "14365h7k9dcfesgujnmqp0r2twvyx8zb");
ODD_LOOKUP.put(Direction.Bottom, "bc01fg45238967deuvhjyznpkmstqrwx");
ODD_LOOKUP.put(Direction.Left, "p0r21436x8zb9dcf5h7kjnmqesgutwvy");
EVEN_LOOKUP.put(Direction.Top, "14365h7k9dcfesgujnmqp0r2twvyx8zb");
EVEN_LOOKUP.put(Direction.Right, "238967debc01fg45kmstqrwxuvhjyznp");
EVEN_LOOKUP.put(Direction.Bottom, "p0r21436x8zb9dcf5h7kjnmqesgutwvy");
EVEN_LOOKUP.put(Direction.Left, "bc01fg45238967deuvhjyznpkmstqrwx");
ODD_BORDERS.put(Direction.Top, "bcfguvyz");
ODD_BORDERS.put(Direction.Right, "prxz");
ODD_BORDERS.put(Direction.Bottom, "0145hjnp");
ODD_BORDERS.put(Direction.Left, "028b");
EVEN_BORDERS.put(Direction.Top, "prxz");
EVEN_BORDERS.put(Direction.Right, "bcfguvyz");
EVEN_BORDERS.put(Direction.Bottom, "028b");
EVEN_BORDERS.put(Direction.Left, "0145hjnp");
}
/** * 將經緯度轉換爲二進制編碼 * * @param lon 經度 * @param lat 緯度 * @param precision 精度,例如13表明每一個維度上用13位二進制表示,經緯結合,共26位二進制。最長精度爲32。 * @return 二進制編碼 */
public static String getBinary(double lon, double lat, int precision) {
if (lon <= MIN_LONGTITUDE || lon >= MAX_LONGTITUDE) {
throw new IllegalArgumentException(String.format("經度取值範圍爲(%f, %f)", MIN_LONGTITUDE, MAX_LONGTITUDE));
}
if (lat <= MIN_LATITUDE || lat >= MAX_LATITUDE) {
throw new IllegalArgumentException(String.format("緯度取值範圍爲(%f, %f)", MIN_LATITUDE, MAX_LATITUDE));
}
if ((precision << 1) > MAX_BINARY_BITS) {
throw new IllegalArgumentException("精度最長爲32位");
}
// 經緯標識符,0表明經度,1表明緯度
int xyFlag = 0;
int bits = 1;
int binaryBits = precision << 1;
double[] lon_range = {MIN_LONGTITUDE, MAX_LONGTITUDE};
double[] lat_range = {MIN_LATITUDE, MAX_LATITUDE};
StringBuilder result = new StringBuilder();
while (bits <= binaryBits) {
char divideResult;
if (xyFlag == 0) {
divideResult = divideConquer(lon_range, lon);
} else {
divideResult = divideConquer(lat_range, lat);
}
result.append(divideResult);
bits++;
xyFlag = xyFlag ^ 0x1;
}
return result.toString();
}
/** * 將二進制編碼轉換爲base32編碼 * [!] 注意,這裏理應對二進制位數作出5的倍數的限制,可是爲了達到不一樣精度均可以經過base32編碼,縮短編碼長度,這裏不作限制, * 但計算出來的編碼不可用於查表法,只能經過通用法,解析經緯的方式計算鄰近。 * * @param binary 二進制編碼 * @return base32編碼 */
public static String getBase32(final String binary) {
valideBinary(binary);
StringBuilder result = new StringBuilder();
for (int i = 0; i < binary.length(); i += ENCODE_EVERY_BITS) {
int delta = Math.min(ENCODE_EVERY_BITS, binary.length() - i);
String tmp = binary.substring(i, i + delta);
int decimal = Integer.parseInt(tmp, 2);
result.append(BASE32_LOOKUP[decimal]);
}
return result.toString();
}
/** * 將base32編碼轉換爲二進制編碼 * * @param base32 base32編碼 * @param bits 二進制長度 * @return 二進制編碼 */
public static String getBinary(final String base32, int bits) {
valideBase32(base32);
int[] region = {(base32.length() - 1) * ENCODE_EVERY_BITS, base32.length() * ENCODE_EVERY_BITS};
if (bits <= region[0] || bits > region[1]) {
throw new IllegalArgumentException(String.format("精度與base32編碼不匹配,長度爲%d的base32編碼,其二進制長度應爲(%d, %d]", base32.length(), region[0], region[1]));
}
StringBuilder result = new StringBuilder();
for (int i = 0; i < base32.length(); ++i) {
int decimal = BASE32_DECODE_LOOKUP.get(base32.charAt(i));
String binary = Integer.toBinaryString(decimal);
// 須要補0的位數
int padding = Math.min(ENCODE_EVERY_BITS - binary.length(), bits - result.length());
binary = PADDING_LOOKUP[padding] + binary;
result.append(binary);
}
return result.toString();
}
/** * 查表法,獲取以當前base32編碼塊造成的九宮格,順序按照編碼順序,以下: * 0 1 2 * 3 4 5 * 6 7 8 * [!] 注意,查表法僅適用於二進制長度爲5的倍數。非5倍數的請用通用法。 * * @param base32 base32編碼 * @return base32編碼的數組 */
public static String[] getNeighborsByTable(final String base32) {
valideBase32(base32);
String[] result = new String[9];
result[4] = base32;
result[1] = getNeighborWithDirectionByTable(base32, Direction.Top);
result[0] = getNeighborWithDirectionByTable(result[1], Direction.Left);
result[2] = getNeighborWithDirectionByTable(result[1], Direction.Right);
result[5] = getNeighborWithDirectionByTable(base32, Direction.Right);
result[7] = getNeighborWithDirectionByTable(base32, Direction.Bottom);
result[3] = getNeighborWithDirectionByTable(base32, Direction.Left);
result[6] = getNeighborWithDirectionByTable(result[7], Direction.Left);
result[8] = getNeighborWithDirectionByTable(result[7], Direction.Right);
return result;
}
/** * 查表法,根據輸入的base32編碼和方向,獲取相應方向相鄰的base32編碼 * * @param base32 base32編碼 * @param dire 方向 * @return 相應方向相鄰的base32編碼 */
public static String getNeighborWithDirectionByTable(final String base32, Direction dire) {
valideBase32(base32);
boolean isOdd = (base32.length() & 0x1) == 0x1;
String prefix = base32.substring(0, base32.length() - 1);
char lastChar = base32.charAt(base32.length() - 1);
// 是否處於邊界
boolean inBorder;
char postfix;
if (isOdd) {
inBorder = ODD_BORDERS.get(dire).indexOf(lastChar) != -1;
postfix = ODD_LOOKUP.get(dire).charAt(BASE32_DECODE_LOOKUP.get(lastChar));
} else {
inBorder = EVEN_BORDERS.get(dire).indexOf(lastChar) != -1;
postfix = EVEN_LOOKUP.get(dire).charAt(BASE32_DECODE_LOOKUP.get(lastChar));
}
// 若處於邊界,繼續往下遞歸
if (inBorder) {
prefix = getNeighborWithDirectionByTable(prefix, dire);
}
String result = prefix + postfix;
return result;
}
/** * 通用法,根據輸入的base32編碼和方向,獲取相應方向相鄰的base32編碼 * * @param binary 二進制編碼 * @param dire 方向 * @return 相應方向相鄰的base32編碼 */
public static String getNeighborWithDirection(final String binary, final Direction dire) {
if (binary.length() <= 1) {
throw new IllegalArgumentException("二進制編碼長度至少爲2");
}
String result = binary;
String lat, lon;
int decimal;
switch (dire) {
case Top:
lat = extractEveryTwoBit(binary, 1);
decimal = Integer.parseInt(lat, 2);
decimal += 1;
lat = maskLastNBit(decimal, lat.length());
result = integrateEveryTwoBit(result, 1, lat);
break;
case Right:
lon = extractEveryTwoBit(binary, 0);
decimal = Integer.parseInt(lon, 2);
decimal += 1;
lon = maskLastNBit(decimal, lon.length());
result = integrateEveryTwoBit(result, 0, lon);
break;
case Bottom:
lat = extractEveryTwoBit(binary, 1);
decimal = Integer.parseInt(lat, 2);
decimal -= 1;
lat = maskLastNBit(decimal, lat.length());
result = integrateEveryTwoBit(result, 1, lat);
break;
case Left:
lon = extractEveryTwoBit(binary, 0);
decimal = Integer.parseInt(lon, 2);
decimal -= 1;
lon = maskLastNBit(decimal, lon.length());
result = integrateEveryTwoBit(result, 0, lon);
break;
}
return result;
}
/** * 通用法,根據二進制編碼,計算鄰近九宮格 * 0 1 2 * 3 4 5 * 6 7 8 * @param binary 二進制編碼 * @return 相應方向相鄰的二進制編碼 */
public static String[] getNeighbors(final String binary) {
valideBinary(binary);
String[] result = new String[9];
result[4] = binary;
result[1] = getNeighborWithDirection(binary, Direction.Top);
result[0] = getNeighborWithDirection(result[1], Direction.Left);
result[2] = getNeighborWithDirection(result[1], Direction.Right);
result[5] = getNeighborWithDirection(binary, Direction.Right);
result[7] = getNeighborWithDirection(binary, Direction.Bottom);
result[3] = getNeighborWithDirection(binary, Direction.Left);
result[6] = getNeighborWithDirection(result[7], Direction.Left);
result[8] = getNeighborWithDirection(result[7], Direction.Right);
return result;
}
/** * 取decimal中最後n位 * * @param decimal 十進制 * @param n 最後位數 * @return 最後n位的字符串 */
private static String maskLastNBit(long decimal, int n) {
long mask = 0xffffffffffffffffL;
decimal <<= (MAX_BINARY_BITS - n);
long nBit = decimal & mask;
StringBuilder res = new StringBuilder();
for (int i = 0; i < n; ++i, nBit <<= 1) {
if ((nBit & FIRST_BIT_FLAGGED) == FIRST_BIT_FLAGGED) {
res.append('1');
} else {
res.append('0');
}
}
return res.toString();
}
/** * 以索引start爲起始,提取二進制編碼binary中每隔一位的值 * * @param binary 二進制編碼 * @param start 起始下標 * @return 二進制編碼binary中每隔一位的值 */
private static String extractEveryTwoBit(String binary, int start) {
if (start >= binary.length()) {
throw new IllegalArgumentException("起始下表超出長度界限");
}
StringBuilder res = new StringBuilder();
for (int i = start; i < binary.length(); i += 2) {
res.append(binary.charAt(i));
}
return res.toString();
}
/** * 以索引start爲起始,將integrate值設置進二進制編碼binary中每隔一位的值 * * @param binary 二進制編碼 * @param start 起始下標 * @param integrate 待整合的編碼 * @return 整合後的二進制編碼 */
private static String integrateEveryTwoBit(String binary, int start, String integrate) {
if (start >= binary.length()) {
throw new IllegalArgumentException("起始下表超出長度界限");
}
StringBuilder res = new StringBuilder(binary);
for (int i = start, j = 0; i < binary.length() && j < integrate.length(); i += 2, ++j) {
res.replace(i, i + 1, integrate.substring(j, j + 1));
}
return res.toString();
}
/** * 二分,根據區間,計算中間值,大於給定值則給0,小於給定值則給1 * * @param range 區間 * @param target 給定值 * @return 大於給定值則給0,小於給定值則給1 */
private static char divideConquer(double[] range, double target) {
// 防止越界
double mid = (range[1] - range[0]) / 2 + range[0];
if (target < mid) {
range[1] = mid;
return '0';
} else {
range[0] = mid;
return '1';
}
}
/** * 驗證base32編碼,若驗證失敗則拋出異常 * * @param base32 base32編碼 */
private static void valideBase32(String base32) throws IllegalArgumentException {
if (base32 == null || base32.length() == 0) {
throw new IllegalArgumentException("base32編碼不能爲空");
}
if (base32.length() > MAX_BASE32_LENGTH) {
throw new IllegalArgumentException("base32編碼最長爲13位");
}
}
/** * 驗證base32編碼,若驗證失敗則拋出異常 * * @param binary base32編碼 */
private static void valideBinary(String binary) throws IllegalArgumentException {
if (binary == null || binary.length() == 0) {
throw new IllegalArgumentException("二進制編碼不能爲空");
}
if (binary.length() > MAX_BINARY_BITS) {
throw new IllegalArgumentException("二進制編碼最長爲64位");
}
}
}
複製代碼