导读
    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算法切分OID4点码 - 图1

    如果在小块范围内递归对半划分呢?
    基于Geohash算法切分OID4点码 - 图2

    可以看到,划分的区域更多了,也更精确了。
    geohash算法就是基于这种思想,划分的次数更多,区域更多,区域面积更小了。
    Geohash算法
    Geohash算法一共有三步
    1、将经纬度变成二进制
    比如这样一个点(39.923201, 116.390705)
    基于Geohash算法切分OID4点码 - 图3

    通过将经纬度编码,给地理位置分区,根据需要的精度,按照上面的方法,逐级换算,最终能得到经、纬度的二进制字符串。
    纬度的二进制为:
    10111000110001111001
    经度116.390705的二进制为:
    11010010110001000100
    2、合并经纬度
    合并方法是将经度、纬度二进制按照奇偶位合并:
    11100 11101 00100 01111 00000 01101 01011
    00001
    3、按照Base32进行编码:
    Base32编码表的其中一种如下
    基于Geohash算法切分OID4点码 - 图4
    是用0-9、b-z(去掉a, i, l, o)这32个字母进行编码。具体操作是先将上一步得到的合并后二进制转换为10进制数据,然后对应生成Base32码。需要注意的是,将5个二进制位转换成一个base32码。

    上例最终得到的值为:
    wx4g0ec1
    基于Geohash算法对OID4范围码点进行切分
    主要改动点:
    1、根据geohash原理,将经纬度的极限值改为,OID4的码值范围
    2、考虑到存储数字比存储字符串性能更优,所以在得到指定层级切分后的二进制字符串后,将其转换为十进制值数字,而不是base32
    3、码值范围比经纬度大很多,所以直接采用整数部分,忽略小数
    核心代码:
    将坐标,按指定层级、指定范围转换为二进制

    1. private static void transBanaryCode(long xy, long startRange, long endRange, int level, int count, BiConsumer<Integer, Character> consumer){
    2. long middle = (startRange + endRange) >> 1;
    3. char code = ONE_CHAR;
    4. if(xy < middle){
    5. code = ZERO_CHAR;
    6. endRange = middle;
    7. } else {
    8. startRange = middle;
    9. }
    10. consumer.accept(count, code);
    11. count++;
    12. if(count >= level){
    13. return;
    14. }
    15. transBanaryCode(xy, startRange, endRange, level, count, consumer);
    16. }

    十进制转二进制

    1. public static char[] toBinary(int num, int accuracy) {
    2. char[] chars = new char[accuracy];
    3. int count = accuracy - 1;
    4. while (num != 0) {
    5. chars[count--] = num % 2 == 0 ? ZERO_CHAR : ONE_CHAR;
    6. num = num >> 1;
    7. }
    8. while (count >= 0){
    9. chars[count--] = ZERO_CHAR;
    10. }
    11. return chars;
    12. }

    根据编码获取指定层级、指定范围所属慢点的坐标范围

    1. private static long[] getRange(int count, int level, long startRange, long endRange, Function<Integer, Character> function){
    2. long middle = (startRange + endRange) >> 1;
    3. if(ZERO_CHAR == function.apply(count)){
    4. endRange = middle;
    5. } else {
    6. startRange = middle;
    7. }
    8. count++;
    9. if(count >= level){
    10. return new long[]{startRange, endRange};
    11. }
    12. return getRange(count, level, startRange, endRange, function);
    13. }

    至此就实现了,根据坐标,定位其所属点码块;根据点码块,获取其点码范围。