目录
- 一、简介
- 二、计算方法
- 三、GeoHash的精度
- 四、查找相邻8个区域的Geohash编码(.NET)
最近项目中需要搜索周边的 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个区域编码
原文地址:https://www.cnblogs.com/zhurong/p/9886016.html