已经是最新一篇文章了!
已经是最后一篇文章了!
使用GeoHash查找附近的药店
如何查找附近的药店
查找附近的药店应该是一个最基本的基于地理位置的应用,但要怎么做呢?
1、通过计算药店与我之间的距离,来确定是否它是否在附近。
2、通过对比药店跟我两个位置的GeoHash值来确定我们是否处于同个区域。
本文说明的是第二种方式,因为它更靠谱一点。
GeoHash介绍
这里推一篇维基百科供参考GeoHash
还有知乎的一篇文章GeoHash
GeoHash应该是一种位置压缩算法,用来把二维的位置信息变成一维的信息。我们表达一个位置需要两个值——经度、维度。这两个值可以通过GeoHash算法压缩成一个字符串。大家可以在这个网站体验一下geohash.co
GeoHash的特点
两个GeoHash相同说明他们是同个区域。随着GeoHash变长,它所表示的区域也就越小。
网上可以找到GeoHash长度跟误差(区域大小)的对照表,比如5位的GeoHash大概表达的是5平方公里这样的面积。
geohash length | lat bits | lng bits | lat error | lng error | km error |
---|---|---|---|---|---|
1 | 2 | 3 | ±23 | ±23 | ±2500 |
2 | 5 | 5 | ±2.8 | ±5.6 | ±630 |
3 | 7 | 8 | ±0.70 | ±0.70 | ±78 |
4 | 10 | 10 | ±0.087 | ±0.18 | ±20 |
5 | 12 | 13 | ±0.022 | ±0.022 | ±2.4 |
6 | 15 | 15 | ±0.0027 | ±0.0055 | ±0.61 |
7 | 17 | 18 | ±0.00068 | ±0.00068 | ±0.076 |
8 | 20 | 20 | ±0.000085 | ±0.00017 | ±0.019 |
使用GeoHash实现查找附近的药店
计算出所有药店的GeoHash
给所有位置都计算出GeoHash,当然长度尽可能的长,这样后期使用的时候,可以灵活调整精度。示例:
latitude | longitude | geohash |
---|---|---|
30.789748 | 121.339645 | wtqr1jhg |
30.257383 | 120.177994 | wtmknsny |
30.236452 | 120.044845 | wtmk58mu |
30.236452 | 120.044845 | wtmk58mu |
34.71191 | 113.65735 | ww0v6r4g |
Geohash的代码实现可以google到,也可以参考我的另外一篇文章使用kettle生成GeoHash。
计算当前位置的以及周边的8个GeoHash
因为GeoHash表达的是一个正方形的区域,你当前位置不一定就是该区域的中心,所以四周的8个区域也是你搜索的目标。
这里推荐一个php的实现https://github.com/chency147/geohash
NorthWest | North | NorthEast |
West | Me | East |
SouthWest | South | SouthEast |
ws4uztw22x | ws4uztw22z | ws4uztw23p |
ws4uztw22w | ws4uztw22y | ws4uztw23n |
ws4uztw22t | ws4uztw22v | ws4uztw23j |
与数据库中的GeoHash对比
以下代码示例使用thinkphp5.1 mysql写的。通过查找前N位一样的值来实现查找附近的药店。
function filterGeohash($geohash, $geohashNeighbors, $topN)
{
$list = DrugStoreModel::where(
'LEFT(geohash, :topN) = LEFT(:geohash, :topN) or
LEFT(geohash, :topN) = LEFT(:North, :topN) or
LEFT(geohash, :topN) = LEFT(:NE, :topN) or
LEFT(geohash, :topN) = LEFT(:East, :topN) or
LEFT(geohash, :topN) = LEFT(:SE, :topN) or
LEFT(geohash, :topN) = LEFT(:South, :topN) or
LEFT(geohash, :topN) = LEFT(:SW, :topN) or
LEFT(geohash, :topN) = LEFT(:West, :topN) or
LEFT(geohash, :topN) = LEFT(:NW, :topN)',
[
'topN' => $topN,
'geohash' => $geohash,
'North' => $geohashNeighbors['North'],
'NE' => $geohashNeighbors['NorthEast'],
'East' => $geohashNeighbors['East'],
'SE' => $geohashNeighbors['SouthEast'],
'South' => $geohashNeighbors['South'],
'SW' => $geohashNeighbors['SouthWest'],
'West' => $geohashNeighbors['West'],
'NW' => $geohashNeighbors['NorthWest']
])
->hidden(['id', 'search_content'])->select();
return $list;
}