导读
OID编码就是光学辨别编码,利用光识别与点读发音技术,达到快速点读,识别记忆的效果;本文主要介绍:Geohash算法在点阵编码中的应用。
什么是Geohash
GeoHash是一种地址编码方法,他能够把二维的空间经纬度数据编码成一个字符串。
GeoHash具有以下特点:
1、GeoHash用一个字符串表示经度和纬度两个坐标。在数据库中可以实现在一列上应用索引;
2、GeoHash表示的并不是一个点,而是一个区域;
3、GeoHash编码的前缀可以表示更大的区域。
我们知道,经度范围是东经180到西经180,纬度范围是南纬90到北纬90,我们设定西经为负,南纬为负,所以地球上的经度范围就是[-180, 180],纬度范围就是[-90,90]。如果以本初子午线、赤道为界,地球可以分成4个部分。
如果纬度范围[-90°, 0°)用二进制0代表,(0°, 90°]用二进制1代表,经度范围[-180°, 0°)用二进制0代表,(0°, 180°]用二进制1代表,那么地球可以分成如下4个部分:
如果在小块范围内递归对半划分呢?
可以看到,划分的区域更多了,也更精确了。
geohash算法就是基于这种思想,划分的次数更多,区域更多,区域面积更小了。
Geohash算法
Geohash算法一共有三步
1、将经纬度变成二进制
比如这样一个点(39.923201, 116.390705)
通过将经纬度编码,给地理位置分区,根据需要的精度,按照上面的方法,逐级换算,最终能得到经、纬度的二进制字符串。
纬度的二进制为:
10111000110001111001
经度116.390705的二进制为:
11010010110001000100
2、合并经纬度
合并方法是将经度、纬度二进制按照奇偶位合并:
11100 11101 00100 01111 00000 01101 01011
00001
3、按照Base32进行编码:
Base32编码表的其中一种如下
是用0-9、b-z(去掉a, i, l, o)这32个字母进行编码。具体操作是先将上一步得到的合并后二进制转换为10进制数据,然后对应生成Base32码。需要注意的是,将5个二进制位转换成一个base32码。
上例最终得到的值为:
wx4g0ec1
基于Geohash算法对OID4范围码点进行切分
主要改动点:
1、根据geohash原理,将经纬度的极限值改为,OID4的码值范围
2、考虑到存储数字比存储字符串性能更优,所以在得到指定层级切分后的二进制字符串后,将其转换为十进制值数字,而不是base32
3、码值范围比经纬度大很多,所以直接采用整数部分,忽略小数
核心代码:
将坐标,按指定层级、指定范围转换为二进制
private static void transBanaryCode(long xy, long startRange, long endRange, int level, int count, BiConsumer<Integer, Character> consumer){
long middle = (startRange + endRange) >> 1;
char code = ONE_CHAR;
if(xy < middle){
code = ZERO_CHAR;
endRange = middle;
} else {
startRange = middle;
}
consumer.accept(count, code);
count++;
if(count >= level){
return;
}
transBanaryCode(xy, startRange, endRange, level, count, consumer);
}
十进制转二进制
public static char[] toBinary(int num, int accuracy) {
char[] chars = new char[accuracy];
int count = accuracy - 1;
while (num != 0) {
chars[count--] = num % 2 == 0 ? ZERO_CHAR : ONE_CHAR;
num = num >> 1;
}
while (count >= 0){
chars[count--] = ZERO_CHAR;
}
return chars;
}
根据编码获取指定层级、指定范围所属慢点的坐标范围
private static long[] getRange(int count, int level, long startRange, long endRange, Function<Integer, Character> function){
long middle = (startRange + endRange) >> 1;
if(ZERO_CHAR == function.apply(count)){
endRange = middle;
} else {
startRange = middle;
}
count++;
if(count >= level){
return new long[]{startRange, endRange};
}
return getRange(count, level, startRange, endRange, function);
}
至此就实现了,根据坐标,定位其所属点码块;根据点码块,获取其点码范围。