Geohashes
Geohashes 是一种将经纬度坐标( lat/lon
)编码成字符串的方式。这么做的初衷只是为了让地理位置在 url 上呈现的形式更加友好,但现在 geohashes 已经变成一种在数据库中有效索引地理坐标点和地理形状的方式。
Geohashes 把整个世界分为 32 个单元的格子 —— 4 行 8 列 —— 每一个格子都用一个字母或者数字标识。比如 g
这个单元覆盖了半个格林兰,冰岛的全部和大不列颠的大部分。每一个单元还可以进一步被分解成新的 32 个单元,这些单元又可以继续被分解成 32 个更小的单元,不断重复下去。 gc
这个单元覆盖了爱尔兰和英格兰, gcp
覆盖了伦敦的大部分和部分南英格兰, gcpuuz94k
是白金汉宫的入口,精确到约 5 米。
换句话说, geohash 的长度越长,它的精度就越高。如果两个 geohashes 有一个共同的前缀— gcpuuz
—就表示他们挨得很近。共同的前缀越长,距离就越近。
这也意味着,两个刚好相邻的位置,可能会有完全不同的 geohash 。比如,伦敦 Millenium Dome 的 geohash 是 u10hbp
,因为它落在了 u
这个单元里,而紧挨着它东边的最大的单元是 g
。
地理坐标点可以自动索引相关的 geohashes ,更重要的是,他们也可以索引所有的 geohashes 前缀 。如索引白金汉宫入口位置——纬度 51.501568
,经度 -0.141257
—将会索引下面表格中列出的所有 geohashes ,表格中也给出了各个 geohash 单元的近似尺寸:
Geohash |
Level |
Dimensions |
g
|
1
|
~ 5,004km x 5,004km |
gc
|
2
|
~ 1,251km x 625km |
gcp
|
3
|
~ 156km x 156km |
gcpu
|
4
|
~ 39km x 19.5km |
gcpuu
|
5
|
~ 4.9km x 4.9km |
gcpuuz
|
6
|
~ 1.2km x 0.61km |
gcpuuz9
|
7
|
~ 152.8m x 152.8m |
gcpuuz94
|
8
|
~ 38.2m x 19.1m |
gcpuuz94k
|
9
|
~ 4.78m x 4.78m |
gcpuuz94kk
|
10
|
~ 1.19m x 0.60m |
gcpuuz94kkp
|
11
|
~ 14.9cm x 14.9cm |
gcpuuz94kkp5
|
12
|
~ 3.7cm x 1.8cm |
Geohashes 映射
首先,你需要决定使用什么样的精度。虽然你也可以使用 12 级的精度来索引所有的地理坐标点,但是你真的需要精确到数厘米吗?如果你把精度控制在一个实际一些的值,比如 1km
,那么你可以节省大量的索引空间:
PUT /attractions
{
"mappings": {
"restaurant": {
"properties": {
"name": {
"type": "string"
},
"location": {
"type": "geo_point",
"geohash_prefix": true, (1)
"geohash_precision": "1km" (2)
}
}
}
}
}
-
将 geohash_prefix
设为 true
来告诉 Elasticsearch 使用指定精度来索引 geohash 的前缀。
-
精度可以是一个具体的数字,代表的 geohash 的长度,也可以是一个距离。 1km
的精度对应的 geohash 的长度是 7
。
通过如上设置, geohash 前缀中 1 到 7 的部分将被索引,所能提供的精度大约在 150 米。
Geohash 单元查询
geohash_cell
查询做的事情非常简单: 把经纬度坐标位置根据指定精度转换成一个 geohash ,然后查找所有包含这个 geohash 的位置——这是非常高效的查询。
GET /attractions/restaurant/_search
{
"query": {
"constant_score": {
"filter": {
"geohash_cell": {
"location": {
"lat": 40.718,
"lon": -73.983
},
"precision": "2km" (1)
}
}
}
}
}
-
precision
字段设置的精度不能高于映射时 geohash_precision
字段指定的值。
此查询将 lat/lon
坐标点转换成对应长度的 geohash —— 本例中为 dr5rsk
—然后查找所有包含这个短语的位置。
然而,如上例中的写法可能不会返回 2km 内所有的餐馆。要知道 geohash 实际上仅是个矩形,而指定的点可能位于这个矩形中的任何位置。有可能这个点刚好落在了 geohash 单元的边缘附近,但过滤器会排除那些落在相邻单元的餐馆。
为了修复这个问题,我们可以通过设置 neighbors
参数为 true
,让查询把周围的单元也包含进来:
GET /attractions/restaurant/_search
{
"query": {
"constant_score": {
"filter": {
"geohash_cell": {
"location": {
"lat": 40.718,
"lon": -73.983
},
"neighbors": true, (1)
"precision": "2km"
}
}
}
}
}
-
此查询将会寻找对应的 geohash 和包围它的 geohashes 。
明显的, 2km
精度的 geohash 加上周围的单元,最终导致一个范围极大的搜索区域。此查询不是为精度而生,但是它非常有效率,而且可以作为更高精度的地理位置过滤器的前置过滤器。
Tip
|
将 precision 参数设置为一个距离可能会有误导性。 2km 的 precision 会被转换成长度为 6 的 geohash 。实际上它的尺寸是约 1.2km x 0.6km。你可能会发现明确的设置长度为 5 或 6 会更容易理解。 |
此查询的另一个优点是,相比 geo_bounding_box
查询,它支持一个字段中有多个坐标位置的情况。 我们在 优化盒模型 中讨论过,设置 lat_lon
选项也是一个很有效的方式,但是它只在每个字段只有单个坐标点的情况下有效。