elasticsearch 深入 —— 地理位置

地理位置

我们拿着纸质地图漫步城市的日子一去不返了。得益于智能手机,我们现在总是可以知道 自己所处的准确位置,也预料到网站会使用这些信息。我想知道从当前位置步行 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_bounding_box

找出落在指定矩形框中的点。

geo_distance

找出与指定位置在给定距离内的点。

geo_distance_range

找出与指定点距离在给定最小距离和最大距离之间的点。

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查询选项

选项 描述

_name

用于标识过滤器的可选名称字段

validation_method

设置为IGNORE_MALFORMED接受具有无效纬度或经度的地理点,设置 COERCE为也尝试推断正确的纬度或经度。(默认是STRICT)。

type

设置为indexedmemory定义此过滤器是在内存中执行还是索引。有关详细信息,请参阅下面的类型memory

接受的格式

与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_rightbottom_left参数。更过名topLeftbottomRighttopRightbottomLeft 支持。可以使用简单名称top,而不是分别设置值leftbottomright分别设置值。

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的envelopeGeoJSON扩展来查找该点 :

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"
                }
            }
        }
    }
}

选项

以下是过滤器允许的选项:

distance

圆的半径以指定位置为中心。落入此圈的点被认为是匹配。在distance可以以各种单元指定。请参见距离单位

distance_type

如何计算距离。可以是arc(默认),或plane(更快,但在长距离和靠近极点时不准确)。

_name

用于标识查询的可选名称字段

validation_method

设置为IGNORE_MALFORMED接受具有无效纬度或经度的地理点,设置COERCE为另外尝试并推断正确的坐标(默认为STRICT)。

geo_point类型

该过滤器要求geo_point要在相关领域的集合类型。

每个文档的多位置

geo_distance过滤器可以用每份文件的多个位置/点工作。一旦单个位置/点与过滤器匹配,文档将包含在过滤器中。

忽略未映射的

设置true为该ignore_unmapped选项时,将忽略未映射的字段,并且不匹配此查询的任何文档。在查询可能具有不同映射的多个索引时,这非常有用。设置为 false(默认值)时,如果未映射字段,查询将引发异常。

 

posted on 2018-09-13 15:40  疯狂的小萝卜头  阅读(945)  评论(0编辑  收藏  举报