我们有时候,希望可以根据当前所在的位置,找到自己身边的符合条件的一些商店,酒店之类的。它主要支持两种类型的地理查询:一种是地理点(geo_point),即经纬度查询,另一种是地理形状查询(geo_shape),即支持点、线、圈、多边形查询等。
我们在计算机屏幕上看到的圆形并不是由完美的连续的线组成的。而是用一个个连续的着色像素点画出的一个近似圆。地理形状的工作方式就与此相似。
tree: geohash 或者 quadtree
precision: 用来控制生成的 geohash 的最大长度
tree_levels:前缀树的最大层数
strategy:该策略参数定义了如何在索引和搜索时间表示形状的方法,不建议认为修改,使用默认的就好
为了在索引有效的表示形状,形状被转换城一系列的hash值代表的网格块。树的概念主要来自于前缀树使用多个网格层,每个网格层的精度越来越高,可以代表地球。ES提供了多种前缀树以供选择:
# GeohashPrefixTree: geo 哈希前缀树
使用geohash代表网格,Geohashes是基于纬度和经度交叉的base32编码字符串,在geohash中添加的每个字符表示另一个树级别,并将5位精度添加到geohash中。geohash代表一个矩形区域,有32个子矩形。ES中最高水平是24。
# QuadPrefixTree: 四叉树前缀树
使用quadtree代表网格,类似于geohash,四叉树交叉在经纬度上,因此产生的哈希是位集,四叉树的树层代表了这个位集的2位,每个位对应一个坐标。
geo_shape不能提供100%的精度,并且取决于怎样去配置,例如,一个点可能位于特定网格单元的边界附近,因此可能不匹配只匹配旁边的单元格的查询——尽管这个形状非常接近这个点。
{
"properties": {
"location": {
"type": "geo_shape",
"tree": "quadtree",
"precision": "1m"
}
}
}
ES使用前缀树中的路径作为索引和查询中的术语。级别越高(因此精度越高),生成的词汇就越多。当然,计算这些term,将它们保存在内存中,并将它们存储在磁盘上都有代价。特别是在较高的树层次上,即使有少量的数据,索引也会变得非常大。此外,功能的大小也很重要。大而复杂的多边形可以在更高的层次上占据很大的空间。哪个设置是正确的取决于用例。一般情况下,一个对索引大小和查询性能的准确性。
这两种实现的弹性搜索的默认值是在赤道上的50米精度和指数大小之间的折衷。这允许索引数以百万计的形状,而不会过多地将结果索引与输入大小联系在一起。
GeoJSON格式代表形状:
Point:
{
"location" : {
"type" : "point",
"coordinates" : [-77.03653, 38.897676]
}
}
LineString:
{
"location" : {
"type" : "linestring",
"coordinates" : [[-77.03653, 38.897676], [-77.009051,38.889939]]
}
}
Polygon:
{
"location" : {
"type" : "polygon",
"coordinates" : [
[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
]
}
}
MultiPoint:
{
"location" : {
"type" : "polygon",
"orientation" : "clockwise",
"coordinates" : [
[[-177.0, 10.0], [176.0, 15.0], [172.0, 0.0], [176.0, -15.0], [-177.0, -10.0],[-177.0, 10.0] ],
[[178.2, 8.2], [-178.8, 8.2], [-180.8, -8.8], [178.2, 8.8] ]
]
}
}
Envelop:
{
"location" : {
"type" : "envelope",
"coordinates" : [ [-45.0, 45.0], [45.0, -45.0] ]
}
}
Circle:
{
"location" : {
"type" : "circle",
"coordinates" : [-45.0, 45.0],
"radius" : "100m"
}
}
地理形状支持2种方式查询:一种是通过整个形状的定义,一种是通过预索引的方式。
与地理形状相同,地理形状过滤使用GeoJSON来表示形状。
POST /attractions/landmark/_search
{
"query":{
"geo_shape": {
"location": {
"shape":{
"type":"circle",
"radius":"690m",
"coordinates":[ 4.89994,52.37815]
}
}
}
}
}
POST /attractions/landmark/_search
{
"query":{
"geo_shape": {
"location": {
"shape":{
"type":"envelope",
"coordinates":[[ 116.39794,39.9001], [ 116.40887, 39.89609 ]]
}
}
}
}
}
查询也支持使用被索引在其他索引或者索引类型中的形状,当存在一个预定义的形状列表时,可以使用逻辑名来引用而不用每一次都提供具体的坐标值。在这种情况下,只需要提供参数见如下所示:
id: 包含预索引形状的文档id
index: 预索引形状的索引名,默认为shapes
type: 预索引形状的索引类型
path:指定的字段作为包含预索引形状的路径,默认为shape
ET /_search
{
"query": {
"bool": {
"must": {
"match_all": {}
},
"filter": {
"geo_shape": {
"location": {
"indexed_shape": {
"id":"DEU",
"type": "countries",
"index": "shapes",
"path": "location"
}
}
}
}
}
}
}
地理形状映射参数决定使用的空间关系操作符,所有可以使用的空间关系操作符列表如下:
INTERSECTS: 返回地理形状字段与查询集合相交的所有文档。默认选项。
DISJOINT: 返回地理形状字段与查询集合没有关联的所有文档
WITHIN: 返回地理形状字段在查询集合内的所有文档
CONTAINS: 返回地理形状字段包含查询集合的所有文档
可以基于一个位置点的范围来过滤查询文档。
PUT /attractions/restaurant/1
{
"name": "Chipotle Mexican Grill",
"location": "40.715, -74.011"
}
PUT /attractions/restaurant/2
{
"name": "PalaPizza",
"location": {
"lat": 40.722,
"lon": -73.989
}
}
PUT /attractions/restaurant/3
{
"name": "MiniMunchies Pizza",
"location": [ -73.983, 40.719 ]
}
有四种地理坐标点相关的过滤器可以用来选中或者排除文档:
geo_bounding_box:找出落在指定矩形框中的点。
geo_distance:找出与指定位置在给定距离内的点。
geo_distance_range:找出与指定点距离在给定最小距离和最大距离之间的点。
geo_polygon:找出落在多边形中的点。 这个过滤器使用代价很大
找出落在指定矩形框中的点。其实也可以相当于一个范围查询。
这是目前为止最有效的地理坐标过滤器了,因为它计算起来非常简单。 你指定一个矩形的 顶部 , 底部 , 左边界 ,和 右边界 ,然后过滤器只需判断坐标的经度是否在左右边界之间,纬度是否在上下边界之间:
他可以指定一下几个属性:
top_left: 指定最左边的经度和最上边的纬度
bottom_right: 指定右边的经度和最下边的纬度
或者
top_right: 指定最上边的纬度和最右边的经度
bottom_left:指定最下边的纬度和最左边的经度
POST /map/hotel/_search
{
"query":{
"constant_score": {
"filter": {
"geo_bounding_box": {
"location":{
"top_left":{
"lat":40.73,
"lon":114.2
},
"bottom_right": {
"lat":30.5,
"lon":120
}
}
}
}
}
}
}
优化盒模型:
地理坐标盒模型过滤器 不需要把所有坐标点都加载到内存里。 因为它要做的 只是简单判断 lat 和 lon 坐标数值是否在给定的范围内可以用倒排索引做一个 range 过滤来实现目标。
要使用这种优化方式,需要把 geo_point 字段 用 lat 和 lon 的方式分别映射到索引中:
PUT /attractions
{
"mappings": {
"restaurant": {
"properties": {
"name": {
"type": "string"
},
"location": {
"type": "geo_point",
"lat_lon": true
}
}
}
}
}
location.lat 和 location.lon 字段将被分别索引。它们可以被用于检索,但是不会在检索结果中返回。
然后,查询时你需要告诉 Elasticesearch使用已索引的 lat 和 lon :
GET /attractions/restaurant/_search
{
"query": {
"filtered": {
"filter": {
"geo_bounding_box": {
"type": "indexed",
"location": {
"top_left": {
"lat": 40.8,
"lon": -74.0
},
"bottom_right": {
"lat": 40.7,
"lon": -73.0
}
}
}
}
}
}
}
设置 type 参数为 indexed (替代默认值 memory )来明确告诉 Elasticsearch 对这个过滤器使用倒排索引
给定一个位置为圆心,然后画一个圆,找出那些地理坐标烙在其中的文档。
比如我要在成都市世纪城附近找最近2km的酒店
distance指定了以世纪城这个坐标为中心,然后以distance为半径的圆内查找
POST /map/hotel/_search
{
"query":{
"constant_score": {
"filter": {
"geo_distance": {
"distance": "2km",
"location": {
"lat": 30.556485,
"lon": 104.069315
}
}
}
}
}
}
距离计算类型
两点间的距离计算,有多种牺牲性能换取精度的算法:
arc
最慢但最精确的是 arc 计算方式,这种方式把世界当作球体来处理。不过这种方式的精度有限,因为这个世界并不是完全的球体。
plane
plane 计算方式把地球当成是平坦的,这种方式快一些但是精度略逊。在赤道附近的位置精度最好,而靠近两极则变差。
sloppy_arc
如此命名,是因为它使用了 Lucene 的 SloppyMath 类。这是一种用精度换取速度的计算方式, 它使用 Haversine formula来计算距离。它比 arc 计算方式快 4 到 5 倍,并且距离精度达 99.9%。这也是默认的计算方式。
geo_distance 和 geo_distance_range 过滤器 的唯一差别在于后者是一个环状的,它会排除掉落在内圈中的那部分文档。
就相当于会把白色部分里面的文档排除掉,而只是匹配蓝色部分的文档
POST /map/hotel/_search
{
"query":{
"constant_score": {
"filter": {
"geo_distance_range": {
"gte":"0.5km",
"lte":"10km",
"location":[104.069315,30.556485]
}
}
}
}
}
但是5系列好像不支持这个功能了,提示:
[geo_distance_range] queries are no longersupported for geo_point field types. Use geo_distance sort or aggregations
POST /map/hotel/_search
{
"query": {
"bool": {
"must": [{"match_all": {}}],
"filter": {
"geo_polygon": {
"location": {
"points": [
{"lat" : 40.73, "lon" : -74.1},
{"lat" : 40.01, "lon" : -71.12},
{"lat" : 50.56, "lon" : -90.58}
]
}
}
}
}
}
}
我们现在要指定成都市宽窄巷子 跳伞塔 春熙路,这三个地区组成的多边形的范围内,我要搜索这里面的酒店,就可以用得上这个查询
检索结果可以按与指定点的距离排序。当你 可以 按距离排序时, 按距离打分 通常是一个更好的解决方案。
POST /map/hotel/_search
{
"query":{
"constant_score": {
"filter": {
"geo_bounding_box": {
"location":{
"top_left":{
"lat":40.73,
"lon":114.2
},
"bottom_right": {
"lat":30.5,
"lon":122
}
}
}
}
}
},
"sort": [
{
"_geo_distance": {
"location":[116.6382,36.77],
"order": "asc",
"unit":"km",
"distance_type":"plane"
}
}
]
}
排序中的location指的是,文档中各个loction与该location的距离。
地理距离排序可以对多个坐标点来使用,不管(这些坐标点)是在文档中还是排序参数中。使用 sort_mode 来指定是否需要使用位置集合的 最小 ( min ) 最大 ( max )或者 平均 ( avg )距离。 如此就可以返回 “离我的工作地和家最近的朋友” 这样的结果了。
按距离打分:
有可能距离是决定返回结果排序的唯一重要因素,不过更常见的情况是距离会和其它因素,比如全文检索匹配度、流行程度或者价格一起决定排序结果。
遇到这种场景你需要在 功能评分查询 中指定方式让我们把这些因子处理后得到一个综合分。 越近越好 中有个一个例子就是介绍地理距离影响排序得分的。
另外按距离排序还有个缺点就是性能:需要对每一个匹配到的文档都进行距离计算。而 function_score 查询,在 rescore 语句 中可以限制只对前 n 个结果进行计算。