博客园  :: 首页  :: 联系 :: 管理

geohash-net实现

Posted on 2016-12-22 22:52  天戈朱  阅读(1253)  评论(0编辑  收藏  举报

  基于c#语言 geohash算法基本实现源码,参见: https://github.com/sharonjl/geohash-net , 源码中具体包含如下方法:

  • String CalculateAdjacent(String hash, Direction direction);               //根据指定hash码,获取相邻的上下左右矩形框hash码
  • double[] Decode(String geohash);                                                  //根据指定hash码返回经纬度
  • String Encode(double latitude, double longitude, int precision = 12); //根据经纬度和指定的hash码长度获取hash码

但在实际应用的过程中,由于GeoHash是将区域划分为一个个规则矩形,并对每个矩形进行编码,这样在查询附近POI信息时会导致以下问题,比如红色的点是我们的位 置,绿色的两个点分别是附近的两个餐馆,但是在查询的时候会发现距离较远餐馆的GeoHash编码与我们一样(因为在同一个GeoHash区域块上),而 较近餐馆的GeoHash编码与我们不一致。这个问题往往产生在边界处,如下图:

 解决方案:在查询时,除了使用定位点的GeoHash编码进行匹配外,还使用周围8个区域的GeoHash编码,取出相邻的8个区域范围内的所有餐馆作为第一层过滤,然后根据当前位置计算出与8个相邻区域内每个餐厅的距离,按距离的由小到大顺序返回结果,扩展代码如下:

  • 获取指定hash对应矩形框相邻的8个区域示例代码:
    /// <summary>
            /// 获取九个格子 顺序 本身 上、下、左、右、 左上、 右上、 左下、右下
            /// </summary>
            /// <param name="geohash"></param>
            /// <returns></returns>
            public List<string> GetGeoHashExpand(String geohash)
            {
                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);//右下
    
                return new List<string>() { geohash, geohashTop, geohashBottom, geohashLeft, geohashRight,
    geohashTopLeft, geohashTopRight, geohashBottomLeft, geohashBottomRight }; }
  • 计算两点之间距离示例代码
    //地球半径,单位米
            private const double EARTH_RADIUS = 6378137;
            /// <summary>
            /// 计算两点位置的距离,返回两点的距离,单位 米
            /// 该公式为GOOGLE提供,误差小于0.2米
            /// </summary>
            /// <param name="lat1">第一点纬度</param>
            /// <param name="lng1">第一点经度</param>
            /// <param name="lat2">第二点纬度</param>
            /// <param name="lng2">第二点经度</param>
            /// <returns></returns>
            public static double GetDistance(double lat1, double lng1, double lat2, double lng2)
            {
                double radLat1 = Rad(lat1);
                double radLng1 = Rad(lng1);
                double radLat2 = Rad(lat2);
                double radLng2 = Rad(lng2);
                double a = radLat1 - radLat2;
                double b = radLng1 - radLng2;
                double result = 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin(a / 2), 2) + Math.Cos(radLat1) * Math.Cos(radLat2) * Math.Pow(Math.Sin(b / 2), 2))) * EARTH_RADIUS;
                return result;
            }
    
            /// <summary>
            /// 经纬度转化成弧度
            /// </summary>
            /// <param name="d"></param>
            /// <returns></returns>
            private static double Rad(double d)
            {
                return (double)d * Math.PI / 180d;
            }

 

  •  计算hash码对应矩形框经纬度范围代码(左下角、右上角),参考: https://github.com/aerisweather/node-geohash/blob/master/main.js , c#实现示例码如下:
     public double[] Decode_bbox(string hash_string)
            {
                bool isLon = true;
                double maxLat = 90,minLat = -90,maxLon = 180,minLon = -180,mid;
    
                int hashValue = 0;
                int l = hash_string.Length;
                for (int i = 0; i < l; i++)
                {
                    string code = Convert.ToString(hash_string[i]);
                    hashValue = BASE32_CODES_DICT[code];
    
                    for (var bits = 4; bits >= 0; bits--)
                    {
                        var bit = (hashValue >> bits) & 1;
                        if (isLon)
                        {
                            mid = (maxLon + minLon) / 2;
                            if (bit == 1)
                            {
                                minLon = mid;
                            }
                            else
                            {
                                maxLon = mid;
                            }
                        }
                        else
                        {
                            mid = (maxLat + minLat) / 2;
                            if (bit == 1)
                            {
                                minLat = mid;
                            }
                            else
                            {
                                maxLat = mid;
                            }
                        }
                        isLon = !isLon;
                    }
                }
                return new double[] { minLat, minLon, maxLat, maxLon };
            }

 

  • 基于以上的代码,在实际应用中基本可完成对附件范围内事物的搜索以及在地图中进行区域标识