zoukankan      html  css  js  c++  java
  • Geohash 基本知识及 .NET 下计算相邻8个区域编码

    目录

    最近项目中需要搜索周边的 POI 信息,查找的过程中了解到了 Geohash ,这这里记录下以便自己牢记也和大家分享下。

    一、简介

    GeoHash是一种地址编码方法。他能够把二维的空间经纬度数据编码成一个字符串。GeoHash具有以下特点:
    1、GeoHash用一个字符串表示经度和纬度两个坐标。在数据库中可以实现在一列上应用索引
    2、GeoHash表示的并不是一个点,而是一个区域;
    3、GeoHash编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。 这个特性可以用于附近地点搜索

    二、计算方法

    GeoHash的计算过程分为三步:
    1、将经纬度转换成二进制:
    比如这样一个点(39.923201, 116.390705)
    纬度的范围是(-90,90),其中间值为0。对于纬度39.923201,在区间(0,90)中,因此得到一个1;(0,90)区间的中间值为45度,纬度39.923201小于45,因此得到一个0,依次计算下去,即可得到纬度的二进制表示,如下表:



    最后得到纬度的二进制表示为:
    10111000110001111001
    同理可以得到经度116.390705的二进制表示为:
    11010010110001000100
    2、合并纬度、经度的二进制:
    合并方法是将经度、纬度二进制按照奇偶位合并:
    11100 11101 00100 01111 00000 01101 01011 00001
    3、按照Base32进行编码:
    Base32编码表(其中一种):

    将上述合并后二进制编码后结果为:
    wx4g0ec1

    三、GeoHash的精度

    编码越长,表示的范围越小,位置也越精确。因此我们就可以通过比较GeoHash匹配的位数来判断两个点之间的大概距离。

    四、查找相邻8个区域的Geohash编码(.NET)

    为什么会有这样的算法,原因是Geohash是有缺点的,如下:

    边缘附近的点,黄色的点要比黑色的点更加靠近红点,但是由于黑点跟红点的GeoHash前缀匹配数目更多,因此得到黑点更加靠近红点的结果(如下图)

    这个问题的解决办法就是:筛选周围8个区域内的所有点,然后计算距离得到满足条件结果

    下面是用C#写的在 .NET 平台下的寻找给定区域相邻的8个区域的代码

      1 using System;
      2 
      3 namespace sharonjl.utils
      4 {
      5     public static class Geohash
      6     {
      7         #region Direction enum
      8 
      9         public enum Direction
     10         {
     11             Top = 0,
     12             Right = 1,
     13             Bottom = 2,
     14             Left = 3 
     15         }
     16 
     17         #endregion
     18 
     19         private const string Base32 = "0123456789bcdefghjkmnpqrstuvwxyz";
     20         private static readonly int[] Bits = new[] {16, 8, 4, 2, 1};
     21 
     22         private static readonly string[][] Neighbors = {
     23                                                            new[]
     24                                                                {
     25                                                                    "p0r21436x8zb9dcf5h7kjnmqesgutwvy", // Top
     26                                                                    "bc01fg45238967deuvhjyznpkmstqrwx", // Right
     27                                                                    "14365h7k9dcfesgujnmqp0r2twvyx8zb", // Bottom
     28                                                                    "238967debc01fg45kmstqrwxuvhjyznp", // Left
     29                                                                }, new[]
     30                                                                       {
     31                                                                           "bc01fg45238967deuvhjyznpkmstqrwx", // Top
     32                                                                           "p0r21436x8zb9dcf5h7kjnmqesgutwvy", // Right
     33                                                                           "238967debc01fg45kmstqrwxuvhjyznp", // Bottom
     34                                                                           "14365h7k9dcfesgujnmqp0r2twvyx8zb", // Left
     35                                                                       }
     36                                                        };
     37 
     38         private static readonly string[][] Borders = {
     39                                                          new[] {"prxz", "bcfguvyz", "028b", "0145hjnp"},
     40                                                          new[] {"bcfguvyz", "prxz", "0145hjnp", "028b"}
     41                                                      };
     42         /// <summary>
     43         /// 计算相邻
     44         /// </summary>
     45         /// <param name="hash"></param>
     46         /// <param name="direction"></param>
     47         /// <returns></returns>
     48         public static String CalculateAdjacent(String hash, Direction direction)
     49         {
     50             hash = hash.ToLower();
     51 
     52             char lastChr = hash[hash.Length - 1];
     53             int type = hash.Length%2;
     54             var dir = (int) direction;
     55             string nHash = hash.Substring(0, hash.Length - 1);
     56 
     57             if (Borders[type][dir].IndexOf(lastChr) != -1)
     58             {
     59                 nHash = CalculateAdjacent(nHash, (Direction) dir);
     60             }
     61             return nHash + Base32[Neighbors[type][dir].IndexOf(lastChr)];
     62         }
     63         /// <summary>
     64         /// 细化间隔
     65         /// </summary>
     66         /// <param name="interval"></param>
     67         /// <param name="cd"></param>
     68         /// <param name="mask"></param>
     69         public static void RefineInterval(ref double[] interval, int cd, int mask)
     70         {
     71             if ((cd & mask) != 0)
     72             {
     73                 interval[0] = (interval[0] + interval[1])/2;
     74             }
     75             else
     76             {
     77                 interval[1] = (interval[0] + interval[1])/2;
     78             }
     79         }
     80 
     81         /// <summary>
     82         /// 解码
     83         /// </summary>
     84         /// <param name="geohash"></param>
     85         /// <returns></returns>
     86         public static double[] Decode(String geohash)
     87         {
     88             bool even = true;
     89             double[] lat = {-90.0, 90.0};
     90             double[] lon = {-180.0, 180.0};
     91 
     92             foreach (char c in geohash)
     93             {
     94                 int cd = Base32.IndexOf(c);
     95                 for (int j = 0; j < 5; j++)
     96                 {
     97                     int mask = Bits[j];
     98                     if (even)
     99                     {
    100                         RefineInterval(ref lon, cd, mask);
    101                     }
    102                     else
    103                     {
    104                         RefineInterval(ref lat, cd, mask);
    105                     }
    106                     even = !even;
    107                 }
    108             }
    109 
    110             return new[] {(lat[0] + lat[1])/2, (lon[0] + lon[1])/2};
    111         }
    112         /// <summary>
    113         /// 编码
    114         /// </summary>
    115         /// <param name="latitude">纬度</param>
    116         /// <param name="longitude">经度</param>
    117         /// <param name="precision">精度</param>
    118         /// <returns></returns>
    119         public static String Encode(double latitude, double longitude, int precision = 12)
    120         {
    121             bool even = true;
    122             int bit = 0;
    123             int ch = 0;
    124             string geohash = "";
    125 
    126             double[] lat = {-90.0, 90.0};
    127             double[] lon = {-180.0, 180.0};
    128 
    129             if (precision < 1 || precision > 20) precision = 12;
    130 
    131             while (geohash.Length < precision)
    132             {
    133                 double mid;
    134 
    135                 if (even)
    136                 {
    137                     mid = (lon[0] + lon[1])/2;
    138                     if (longitude > mid)
    139                     {
    140                         ch |= Bits[bit];
    141                         lon[0] = mid;
    142                     }
    143                     else
    144                         lon[1] = mid;
    145                 }
    146                 else
    147                 {
    148                     mid = (lat[0] + lat[1])/2;
    149                     if (latitude > mid)
    150                     {
    151                         ch |= Bits[bit];
    152                         lat[0] = mid;
    153                     }
    154                     else
    155                         lat[1] = mid;
    156                 }
    157 
    158                 even = !even;
    159                 if (bit < 4)
    160                     bit++;
    161                 else
    162                 {
    163                     geohash += Base32[ch];
    164                     bit = 0;
    165                     ch = 0;
    166                 }
    167             }
    168             return geohash;
    169         }
    170 
    171         /// <summary>
    172         /// 获取九个格子 顺序 本身 上、下、左、右、 左上、 右上、 左下、右下
    173         /// </summary>
    174         /// <param name="geohash"></param>
    175         /// <returns></returns>
    176         public static String[] getGeoHashExpand(String geohash)
    177         { 
    178         
    179         try {
    180             String geohashTop = CalculateAdjacent(geohash, Direction.Top);//
    181 
    182             String geohashBottom = CalculateAdjacent(geohash, Direction.Bottom);//
    183 
    184             String geohashLeft = CalculateAdjacent(geohash, Direction.Left);//
    185 
    186             String geohashRight = CalculateAdjacent(geohash, Direction.Right);//
    187 
    188 
    189             String geohashTopLeft = CalculateAdjacent(geohashLeft, Direction.Top);//左上
    190 
    191             String geohashTopRight = CalculateAdjacent(geohashRight, Direction.Top);//右上
    192 
    193             String geohashBottomLeft = CalculateAdjacent(geohashLeft, Direction.Bottom);//左下
    194 
    195             String geohashBottomRight = CalculateAdjacent(geohashRight, Direction.Bottom);//右下
    196 
    197             String[] expand = { geohash, geohashTop, geohashBottom, geohashLeft, geohashRight, geohashTopLeft, geohashTopRight,  geohashBottomLeft, geohashBottomRight};
    198             return expand;
    199         } catch (Exception e) {
    200             return null;
    201         }
    202         }
    203 
    204 
    205         ///// <summary>
    206         ///// test 
    207         ///// </summary>
    208         ///// <param name="args"></param>
    209         //public  void main()
    210         //{
    211         //    double lat = 39.90403;
    212         //    double lon = 116.407526; //需要查询经纬度,目前指向的是BeiJing
    213         //    string hash = Geohash.Encode(lat, lon);
    214         //    int geohashLen = 6;
    215         //    /*获取中心点的geohash*/
    216         //    String geohash = hash.Substring(0, geohashLen);
    217         //    /*获取所有的矩形geohash, 一共是九个 ,包含中心点,打印顺序请参考参数*/
    218         //    String[] result = Geohash.getGeoHashExpand(geohash);
    219         //}
    220     }
    221 }
    View Code

    五、MySQL 中使用 GeoHash 

    在MySQL 5.7 以后,对GIS空间数据的更好的支持,加上虚拟列可以很方便的使用GeoHash。

    选择存储 GeoHash 的列,设置为“虚拟列”,在“表达式”里填入“st_geohash(`point`,6)”,其中 point 是要编码的点字段

    参考:

    https://blog.csdn.net/youhongaa/article/details/78816700

    https://www.cnblogs.com/lucoo/p/5085986.html

  • 相关阅读:
    开发中的报错问题
    vue2.0入门
    nodejs的一些基操
    git
    es6模块化规范
    js面向对象的笼统介绍
    this指向问题(改变它的指向)
    js面向对象杂谈
    原生js贪吃蛇
    bootstrap基础自我总结
  • 原文地址:https://www.cnblogs.com/zhurong/p/9886016.html
Copyright © 2011-2022 走看看