geohash算法原理及实现方式
3、geohash的php 、python、java、C#实现代码
4、观点讨论
首先,geohash用一个字符串表示经度和纬度两个坐标。某些情况下无法在两列上同时应用索引 (例如MySQL 4之前的版本,Google App Engine的数据层等),利用geohash,只需在一列上应用索引即可。
其次,geohash表示的并不是一个点,而是一个矩形区域。比如编码wx4g0ec19,它表示的是一个矩形区域。 使用者可以发布地址编码,既能表明自己位于北海公园附近,又不至于暴露自己的精确坐标,有助于隐私保护。
第三,编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。 这个特性可以用于附近地点搜索。首先根据用户当前坐标计算geohash(例如wx4g0ec1)然后取其前缀进行查询 (SELECT * FROM place WHERE geohash LIKE 'wx4g0e%'),即可查询附近的所有地点。
Geohash比直接用经纬度的高效很多。
Geohash的最简单的解释就是:将一个经纬度信息,转换成一个可以排序,可以比较的字符串编码
首先将纬度范围(-90, 90)平分成两个区间(-90,0)、(0, 90),如果目标纬度位于前一个区间,则编码为0,否则编码为1。
由于39.92324属于(0, 90),所以取编码为1。
然后再将(0, 90)分成 (0, 45), (45, 90)两个区间,而39.92324位于(0, 45),所以编码为0。
以此类推,直到精度符合要求为止,得到纬度编码为1011 1000 1100 0111 1001。
纬度范围 |
划分区间0 |
划分区间1 |
39.92324所属区间 |
(-90, 90) |
(-90, 0.0) |
(0.0, 90) |
1 |
(0.0, 90) |
(0.0, 45.0) |
(45.0, 90) |
0 |
(0.0, 45.0) |
(0.0, 22.5) |
(22.5, 45.0) |
1 |
(22.5, 45.0) |
(22.5, 33.75) |
(33.75, 45.0) |
1 |
(33.75, 45.0) |
(33.75, 39.375) |
(39.375, 45.0) |
1 |
(39.375, 45.0) |
(39.375, 42.1875) |
(42.1875, 45.0) |
0 |
(39.375, 42.1875) |
(39.375, 40.7812) |
(40.7812, 42.1875) |
0 |
(39.375, 40.7812) |
(39.375, 40.0781) |
(40.0781, 40.7812) |
0 |
(39.375, 40.0781) |
(39.375, 39.7265) |
(39.7265, 40.0781) |
1 |
(39.7265, 40.0781) |
(39.7265, 39.9023) |
(39.9023, 40.0781) |
1 |
(39.9023, 40.0781) |
(39.9023, 39.9902) |
(39.9902, 40.0781) |
0 |
(39.9023, 39.9902) |
(39.9023, 39.9462) |
(39.9462, 39.9902) |
0 |
(39.9023, 39.9462) |
(39.9023, 39.9243) |
(39.9243, 39.9462) |
0 |
(39.9023, 39.9243) |
(39.9023, 39.9133) |
(39.9133, 39.9243) |
1 |
(39.9133, 39.9243) |
(39.9133, 39.9188) |
(39.9188, 39.9243) |
1 |
(39.9188, 39.9243) |
(39.9188, 39.9215) |
(39.9215, 39.9243) |
1 |
经度也用同样的算法,对(-180, 180)依次细分,得到116.3906的编码为1101 0010 1100 0100 0100。
经度范围 |
划分区间0 |
划分区间1 |
116.3906所属区间 |
(-180, 180) |
(-180, 0.0) |
(0.0, 180) |
1 |
(0.0, 180) |
(0.0, 90.0) |
(90.0, 180) |
1 |
(90.0, 180) |
(90.0, 135.0) |
(135.0, 180) |
0 |
(90.0, 135.0) |
(90.0, 112.5) |
(112.5, 135.0) |
1 |
(112.5, 135.0) |
(112.5, 123.75) |
(123.75, 135.0) |
0 |
(112.5, 123.75) |
(112.5, 118.125) |
(118.125, 123.75) |
0 |
(112.5, 118.125) |
(112.5, 115.312) |
(115.312, 118.125) |
1 |
(115.312, 118.125) |
(115.312, 116.718) |
(116.718, 118.125) |
0 |
(115.312, 116.718) |
(115.312, 116.015) |
(116.015, 116.718) |
1 |
(116.015, 116.718) |
(116.015, 116.367) |
(116.367, 116.718) |
1 |
(116.367, 116.718) |
(116.367, 116.542) |
(116.542, 116.718) |
0 |
(116.367, 116.542) |
(116.367, 116.455) |
(116.455, 116.542) |
0 |
(116.367, 116.455) |
(116.367, 116.411) |
(116.411, 116.455) |
0 |
(116.367, 116.411) |
(116.367, 116.389) |
(116.389, 116.411) |
1 |
(116.389, 116.411) |
(116.389, 116.400) |
(116.400, 116.411) |
0 |
(116.389, 116.400) |
(116.389, 116.394) |
(116.394, 116.400) |
0 |
接下来将经度和纬度的编码合并,奇数位是纬度,偶数位是经度,得到编码 11100 11101 00100 01111 00000 01101 01011 00001。
最后,用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,得到(39.92324, 116.3906)的编码为wx4g0ec1。
十进制 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
base32 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
b |
c |
d |
e |
f |
g |
十进制 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
base32 |
h |
j |
k |
m |
n |
p |
q |
r |
s |
t |
u |
v |
w |
x |
y |
z |
解码算法与编码算法相反,先进行base32解码,然后分离出经纬度,最后根据二进制编码对经纬度范围进行细分即可,这里不再赘述。
php版本的实现方式:http://blog.dixo.net/downloads/geohash-php-class/ 我下载了一个上传的
php:
geohash.class.php
python:
python版本的geohash:python-geohash
java:
java版本的geohash,实现:http://code.google.com/p/geospatialweb/source/browse/#svn/trunk/geohash/src
C#:
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 43 public static String CalculateAdjacent(String hash, Direction direction) 44 { 45 hash = hash.ToLower(); 46 47 char lastChr = hash[hash.Length - 1]; 48 int type = hash.Length%2; 49 var dir = (int) direction; 50 string nHash = hash.Substring(0, hash.Length - 1); 51 52 if (Borders[type][dir].IndexOf(lastChr) != -1) 53 { 54 nHash = CalculateAdjacent(nHash, (Direction) dir); 55 } 56 return nHash + Base32[Neighbors[type][dir].IndexOf(lastChr)]; 57 } 58 59 public static void RefineInterval(ref double[] interval, int cd, int mask) 60 { 61 if ((cd & mask) != 0) 62 { 63 interval[0] = (interval[0] + interval[1])/2; 64 } 65 else 66 { 67 interval[1] = (interval[0] + interval[1])/2; 68 } 69 } 70 71 public static double[] Decode(String geohash) 72 { 73 bool even = true; 74 double[] lat = {-90.0, 90.0}; 75 double[] lon = {-180.0, 180.0}; 76 77 foreach (char c in geohash) 78 { 79 int cd = Base32.IndexOf(c); 80 for (int j = 0; j < 5; j++) 81 { 82 int mask = Bits[j]; 83 if (even) 84 { 85 RefineInterval(ref lon, cd, mask); 86 } 87 else 88 { 89 RefineInterval(ref lat, cd, mask); 90 } 91 even = !even; 92 } 93 } 94 95 return new[] {(lat[0] + lat[1])/2, (lon[0] + lon[1])/2}; 96 } 97 98 public static String Encode(double latitude, double longitude, int precision = 12) 99 { 100 bool even = true; 101 int bit = 0; 102 int ch = 0; 103 string geohash = ""; 104 105 double[] lat = {-90.0, 90.0}; 106 double[] lon = {-180.0, 180.0}; 107 108 if (precision < 1 || precision > 20) precision = 12; 109 110 while (geohash.Length < precision) 111 { 112 double mid; 113 114 if (even) 115 { 116 mid = (lon[0] + lon[1])/2; 117 if (longitude > mid) 118 { 119 ch |= Bits[bit]; 120 lon[0] = mid; 121 } 122 else 123 lon[1] = mid; 124 } 125 else 126 { 127 mid = (lat[0] + lat[1])/2; 128 if (latitude > mid) 129 { 130 ch |= Bits[bit]; 131 lat[0] = mid; 132 } 133 else 134 lat[1] = mid; 135 } 136 137 even = !even; 138 if (bit < 4) 139 bit++; 140 else 141 { 142 geohash += Base32[ch]; 143 bit = 0; 144 ch = 0; 145 } 146 } 147 return geohash; 148 } 149 } 150 }
C#代码来自:https://github.com/sharonjl/geohash-net
geohash演示:http://openlocation.org/geohash/geohash-js/
各种版本下载:打包下载
引用阿里云以为技术专家的博客上的讨论:
这一点是有些用户对geohash的误解,虽然geo确实尽可能的将位置相近的点hash到了一起,可是这并不是严格意义上的(实际上也并不可能,因为毕竟多一维坐标),
例如在方格4的左下部分的点和大方格1的右下部分的点离的很近,可是它们的geohash值一定是相差的相当远,因为头一次的分块就相差太大了,很多时候我们对geohash的值进行简单的排序比较,结果貌似真的能够找出相近的点,并且似乎还是按照距离的远近排列的,可是实际上会有一些点被漏掉了。
上述这个问题,可以通过搜索一个格子,周围八个格子的数据,统一获取后再进行过滤。这样就在编码层次解决了这个问题。
2.既然不能做到将相近的点hash值也相近,那么geohash的意义何在呢?
我觉得geohash还是相当有用的一个算法,毕竟这个算法通过无穷的细分,能确保将每一个小块的geohash值确保在一定的范围之内,这样就为灵活的周边查找和范围查找提供了可能。
常见的一些应用场景
A、如果想查询附近的点?如何操作
查出改点的gehash值,然后到数据库里面进行前缀匹配就可以了。
B、如果想查询附近点,特定范围内,例如一个点周围500米的点,如何搞?
可以查询结果,在结果中进行赛选,将geohash进行解码为经纬度,然后进行比较
*在纬度相等的情况下:
*经度每隔0.00001度,距离相差约1米;
*每隔0.0001度,距离相差约10米;
*每隔0.001度,距离相差约100米;
*每隔0.01度,距离相差约1000米;
*每隔0.1度,距离相差约10000米。
*在经度相等的情况下:
*纬度每隔0.00001度,距离相差约1.1米;
*每隔0.0001度,距离相差约11米;
*每隔0.001度,距离相差约111米;
*每隔0.01度,距离相差约1113米;
*每隔0.1度,距离相差约11132米。
参考资料:
http://iamzhongyong.iteye.com/blog/1399333
相关推荐
Java实现GeoHash算法是一种在IT领域中用于地理位置数据存储和检索的技术。GeoHash将经纬度坐标转换为字符串,使得地理位置可以被高效地索引和查询。这种算法利用了空间分割和编码策略,使得相邻的位置在编码后具有...
通过分析这些代码,我们可以深入理解GeoHash算法的工作原理,并且学习如何在实际项目中应用它。 GeoHash算法的使用场景广泛,例如在地图应用中,可以用来快速找到附近的服务设施;在社交网络中,可以帮助用户发现...
相信找到这里的 ,geohash算法原理及实现方式应该也有所了解了。原代码了出自 邓星林博客 http://www.cnblogs.com/dengxinglin/archive/2012/12/14/2817761.html 自已找来,加上8个区的实现,加些注释和例子,测试...
在Java中实现找到周围8个区域的GeoHash编码涉及到地理空间索引和定位算法的应用。以下是对这个主题的详细解释: GeoHash的工作原理: GeoHash的基本思想是通过将地球表面划分为网格,并对每个网格分配一个唯一的...
首先,让我们了解一下Geohash的工作原理。Geohash将地球表面分成多个小格子,每个格子都有一个唯一的字符串表示。这个字符串由一系列的二进制位组成,这些二进制位分别代表了经度和纬度的递归分割。通过不断地将空间...
Geohash算法主要基于Base32编码,它将地球表面划分为一系列的网格,然后对每个网格进行编码。通过递归地将每个网格分成相等的四部分(北、南、东、西),直到得到的网格足够小,可以接受一个Base32字符来表示。这样...
Geohash GeoHash是目前比较主流的实现位置服务的技术,Geohash算法将通过纬度二维数据编码为一个字符串,本质上是一个降维的过程,一个栗子地点经纬度Geohash鸟巢116.402843,39.999375 wx4g8c9v水立方116.3967,39....
GeoHash 是一种将地理位置数据(经纬度)编码为字符串的技术,这种编码方法可以方便地进行空间索引和距离估算。...通过深入理解 GeoHash 的原理和实现,可以提升你在地理信息系统(GIS)领域的开发能力。
7. **集成第三方库**:虽然可以手动实现Geohash算法,但也有许多现成的第三方库可供使用,如开源的`GeohashKit`或`SDGeoHash`,它们已经封装了Geohash的处理逻辑,使开发者能更快地集成到项目中。 通过以上知识点,...
在本篇文章中,我们将深入探讨Geohash的原理,并通过分析提供的"geohash-源码.zip"文件来理解其实现细节。 1. Geohash原理: Geohash的基本思想是将地球视为一个三维空间,然后将这个空间不断分割成小块,每一块都...
通过查看和分析这些源代码,开发者可以了解Geohash算法的内部工作原理,也可以根据自身需求进行定制和优化。 总结起来,Geohash是一种有效的地理位置编码技术,可以简化地理数据的处理和存储。Java、Lua和PHP中的...
**Geohash 知识详解** Geohash是一种地理编码技术,它将地球表面上的经纬度坐标转换为一串可比较的字符...理解和掌握Geohash的原理和应用,有助于我们在开发涉及地理位置功能的软件和服务时,实现更优化的设计和性能。
Georaptor可能是对原始Geohash算法的一种扩展或增强,旨在减少存储需求,同时保持足够的定位精度。在处理大量地理位置信息时,这种压缩技术尤为重要,因为它可以降低数据库大小,提升查询速度,节省服务器资源。 ...
在PHP的进阶学习中,Geo地图定位算法是开发者必须掌握的重要技能之一。Geo定位算法主要涉及经纬度坐标系统和GeoHash技术,...学习和理解GeoHash的原理和实现方法,对于提升PHP开发者在地图应用开发中的能力至关重要。
【共享单车案例】是一个结合了大数据处理和地理位置服务的实战项目,主要涉及了需求分析、GeoHash算法的应用以及数据的获取和存储。以下是该案例中的关键知识点: 1. **需求分析与流程**: - 首先,我们需要理解...
本文将从源码角度对其算法原理进行解析,并推算查询时间复杂度。 操作命令 自Redis 3.2开始,Redis基于geohash和有序集合提供了地理位置相关功能。 Redis Geo模块包含了以下6个命令: GEOADD: 将给定的位置对象...
- **编码机制**:介绍Geohash算法的基本原理和应用场景。 - **索引结构**:使用R-tree等数据结构来加速位置信息的查询效率。 - **服务性能**:支持每秒超过3万次查询请求,平均响应时间控制在10毫秒以内。 ##### 五...
Redis的Geo功能包括GeoHash算法,它将地球编码为哈希值,并可以在Redis中使用。Scan命令允许遍历数据库中的键,有多种用法和原理。 Redis作为一个单线程的数据库,却可以处理高并发,其奥秘在于其使用了非阻塞IO,...