Geohash 基本知识及 .NET 下计算相邻8个区域编码

Posted zhurong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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个区域的代码

using System;

namespace sharonjl.utils
{
    public static class Geohash
    {
        #region Direction enum

        public enum Direction
        {
            Top = 0,
            Right = 1,
            Bottom = 2,
            Left = 3 
        }

        #endregion

        private const string Base32 = "0123456789bcdefghjkmnpqrstuvwxyz";
        private static readonly int[] Bits = new[] {16, 8, 4, 2, 1};

        private static readonly string[][] Neighbors = {
                                                           new[]
                                                               {
                                                                   "p0r21436x8zb9dcf5h7kjnmqesgutwvy", // Top
                                                                   "bc01fg45238967deuvhjyznpkmstqrwx", // Right
                                                                   "14365h7k9dcfesgujnmqp0r2twvyx8zb", // Bottom
                                                                   "238967debc01fg45kmstqrwxuvhjyznp", // Left
                                                               }, new[]
                                                                      {
                                                                          "bc01fg45238967deuvhjyznpkmstqrwx", // Top
                                                                          "p0r21436x8zb9dcf5h7kjnmqesgutwvy", // Right
                                                                          "238967debc01fg45kmstqrwxuvhjyznp", // Bottom
                                                                          "14365h7k9dcfesgujnmqp0r2twvyx8zb", // Left
                                                                      }
                                                       };

        private static readonly string[][] Borders = {
                                                         new[] {"prxz", "bcfguvyz", "028b", "0145hjnp"},
                                                         new[] {"bcfguvyz", "prxz", "0145hjnp", "028b"}
                                                     };
        /// <summary>
        /// 计算相邻
        /// </summary>
        /// <param name="hash"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        public static String CalculateAdjacent(String hash, Direction direction)
        {
            hash = hash.ToLower();

            char lastChr = hash[hash.Length - 1];
            int type = hash.Length%2;
            var dir = (int) direction;
            string nHash = hash.Substring(0, hash.Length - 1);

            if (Borders[type][dir].IndexOf(lastChr) != -1)
            {
                nHash = CalculateAdjacent(nHash, (Direction) dir);
            }
            return nHash + Base32[Neighbors[type][dir].IndexOf(lastChr)];
        }
        /// <summary>
        /// 细化间隔
        /// </summary>
        /// <param name="interval"></param>
        /// <param name="cd"></param>
        /// <param name="mask"></param>
        public static void RefineInterval(ref double[] interval, int cd, int mask)
        {
            if ((cd & mask) != 0)
            {
                interval[0] = (interval[0] + interval[1])/2;
            }
            else
            {
                interval[1] = (interval[0] + interval[1])/2;
            }
        }

        /// <summary>
        /// 解码
        /// </summary>
        /// <param name="geohash"></param>
        /// <returns></returns>
        public static double[] Decode(String geohash)
        {
            bool even = true;
            double[] lat = {-90.0, 90.0};
            double[] lon = {-180.0, 180.0};

            foreach (char c in geohash)
            {
                int cd = Base32.IndexOf(c);
                for (int j = 0; j < 5; j++)
                {
                    int mask = Bits[j];
                    if (even)
                    {
                        RefineInterval(ref lon, cd, mask);
                    }
                    else
                    {
                        RefineInterval(ref lat, cd, mask);
                    }
                    even = !even;
                }
            }

            return new[] {(lat[0] + lat[1])/2, (lon[0] + lon[1])/2};
        }
        /// <summary>
        /// 编码
        /// </summary>
        /// <param name="latitude">纬度</param>
        /// <param name="longitude">经度</param>
        /// <param name="precision">精度</param>
        /// <returns></returns>
        public static String Encode(double latitude, double longitude, int precision = 12)
        {
            bool even = true;
            int bit = 0;
            int ch = 0;
            string geohash = "";

            double[] lat = {-90.0, 90.0};
            double[] lon = {-180.0, 180.0};

            if (precision < 1 || precision > 20) precision = 12;

            while (geohash.Length < precision)
            {
                double mid;

                if (even)
                {
                    mid = (lon[0] + lon[1])/2;
                    if (longitude > mid)
                    {
                        ch |= Bits[bit];
                        lon[0] = mid;
                    }
                    else
                        lon[1] = mid;
                }
                else
                {
                    mid = (lat[0] + lat[1])/2;
                    if (latitude > mid)
                    {
                        ch |= Bits[bit];
                        lat[0] = mid;
                    }
                    else
                        lat[1] = mid;
                }

                even = !even;
                if (bit < 4)
                    bit++;
                else
                {
                    geohash += Base32[ch];
                    bit = 0;
                    ch = 0;
                }
            }
            return geohash;
        }

        /// <summary>
        /// 获取九个格子 顺序 本身 上、下、左、右、 左上、 右上、 左下、右下
        /// </summary>
        /// <param name="geohash"></param>
        /// <returns></returns>
        public static String[] getGeoHashExpand(String geohash)
        { 
        
        try {
            String geohashTop = CalculateAdjacent(geohash, Direction.Top);//

            String geohashBottom = CalculateAdjacent(geohash, Direction.Bottom);//

            String geohashLeft = CalculateAdjacent(geohash, Direction.Left);//

            String geohashRight = CalculateAdjacent(geohash, Direction.Right);//


            String geohashTopLeft = CalculateAdjacent(geohashLeft, Direction.Top);//左上

            String geohashTopRight = CalculateAdjacent(geohashRight, Direction.Top);//右上

            String geohashBottomLeft = CalculateAdjacent(geohashLeft, Direction.Bottom);//左下

            String geohashBottomRight = CalculateAdjacent(geohashRight, Direction.Bottom);//右下

            String[] expand = { geohash, geohashTop, geohashBottom, geohashLeft, geohashRight, geohashTopLeft, geohashTopRight,  geohashBottomLeft, geohashBottomRight};
            return expand;
        } catch (Exception e) {
            return null;
        }
        }


        ///// <summary>
        ///// test 
        ///// </summary>
        ///// <param name="args"></param>
        //public  void main()
        //{
        //    double lat = 39.90403;
        //    double lon = 116.407526; //需要查询经纬度,目前指向的是BeiJing
        //    string hash = Geohash.Encode(lat, lon);
        //    int geohashLen = 6;
        //    /*获取中心点的geohash*/
        //    String geohash = hash.Substring(0, geohashLen);
        //    /*获取所有的矩形geohash, 一共是九个 ,包含中心点,打印顺序请参考参数*/
        //    String[] result = Geohash.getGeoHashExpand(geohash);
        //}
    }
}

 参考:

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

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


















以上是关于Geohash 基本知识及 .NET 下计算相邻8个区域编码的主要内容,如果未能解决你的问题,请参考以下文章

如何计算 geohash 上的相邻网格。需要算法

GeoHash算法学习讲解解析及原理分析

geohash算法原理及实现方式

地理空间索引:GeoHash原理

地理空间索引:GeoHash原理

GeoHash