地理位置
我们拿着纸质地图漫步城市的日子一去不返了。得益于智能手机,我们现在总是可以知道 自己所处的准确位置,也预料到网站会使用这些信息。我想知道从当前位置步行 5 分钟内可到的那些餐馆,对伦敦更大范围内的其他餐馆并不感兴趣。
但地理位置功能仅仅是 Elasticsearch 的冰山一角,Elasticsearch 的妙处在于,它让你可以把地理位置、全文搜索、结构化搜索和分析结合到一起。
例如:告诉我提到 vitello tonnato 这种食物、步行 5 分钟内可到、且晚上 11 点还营业的餐厅,然后结合用户评价、距离、价格排序。另一个例子:给我展示一幅整个城市8月份可用假期出租物业的地图,并计算出每个区域的平均价格。
Elasticsearch 提供了 两种表示地理位置的方式:用纬度-经度表示的坐标点使用 geo_point
字段类型,以 GeoJSON 格式定义的复杂地理形状,使用 geo_shape
字段类型。
Geo-points 允许你找到距离另一个坐标点一定范围内的坐标点、计算出两点之间的距离来排序或进行相关性打分、或者聚合到显示在地图上的一个网格。另一方面,Geo-shapes 纯粹是用来过滤的。它们可以用来判断两个地理形状是否有重合或者某个地理形状是否完全包含了其他地理形状。
地理坐标点
地理坐标点 是指地球表面可以用经纬度描述的一个点。 地理坐标点可以用来计算两个坐标间的距离,还可以判断一个坐标是否在一个区域中,或在聚合中。
地理坐标点不能被动态映射 (dynamic mapping)自动检测,而是需要显式声明对应字段类型为 geo-point
:
PUT /attractions
{
"mappings": {
"restaurant": {
"properties": {
"name": {
"type": "text"
},
"location": {
"type": "geo_point"
}
}
}
}
}
经纬度坐标格式
如上例,location
字段被声明为 geo_point
后,我们就可以索引包含了经纬度信息的文档了。 经纬度信息的形式可以是字符串、数组或者对象:
PUT /attractions/restaurant/1
{
"name": "Chipotle Mexican Grill",
"location": "40.715, -74.011"
}PUT /attractions/restaurant/2
{
"name": "Pala Pizza",
"location": {
"lat": 40.722,
"lon": -73.989
}
}PUT /attractions/restaurant/3
{
"name": "Mini Munchies Pizza",
"location": [ -73.983, 40.719 ]
}
字符串形式以半角逗号分割,如 "lat,lon" 。
对象形式显式命名为 lat 和 lon 。
数组形式表示为 [lon,lat] 。
可能所有人都至少一次踩过这个坑:地理坐标点用字符串形式表示时是纬度在前,经度在后(
"latitude,longitude"
),而数组形式表示时是经度在前,纬度在后([longitude,latitude]
)—顺序刚好相反。其实,在 Elasticesearch 内部,不管字符串形式还是数组形式,都是经度在前,纬度在后。不过早期为了适配 GeoJSON 的格式规范,调整了数组形式的表示方式。
因此,在使用地理位置的路上就出现了这么一个“捕熊器”,专坑那些不了解这个陷阱的使用者。
通过地理坐标点过滤
有四种地理坐标点相关的过滤器 可以用来选中或者排除文档:
找出落在指定矩形框中的点。
找出与指定位置在给定距离内的点。
找出与指定点距离在给定最小距离和最大距离之间的点。
geo_polygon
找出落在多边形中的点。 这个过滤器使用代价很大 。当你觉得自己需要使用它,最好先看看 geo-shapes。
这些过滤器判断点是否落在指定区域时的计算方法稍有不同,但过程类似。指定的区域被转换成一系列以quad/geohash为前缀的tokens,并被用来在倒排索引中搜索拥有相同tokens的文档。
地理坐标过滤器使用代价昂贵 — 所以最好在文档集合尽可能少的场景下使用。你可以先使用那些简单快捷的过滤器,比如
term
或range
,来过滤掉尽可能多的文档,最后才交给地理坐标过滤器处理。布尔型过滤器
bool
filter 会自动帮你做这件事。 它会优先让那些基于“bitset”的简单过滤器(见 关于缓存 )来过滤掉尽可能多的文档,然后依次才是更昂贵的地理坐标过滤器或者脚本类的过滤器。
Geo Bounding Box 查询
这是目前为止最有效的地理坐标过滤器了,因为它计算起来非常简单。 你指定一个矩形的 顶部
, 底部
, 左边界
,和 右边界
,然后过滤器只需判断坐标的经度是否在左右边界之间,纬度是否在上下边界之间:
PUT /my_locations
{
"mappings": {
"_doc": {
"properties": {
"pin": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
}
}
}
PUT /my_locations/_doc/1
{
"pin" : {
"location" : {
"lat" : 40.12,
"lon" : -71.34
}
}
}
然后可以使用geo_bounding_box
过滤器执行以下简单查询 :
GET /_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_bounding_box" : {
"pin.location" : {
"top_left" : {
"lat" : 40.73,
"lon" : -74.1
},
"bottom_right" : {
"lat" : 40.01,
"lon" : -71.12
}
}
}
}
}
}
}
pin.location这些坐标也可以用 bottom_left 和 top_right 来表示
geo_bounding_box查询选项
选项 | 描述 |
---|---|
|
用于标识过滤器的可选名称字段 |
|
设置为 |
|
设置为 |
接受的格式
与geo_point类型可以接受地理坐标点的不同格式的方式大致相同,过滤器也可以接受它:
Lat Lon As Properties
GET /_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_bounding_box" : {
"pin.location" : {
"top_left" : {
"lat" : 40.73,
"lon" : -74.1
},
"bottom_right" : {
"lat" : 40.01,
"lon" : -71.12
}
}
}
}
}
}
}
Lat Lon As Array
在[lon, lat]
这里格式化,注意,lon / lat的顺序,以符合GeoJSON。
GET /_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_bounding_box" : {
"pin.location" : {
"top_left" : "40.73, -74.1",
"bottom_right" : "40.01, -71.12"
}
}
}
}
}
}
Lat Lon As String
格式化lat,lon作为字符串
GET /_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_bounding_box" : {
"pin.location" : {
"top_left" : "40.73, -74.1",
"bottom_right" : "40.01, -71.12"
}
}
}
}
}
}
边界框为 Well-Known 文本(WKT)
GET /_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_bounding_box" : {
"pin.location" : {
"wkt" : "BBOX (-74.1, -71.12, 40.73, 40.01)"
}
}
}
}
}
}
Geohash
GET /_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_bounding_box" : {
"pin.location" : {
"top_left" : "dr5r9ydj2y73",
"bottom_right" : "drj7teegpus6"
}
}
}
}
}
}
顶点
边界框的顶点可以通过设定top_left
和 bottom_right
或通过top_right
和bottom_left
参数。更过名topLeft
,bottomRight
,topRight
和bottomLeft
支持。可以使用简单名称top
,而不是分别设置值left
,bottom
并right
分别设置值。
GET /_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_bounding_box" : {
"pin.location" : {
"top" : 40.73,
"left" : -74.1,
"bottom" : 40.01,
"right" : -71.12
}
}
}
}
}
}
geo_point类型
该过滤器要求的geo_point
要在相关领域的集合类型。
每个文档的多位置
过滤器可以处理每个文档的多个位置/点。一旦单个位置/点与过滤器匹配,文档将包含在过滤器中
输入
默认情况下,边界框执行的类型设置为memory
,这意味着在内存中检查doc是否在边界框范围内。在某些情况下,indexed
选项执行速度会更快(但请注意,geo_point
在这种情况下,类型必须具有lat和lon索引)。请注意,使用索引选项时,不支持每个文档字段的多个位置。这是一个例子:
GET /_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_bounding_box" : {
"pin.location" : {
"top_left" : {
"lat" : 40.73,
"lon" : -74.1
},
"bottom_right" : {
"lat" : 40.10,
"lon" : -71.12
}
},
"type" : "indexed"
}
}
}
}
}
忽略未映射
设置true
为该ignore_unmapped
选项时,将忽略未映射的字段,并且不匹配此查询的任何文档。在查询可能具有不同映射的多个索引时,这非常有用。设置为 false
(默认值)时,如果未映射字段,查询将引发异常。
精度注意事项
Geopoints的精度有限,并且在索引时间内总是向下舍入。在查询时间期间,边界框的上边界向下舍入,而下边界向上舍入。因此,由于舍入误差,沿着下边界(边界框的左边缘和左边缘)的点可能不会进入边界框。同时,查询可以选择上边界(顶边和右边)旁边的点,即使它们位于边缘稍外。圆周误差应小于纬度上的4.20e-8度,经度上小于8.39e-8度,即使在赤道上也会误差小于1cm。
GeoShape查询
过滤使用geo_shape
类型索引的文档。
需要geo_shape
映射。
该geo_shape
查询使用相同的网格方形表示作为 geo_shape
映射,以查找具有与查询形状相交的形状的文档。它还将使用与字段映射定义的相同的PrefixTree配置。
该查询支持两种定义查询形状的方法,方法是提供整个形状定义,或者引用另一个索引中预先索引的形状的名称。以下通过示例定义两种格式。
内联形状定义
与geo_shape
类型类似,geo_shape
查询使用 GeoJSON来表示形状。
鉴于以下指数:
PUT /example
{
"mappings": {
"_doc": {
"properties": {
"location": {
"type": "geo_shape"
}
}
}
}
}
POST /example/_doc?refresh
{
"name": "Wind & Wetter, Berlin, Germany",
"location": {
"type": "point",
"coordinates": [13.400544, 52.530286]
}
}
以下查询将使用Elasticsearch的envelope
GeoJSON扩展来查找该点 :
GET /example/_search
{
"query":{
"bool": {
"must": {
"match_all": {}
},
"filter": {
"geo_shape": {
"location": {
"shape": {
"type": "envelope",
"coordinates" : [[13.0, 53.0], [14.0, 52.0]]
},
"relation": "within"
}
}
}
}
}
}
预索引形状
Query还支持使用已在另一个索引和/或索引类型中编入索引的形状。当您具有对应用程序有用的预定义形状列表并且您希望使用逻辑名称(例如New Zealand)引用它而不是每次都必须提供它们的坐标时,这尤其有用。在这种情况下,只需要提供:
id
- 包含预索引形状的文档的ID。index
- 预索引形状所在的索引的名称。默认为形状。type
- 预索引形状所在的索引类型。path
- 指定为包含预索引形状的路径的字段。默认为形状。routing
- 如果需要,形状文档的路由。
以下是使用具有预索引形状的过滤器的示例:
PUT /shapes
{
"mappings": {
"_doc": {
"properties": {
"location": {
"type": "geo_shape"
}
}
}
}
}
PUT /shapes/_doc/deu
{
"location": {
"type": "envelope",
"coordinates" : [[13.0, 53.0], [14.0, 52.0]]
}
}
GET /example/_search
{
"query": {
"bool": {
"filter": {
"geo_shape": {
"location": {
"indexed_shape": {
"index": "shapes",
"type": "_doc",
"id": "deu",
"path": "location"
}
}
}
}
}
}
}
空间关系
所述geo_shape策略映射参数确定其可在搜索时间被使用的空间关系的运营商。
以下是可用的空间关系运算符的完整列表:
"relation": "within"
INTERSECTS
- (默认)返回其geo_shape
字段与查询几何体相交的所有文档。DISJOINT
- 返回其geo_shape
字段与查询几何图形无任何共同点的所有文档。WITHIN
- 返回其geo_shape
字段在查询几何中的所有文档。CONTAINS
- 返回其geo_shape
字段包含查询几何的所有文档。
忽略未映射的
设置true
为该ignore_unmapped
选项时,将忽略未映射的字段,并且不匹配此查询的任何文档。在查询可能具有不同映射的多个索引时,这非常有用。设置为 false
(默认值)时,如果未映射字段,查询将引发异常。
Geo Distance 查询
过滤仅包含与地理位置相距特定距离内的匹配的文档。假设以下映射和索引文档:
PUT /my_locations
{
"mappings": {
"_doc": {
"properties": {
"pin": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
}
}
}
PUT /my_locations/_doc/1
{
"pin" : {
"location" : {
"lat" : 40.12,
"lon" : -71.34
}
}
}
然后可以使用geo_distance
过滤器执行以下简单查询:
GET /my_locations/_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_distance" : {
"distance" : "200km",
"pin.location" : {
"lat" : 40,
"lon" : -70
}
}
}
}
}
}
接受的格式
以geo_point
类似的方式,类型可以接受地理点的不同表示,过滤器也可以接受它:
Lat Lon As Properties
GET /my_locations/_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_distance" : {
"distance" : "12km",
"pin.location" : {
"lat" : 40,
"lon" : -70
}
}
}
}
}
}
Lat Lon As Array
在[lon, lat]
这里格式化,注意,lon / lat的顺序,以符合GeoJSON。
GET /my_locations/_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_distance" : {
"distance" : "12km",
"pin.location" : [-70, 40]
}
}
}
}
}
Lat Lon As String
格式化lat,lon
。
GET /my_locations/_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_distance" : {
"distance" : "12km",
"pin.location" : "40,-70"
}
}
}
}
}
Geohash
GET /my_locations/_search
{
"query": {
"bool" : {
"must" : {
"match_all" : {}
},
"filter" : {
"geo_distance" : {
"distance" : "12km",
"pin.location" : "drm3btev3e86"
}
}
}
}
}
选项
以下是过滤器允许的选项:
|
圆的半径以指定位置为中心。落入此圈的点被认为是匹配。在 |
|
如何计算距离。可以是 |
|
用于标识查询的可选名称字段 |
|
设置为 |
geo_point类型
该过滤器要求的geo_point
要在相关领域的集合类型。
每个文档的多位置
该geo_distance
过滤器可以用每份文件的多个位置/点工作。一旦单个位置/点与过滤器匹配,文档将包含在过滤器中。
忽略未映射的
设置true
为该ignore_unmapped
选项时,将忽略未映射的字段,并且不匹配此查询的任何文档。在查询可能具有不同映射的多个索引时,这非常有用。设置为 false
(默认值)时,如果未映射字段,查询将引发异常。