万字长文:一文彻底搞懂Elasticsearch中Geo数据类型查询、聚合、排序

在我们使用elasticsearch创建索引时,经常会遇到一种字段类型为geo_point的数据类型,该类型的字段接收经纬度的值,那么geo_point类型的字段可以用来做什么?

  • 基于Geo的地理位置范围查询
  • 基于Geo范围内到中心点距离的聚合统计
  • 加入到相关性得分计算中
  • 基于Geo地理位置信息到中心点距离的排序

通过阅读本文,可以学到以上知识点,学到就是赚到,还不快快开始阅读吧

环境

  • MacOS 10.14.6
  • Elasticsearch 8.1
  • Kibana 8.1

帮助信息

Geopoint(点)

如何指定一个地理位置信息

Elasticsearch中geo_point类型的数据字段支持以下5种方式录入地理位置数据,分别如下:

首先我们还是先定义一个索引,创建一个数据类型为geo_point的字段

  • 创建索引

    PUT my-index-000001
    {
      "mappings": {
        "properties": {
          "text":{
            "type": "text"
          },
          "location":{
            "type": "geo_point"
          }
        }
      }
    }
    
  • 作为一个对象传入位置信息

    PUT my-index-000001/_doc/1
    {
      "text": "Geopoint as an object",
      "location": { 
        "lat": 41.12,
        "lon": -71.34
      }
    }
    
  • 字符串形式写入"lat,lon"

    PUT my-index-000001/_doc/2
    {
      "text": "Geopoint as a string",
      "location": "41.12,-71.34" 
    }
    
  • geohash形式写入

    PUT my-index-000001/_doc/3
    {
      "text": "Geopoint as a geohash",
      "location": "drm3btev3e86" 
    }
    
  • 数组[lon,lat]

    PUT my-index-000001/_doc/4
    {
      "text": "Geopoint as an array",
      "location": [ -71.34, 41.12 ] 
    }
    
  • POINT 文本"POINT(lon lat)"

    PUT my-index-000001/_doc/5
    {
      "text": "Geopoint as a WKT POINT primitive",
      "location" : "POINT (-71.34 41.12)" 
    }
    
  • 基于一定地理位置边界的查询

    GET my-index-000001/_search
    {
      "query": {
        "geo_bounding_box": { 
          "location": {
            "top_left": {
              "lat": 43,
              "lon": -72
            },
            "bottom_right": {
              "lat": 40,
              "lon": -74
            }
          }
        }
      }
    }
    
    

    通过上面的例子可以看出geo_point类型的字段可以同时接收五种参数形式的数据写入,并且丝毫不影响查询,就是这么强大,还不快用起来,温馨提醒,千万不要为了用而用哦

geo_point 类型字段支持的参数

  • ignore_malformed

    默认false,写入文档时遇到不符合规范的地理位置信息字段值会抛出异常,如果为true,则会忽略。如果设置了script字段,该字段将不能使用

  • ignore_z_value

    默认true,三维空间信息将被记录,但是只有经纬度的值能被索引,如果为false,写入文档时如果有超过二维的信息,抛出异常拒绝文档写入

  • index

    是否可以被索引,默认true,如果为false,未建立索引的字段将不能被检索

  • null_value

    默认值为null,如果设置为null,则意味着该字段是不存在的,如果使用了script,则该字段不可被设置

  • on_script_error

    只有设置了script字段的时候才可以设置该字段,定义使用脚步发生错误时如何处理,默认fail,拒绝整个文档,但是会继续,在元数据字段中注册该字段

  • script

    脚本字段,如果设置了该值,将索引该脚本执行之后的结果,如果在写文档的时候给该字段设置了值,文档将会被拒绝。脚本的格式与运行时相同,应该是一对(lat,lon)双值的形式

在脚本中使用geopoints

当在脚本中访问geopoint类型的值时,该值做为GeoPoint对象返回,可以使用如下方式读取

def geopoint = doc['location'].value;
def lat      = geopoint.lat;
def lon      = geopoint.lon;

但是更推下如下这样读取

def lat      = doc['location'].lat;
def lon      = doc['location'].lon;

Geoshape(形状)

对几何形状进行索引和查询(矩形和多边形),当索引的数据和查询包含除了点以外的形状时应该使用geo_shape类型

通过上面这俩类型的描述,我们可以得出一个结论,就是geo_point表示一个点,geo_shape表示多个点连接线组成的形状

支持的映射参数

geo_shape 映射将GeoJSON几何对象映射到geo_shape,要是用该映射类型,必须显式的设置,这句话意思就是我们使用该数据类型的时候必须明确指定索引类型

  • orientation

    可选参数,WKT多边形的默认方向,默认RIGHT逆时针。LEFT顺时针

    如果要指定RGIHT可以使用如下值设置

    • right
    • counterclockwise
    • ccw

    如果要指定LEFT可以使用如下值设置

    • left
    • clockwise
    • cw

    WKT(Well-known text)是一种文本标记语言,用于表示矢量几何对象、空间参照系统及空间参照系统之间的转换

  • ignore_malformed

    默认false,如果是不符合GeoJSON或者WKT的格式抛出异常,如果为true,则忽略

  • ignore_z_value

    默认true,三维空间信息将被记录,但是只有经纬度的值能被索引,如果为false,写入文档时如果有超过二维的信息,抛出异常拒绝文档写入

  • Coerce

    默认false,如果为true,如果是非封闭的多边形将自动闭合形成封闭的多边形

创建索引

PUT /example
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_shape"
      }
    }
  }
}

输入类型

地理位置形状信息可以使用GeoJSON或者WKT表示,下面是GeoJSONWKTElasticsearch中类型的对应关系

GeoJSON Type WKT Type Elasticsearch Type description
Point POINT Point 单一的经纬度坐标
LineString LINTSTRING lingstring 给定两个点或多个点组成的任意直线
Polygon POLYGON polygon 封闭的多边形,第一个点和最后一个点必须匹配,也就是n+1个点形成的n边多边形,至少4个顶点
MultiPoint MULTIPOINT multipoint 一组不相连但是可能相关的点
MultiLineString MULTILINESTRING multilinestring 单独的行字符串组成的数组
MultiPolygon MULTIPOLYGON multipolygon 单独的多边形数组
GeometryCollection GEOMETRYCOLLECTION geometrycollection 与multi开头的类型类似的一个GeoJSON形状,但是多个类型可以共同存在比如(Point和LineString同时存在)
N/A BBOX envelope 仅指定左上角和右下角两个点组成的边框或包

对于所有的类型都应该有子类型和字段坐标,GeoJSONWKT以及Elasticsearch中,坐标信息都是(经度,纬度),这与很多的地理信息API是不同的,地图地理信息API通常使用(纬度,经度),这一点是需要注意的一个地方,下面我将针对以上集中类型情况分别做一个写入数据的测试

  • 点的话比较简单,就比如手机定位,或者某个建筑的位置信息

    # GeoJSON
    POST /example/_doc
    {
      "location" : {
        "type" : "Point",
        "coordinates" : [-77.03653, 38.897676]
      }
    }
    # WKT
    POST /example/_doc
    {
      "location" : "POINT (-77.03653 38.897676)"
    }
    
  • 线

    由两个或者多个数组以上定义的行字符串,通过指定两个点,linestring将表示一条线,两个以上的点表示任意路径

    # GeoJSON
    POST /example/_doc
    {
      "location" : {
        "type" : "LineString",
        "coordinates" : [[-77.03653, 38.897676], [-77.009051, 38.889939]]
      }
    }
    # WKT
    POST /example/_doc
    {
      "location" : "LINESTRING (-77.03653 38.897676, -77.009051 38.889939)"
    }
    
  • 多边形

    多边形是由多个点定义的,列表的第一个点和最后一个点必须相同

    # GeoJSON
    POST /example/_doc
    {
      "location" : {
        "type" : "Polygon",
        "coordinates" : [
          [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
        ]
      }
    }
    # WKT
    POST /example/_doc
    {
      "location" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))"
    }
    

    上面这个定义的是一个多边形,下面定义一个带洞的多边形,下面例子中,第一个数组代表多边形外部边界,第二个数组代表内部洞的边界

    # GeoJSON
    POST /example/_doc
    {
      "location" : {
        "type" : "Polygon",
        "coordinates" : [
          [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],
          [ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]
        ]
      }
    }
    # WKT
    POST /example/_doc
    {
      "location" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2))"
    }
    
  • 多边形的方向

    多边形的方向也就是顶点的顺序,有Right逆时针和Left顺时针,Elasticsearch使用多边形的方向来确认是否跨越了国际日期线(+-180度)

    因为WKT没有指定或者强制默认方向,我们可以使用方向映射的参数orientation来设置多边形的方向

    GeoJSON默认使用Right的方向,与参数orientation设置无关,GeoJSON规范要求外部多边形使用逆时针,内部多边形使用顺时针

    但是可以使用文档级别的参数orientation覆盖来重写GeoJSON的默认方向,如下所示多边形方向就是Left

    POST /example/_doc
    {
      "location" : {
        "type" : "Polygon",
        "orientation" : "LEFT",
        "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] ]
        ]
      }
    }
    

    Elasticsearch 仅使用多边形的方向来确定是否跨越了国际日期线,如果一个多边形的最小经度与最大经度差值小于180,那多边形就没有跨越国际日期线,对多边形的方向也就不会有影响

    如果多边形的最小经度与最大经度差值等于180或者大于180,则Elasticsearch会检查文档级别的orientation参数方向与默认的方向是否不同,如果方向不同,Elasticsearch会在跨越国际日期线的地方分割多边形

  • 点的列表

    # GeoJSON
    POST /example/_doc
    {
      "location" : {
        "type" : "MultiPoint",
        "coordinates" : [
          [102.0, 2.0], [103.0, 2.0]
        ]
      }
    }
    # WKT
    POST /example/_doc
    {
      "location" : "MULTIPOINT (102.0 2.0, 103.0 2.0)"
    }
    
  • 线的列表

    # GeoJSON
    POST /example/_doc
    {
      "location" : {
        "type" : "MultiLineString",
        "coordinates" : [
          [ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0] ],
          [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0] ],
          [ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8] ]
        ]
      }
    }
    #WKT
    POST /example/_doc
    {
      "location" : "MULTILINESTRING ((102.0 2.0, 103.0 2.0, 103.0 3.0, 102.0 3.0), (100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8))"
    }
    
  • 多个多边形

    如下代表两个多边形,其中第二个多边形包含一个洞

    # GeoJSON
    POST /example/_doc
    {
      "location" : {
        "type" : "MultiPolygon",
        "coordinates" : [
          [ [[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]] ],
          [ [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],
            [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]] ]
        ]
      }
    }
    # WKT
    POST /example/_doc
    {
      "location" : "MULTIPOLYGON (((102.0 2.0, 103.0 2.0, 103.0 3.0, 102.0 3.0, 102.0 2.0)), ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2)))"
    }
    
  • 集合对象

    包含PointLineString的例子

    # GeoJSON
    POST /example/_doc
    {
      "location" : {
        "type": "GeometryCollection",
        "geometries": [
          {
            "type": "Point",
            "coordinates": [100.0, 0.0]
          },
          {
            "type": "LineString",
            "coordinates": [ [101.0, 0.0], [102.0, 1.0] ]
          }
        ]
      }
    }
    # WKT
    POST /example/_doc
    {
      "location" : "GEOMETRYCOLLECTION (POINT (100.0 0.0), LINESTRING (101.0 0.0, 102.0 1.0))"
    }
    
  • envelope

    指定对角线的多边形,格式为[[minLon,maxLat],[maxLon,minLat]]

    POST /example/_doc
    {
      "location" : {
        "type" : "envelope",
        "coordinates" : [ [100.0, 1.0], [101.0, 0.0] ]
      }
    }
    

    下面使用WKT的BBOX类型,WKT的格式顺序为minLon,maxLon,maxLat,minLat

    POST /example/_doc
    {
      "location" : "BBOX (100.0, 102.0, 2.0, 0.0)"
    }
    
  • GeoJSONWKT都不支持一个点的半径圆类型,但是可以使用circle ingest processor来近似的模拟一个圆来作为一个多边形

排序和检索

由于形状的输入结构复杂度和索引表示形状的复杂性,目前不能对索引进行排序或者直接检索他们的字段,目前geo_shape只能通过_source来检索

基于地理位置信息的范围查询

参考:https://kucw.github.io/blog/2019/12/elasticsearch-geo-point/

Geo-bounding box query(矩形过滤)

https://www.elastic.co/guide/en/elasticsearch/reference/8.1/query-dsl-geo-bounding-box-query.html

可以对数据类型为geo_pointgeo_shape的值在矩形边框内进行检索,找出落在矩形内的点

Geopoint 值的匹配

下面是一个演示一个点在一个矩形边框范围的一个例子,首先还是创建一个索引

  • 创建索引

    索引包含两个字段,desc为地点的描述信息,pin.location为具体的地理位置信息

    PUT /my_locations
    {
      "mappings": {
        "properties": {
          "desc":{
            "type": "text"
          },
          "pin": {
            "properties": {
              "location": {
                "type": "geo_point"
              }
            }
          }
        }
      }
    }
    
  • 插入一条测试数据,使用地图获取故宫博物院的坐标信息(位置信息与截图稍微有微微的偏差,为后期补充图片)
PUT /my_locations/_doc/1
{
  "desc":"故宫博物院",
  "pin": {
    "location": {
      "lat": 39.92375,
      "lon": 116.40348
    }
  }
}
  • 定义下图所示的边界范围,矩形框内查询,top_left就是顶点1bottom_right就是顶点2
GET my_locations/_search
{
  "query": {
    "bool": {
      "must": {
        "match_all": {}
      },
      "filter": {
        "geo_bounding_box": {
          "pin.location": {
            "top_left": {
              "lat": 39.93872,
              "lon": 116.37958
            },
            "bottom_right": {
              "lat": 39.90924,
              "lon": 116.42475
            }
          }
        }
      }
    }
  }
}

查看返回结果可以获取到刚才插入的故宫博物院地点信息

Geoshape 值的匹配

  • 新建索引

    PUT /my_geoshapes
    {
      "mappings": {
        "properties": {
          "desc":{
            "type": "text"
          },
          "pin": {
            "properties": {
              "location": {
                "type": "geo_shape"
              }
            }
          }
        }
      }
    }
    
  • 插入测试数据,如下数据在地图展示如下,5个地理位置信息为图中矩形的顶点,顺时针第一个和最后一个保持相同如下地理位置信息数据的顺序依次为[1,2,3,4,1]

    PUT /my_geoshapes/_doc/1
    {
      "desc":"故宫博物院",
      "pin": {
        "location": {
          "type" : "polygon",
          "coordinates" : [[[116.39801,39.92905], [116.40774,39.92905], [116.40936,39.92063], [116.39761,39.92000], [116.39801,39.92905]]]
        }
      }
    }
    
  • 查询范围内形状匹配的值

    GET my_geoshapes/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_bounding_box": {
              "pin.location": {
                "top_left": {
                  "lat": 39.93872,
                  "lon": 116.37958
                },
                "bottom_right": {
                  "lat": 39.90794,
                  "lon": 116.42486
                }
              }
            }
          }
        }
      }
    }
    

    上面的top_left对应的就是图中的顶点1bottom_right对应的就是图中的顶点2

Geo-distance query(圆形过滤)

https://www.elastic.co/guide/en/elasticsearch/reference/8.1/query-dsl-geo-distance-query.html#query-dsl-geo-distance-query

以给定位置作圆点,画一个给定距离的圆,以匹配圆范围内的文档数据,下面我们用例子来说明这个圆形过滤

  • 创建索引,my_locations测试geo_point,my_geoshapes测试geo_shape

    PUT /my_locations
    {
      "mappings": {
        "properties": {
          "desc":{
            "type": "text"
          },
          "pin": {
            "properties": {
              "location": {
                "type": "geo_point"
              }
            }
          }
        }
      }
    }
    
    PUT /my_geoshapes
    {
      "mappings": {
        "properties": {
          "desc":{
            "type": "text"
          },
          "pin": {
            "properties": {
              "location": {
                "type": "geo_shape"
              }
            }
          }
        }
      }
    }
    
  • 插入一个测试数据,地点信息为西单大悦城(经纬度:116.37927 , 39.91706)

    PUT /my_locations/_doc/1
    {
      "desc":"西单大悦城",
      "pin": {
        "location": {
          "lat":39.91706 ,
          "lon": 116.37927
        }
      }
    }
    PUT /my_geoshapes/_doc/1
    {
      "desc":"西单大悦城",
      "pin": {
        "location": {
          "type" : "polygon",
          "coordinates" : [[[116.37855,39.91761], [116.38018,39.91756], [116.38024,39.91651], [116.37862,39.91636 ], [116.37855,39.91761]]]
        }
      }
    }
    
    
  • 查询故宫博物院周边5公里的文档信息

    GET /my_locations/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_distance": {
              "distance": "5km",
              "pin.location": {
                "lat": 39.92402,
                "lon": 116.4036
              }
            }
          }
        }
      }
    }
    GET my_geoshapes/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_distance": {
              "distance": "5km",
              "pin.location": {
                "lat": 39.92402,
                "lon": 116.40360
              }
            }
          }
        }
      }
    }
    

    查看结果可以得到距离为周边3公里内是搜索不到的,大于3公里就可以搜索到我们定义的西单大悦城的地点信息

    看下面例子,同时在my_locationsmy_geoshapes两个索引中搜索故宫博物院周边5公里内的地点信息

    GET my_locations,my_geoshapes/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_distance": {
              "distance": "5km",
              "pin.location": {
                "lat": 39.92402,
                "lon": 116.40360
              }
            }
          }
        }
      }
    }
    

扩展知识

在上文中我们也看到了,录入测试数据的方式支持多种,在使用过滤器查询数据的时候同样如此可以使用多种方式进行过滤,如下简单举例几种,比如

  • 使用数组形式(经度,纬度)

    GET /my_locations/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_distance": {
              "distance": "5km",
              "pin.location": [ 116.40360,39.92402 ]
            }
          }
        }
      }
    }
    
  • 使用字符串(纬度,经度)

    GET /my_locations/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_distance": {
              "distance": "5km",
              "pin.location": "39.92402,116.40360"
            }
          }
        }
      }
    }
    GET /my_geoshapes/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_distance": {
              "distance": "5km",
              "pin.location": "39.92402,116.40360"
            }
          }
        }
      }
    }
    

过滤查询接收的参数说明

  • distance

    以指定的点为圆心,以distance的值为半径画圆,匹配圆内的地点信息数据,距离单位默认米(m),其他距离单位可以参考官网

    https://www.elastic.co/guide/en/elasticsearch/reference/8.1/api-conventions.html#distance-units

  • distance_type

    计算距离的方式,也就是distance生效的方式,默认是上述所说的为圆也就是弧形计算(arc),另一个可选值是平面的(plane),效率比arc快,但是精度有所损失,尤其是distance的值很大时或者两极处。plane比喻地球是平的,在赤道附近时精度是最佳的,靠近两级时精度略有损失。比如想找附近5km的地点,然后找到了5.2km的地点,这个误差取决于我们是否可以接受

  • _name

    该查询语句的名称

  • validation_method

    是否接受错误的地点坐标信息

    • STRICT 默认值,抛出异常
    • COERCE 尝试修正错误坐标信息为正确的坐标
    • IGNORE_MALFORMED 允许无效的错误的地点坐标信息

geo_distance的过滤请求可以匹配一篇文档中的多个地点值,只要有一个地点值匹配,该文档就会被返回

ignore_unmapped字段的值设置为true,查询时如果字段不匹配会忽略报错,返回空文档;如果设置为false,遇到不匹配的字段会 抛出异常

如下示例,匹配pin.location1字段,我们知道该字段在索引中是不存在的,所以当ignore_unmapped设置为true时返回空文档,设置为false时抛出异常

GET my_locations,my_geoshapes/_search
{
  "query": {
    "bool": {
      "must": {
        "match_all": {}
      },
      "filter": {
        "geo_distance": {
          "ignore_unmapped":true,
          "_name":"zuiyuquery",
          "distance": "5km",
          "pin.location1": "wx4g0gfw22x"
        }
      }
    }
  }
}

Geo-polygon query(自定义多边形过滤)

https://www.elastic.co/guide/en/elasticsearch/reference/8.1/query-dsl-geo-polygon-query.html

多边形过滤,顾名思义就是查询条件定义个多边形,返回该多边形区域内的点的文档信息

官网已经说明es7.12版本开始废弃,推荐使用geoshape,参考后文的 geoshape query

该查询接收的参数有如下

  • _name

    该查询语句的名称

  • validation_method

    是否接受错误的地点坐标信息

    • STRICT 默认值,抛出异常
    • COERCE 尝试修正错误坐标信息为正确的坐标
    • IGNORE_MALFORMED 允许无效的错误的地点坐标信息

需要注意的是,查询的字段需要设置为geo_point类型,同样ignore_unmapped字段的值设置为true,查询时如果字段不匹配会忽略报错,返回空文档;如果设置为false,遇到不匹配的字段会 抛出异常

  • 我们先插入一条故宫位置的信息文档

    PUT /my_locations/_doc/2
    {
      "desc":"故宫",
      "pin":{
        "location":"39.92307,116.40291"
      }
    }
    

如下是对pin.location字段定义一个三条边的形状的过滤查询语句,三个点顺序【1,2,3】排列,此时该 查询会返回故宫的信息,但是刚才定的西单大悦城的信息就不会返回

GET my_locations/_search
{
  "query": {
    "bool": {
      "must": {
        "match_all": {}
      },
      "filter": {
        "geo_polygon": {
          "pin.location": {
            "points": [
              { "lat": 39.93506, "lon": 116.41043 },
              { "lat": 39.91101, "lon": 116.42383 },
              { "lat": 39.92217, "lon": 116.35836 }
            ]
          }
        }
      }
    }
  }
}

同样的自定义查询语句也可以接收【字符串,数组,geohash】的地点信息

  • 字符串

    # 字符串
    GET my_locations/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_polygon": {
              "pin.location": {
                "points": [
                  "39.93506,116.41043","39.91101,116.42383","39.92217,116.35836"
                ]
              }
            }
          }
        }
      }
    }
    
  • 数组

    # 数组
    GET my_locations/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_polygon": {
              "pin.location": {
                "points": [
                  [116.41043,39.93506],
                  [116.42383,39.91101],
                  [116.35836,39.92217]
                ]
              }
            }
          }
        }
      }
    }
    
  • geohash

    # geohash
    
    GET my_locations/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_polygon": {
              "pin.location": {
                "points": [
                  "wx4g0vzqxdg",
                 "39.91101,116.42383",
                 "39.92217,116.35836"
                ]
              }
            }
          }
        }
      }
    

Geoshape query(形状查询)

https://www.elastic.co/guide/en/elasticsearch/reference/8.1/query-dsl-geo-shape-query.html

支持geo_shape和geo_point两种字段类型的过滤查询

形状查询,支持传入一个完整图形形状的数据或者提前写入的图形形状数据,首先还是先演示一下如何使用传入的图形形状数据检索

输入图形形状查询

  • 定义索引,geo_shape字段

    PUT /example
    {
      "mappings": {
        "properties": {
          "location": {
            "type": "geo_shape"
          }
        }
      }
    }
    
  • 插入测试数据

    POST /example/_doc?refresh
    {
      "name": "东单",
      "location": {
        "type": "point",
        "coordinates": [ 116.426466,39.914743]
      }
    }
    POST /example/_doc?refresh
    {
      "name": "天安门东",
      "location": {
        "type": "point",
        "coordinates": [ 116.40771,39.914079]
      }
    }
    POST /example/_doc?refresh
    {
      "name": "天安门西",
      "location": {
        "type": "point",
        "coordinates": [ 116.397936,39.913802]
      }
    }
    POST /example/_doc?refresh
    {
      "name": "西单",
      "location": {
        "type": "point",
        "coordinates": [ 116.383132,39.913581]
      }
    }
    
  • 过滤检索,返回文档中形状与检索形状相交的文档数据,如下查询语句返回经度116-117之间,纬度39-40之间的文档

    GET /example/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_shape": {
              "location": {
                "shape": {
                  "type": "envelope",
                  "coordinates": [ [116.0,40.0 ], [ 117.0,39.0 ] ]
                },
                "relation": "within"
              }
            }
          }
        }
      }
    }
    
  • 下面是geo_point类型字段的demo

    PUT /example_points
    {
      "mappings": {
        "properties": {
          "location": {
            "type": "geo_point"
          }
        }
      }
    }
    
    PUT /example_points/_doc/1?refresh
    {
      "name": "Wind & Wetter, Berlin, Germany",
      "location": [13.400544, 52.530286]
    }
    
    PUT /example_points/_doc/1?refresh
    {
      "name": "东单",
      "location": [116.426466,39.914743]
    }
    PUT /example_points/_doc/2?refresh
    {
      "name": "天安门东",
      "location": [116.40771,39.914079]
    }
    PUT /example_points/_doc/3?refresh
    {
      "name": "天安门西",
      "location": [116.397936,39.913802]
    }
    PUT /example_points/_doc/4?refresh
    {
      "name": "西单",
      "location": [116.383132,39.913581]
    }
    
    GET /example_points/_search
    {
      "query": {
        "bool": {
          "must": {
            "match_all": {}
          },
          "filter": {
            "geo_shape": {
              "location": {
                "shape": {
                  "type": "envelope",
                  "coordinates": [ [ 116.0, 40.0 ], [ 117.0, 39.0 ] ]
                },
                "relation": "intersects"
              }
            }
          }
        }
      }
    }
    

    通过上面两个不同字段的检索demo,我们应该也发现了,relation这个的值我们竟然使用的不一样,那么他们有什么区别呢,各自代表的含义如下:

  • INTERSECTS

    默认值,返回字段类型为geo_shape或者geo_point与检索的图形相交的文档

  • DISJOINT

    返回字段类型为geo_shape或者geo_point与检索图形不相交的文档

  • WITHIN

    返回字段类型为geo_shape或者geo_point``在检索图形范围内的文档,不支持线的几何图形

  • CONTAINS

    返回文档中字段类型为geo_shape或者geo_point的信息 包含检索图形形状的文档

预置图形形状查询

预置就是提前设置好图形的数据,保存在索引中,查询时根据提前定义好图形的名称来过滤数据,首先它有如下几个参数

  • id 预索引形状的id
  • index 预索引形状所在的索引位置,默认shapes
  • path 预索引形状所在索引中的字段名称,默认shape
  • routing 可选,预索引形状的路由参数

查询示例如下

PUT /shapes
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_shape"
      }
    }
  }
}

PUT /shapes/_doc/oneline
{
  "location": {
    "type": "envelope",
    "coordinates" : [[116.0, 40.0], [ 117.0, 39.0]]
  }
}

GET /example/_search
{
  "query": {
    "bool": {
      "filter": {
        "geo_shape": {
          "location": {
            "indexed_shape": {
              "index": "shapes",
              "id": "oneline",
              "path": "location"
            }
          }
        }
      }
    }
  }
}

基于地理位置信息或者到中心点距离的聚合统计

Geo网格聚合

https://www.elastic.co/guide/en/elasticsearch/reference/8.1/search-aggregations-bucket-geohashgrid-aggregation.html

网格聚合,将字段类型为geo_point或者geo_shape的数据划分为一个个的单元格,也就是一个个的单元格的桶中,既然是单元格那也就是有大有小,也就是高精确度与低精确度值,精度值的范围为1-121表示精确度最低,范围最大,12表示精确度最高但是范围也最小,甚至可能出现百万个以上的桶的聚合,所以这个是需要注意的地方,如果精确度要求比较高的话,可以使用geo_bounding box query过滤缩小聚合范围。网格聚合通过geohash来实现,通过指定聚合类型为geohash_grid来实现网格聚合,下面是演示demo

geo_point

  • 创建索引

    PUT /gugong_map
    {
      "mappings": {
        "properties": {
          "location": {
            "type": "geo_point"
          }
        }
      }
    }
    
    
  • 插入测试数据

    POST /gugong_map/_bulk?refresh
    {"index":{"_id":1}}
    {"location": "39.91466,116.42633", "name": "东单"}
    {"index":{"_id":2}}
    {"location": "39.914105,116.407934", "name": "天安门东"}
    {"index":{"_id":3}}
    {"location": "39.913662,116.398232", "name": "天安门西"}
    {"index":{"_id":4}}
    {"location": "39.913441,116.3835", "name": "西单A"}
    {"index":{"_id":5}}
    {"location": "39.913385,116.380625", "name": "西单B"}
    {"index":{"_id":6}}
    {"location": "39.922184,116.380338", "name": "灵境胡同"}
    
    
  • 聚合查询

    POST /gugong_map/_search?size=0
    {
      "aggregations": {
        "large-grid": {
          "geohash_grid": {
            "field": "location",
            "precision": 5
          }
        }
      }
    }
    
  • 使用过滤条件的聚合

    POST /gugong_map/_search?size=0
    {
      "aggregations": {
        "zoomed-in": {
          "filter": {
            "geo_bounding_box": {
              "location": {
                "top_left": "39.930429,116.379619",
                "bottom_right": "39.909788,116.403981"
              }
            }
          },
          "aggregations": {
            "zoom1": {
              "geohash_grid": {
                "field": "location",
                "precision": 8
              }
            }
          }
        }
      }
    }
    
  • 使用过滤参数的聚合

    POST /gugong_map/_search?size=0
    {
      "aggregations": {
        "tiles-in-bounds": {
          "geohash_grid": {
            "field": "location",
            "precision": 8,
            "bounds": {
                "top_left": "39.930429,116.379619",
                "bottom_right": "39.909788,116.403981"
            }
          }
        }
      }
    }
    

geo_shape

geo_shapegeo_point类似

支持参数

  • field 聚合的字段名称, 必选字段
  • precision 精度值,范围从1-12,也可以使用距离比如1km10m这种距离值 可选
  • bounds 等价于geo_bounding box query 过滤边界的参数值设置 可选
  • size 返回聚合桶的数量 可选
  • shard_size 为了对返回的聚合结果进行更精确的计算,该值为每个分片返回的聚合桶的数量,默认max(10,(size x number-of-shards))的最大值

圆点距离聚合

https://www.elastic.co/guide/en/elasticsearch/reference/8.1/search-aggregations-bucket-geodistance-aggregation.html

给定一圆点,以给出距离为半径画圆,将落在圆内的数据按照到原点的距离聚合,我们可以定一个起始值,一组范围的值来规定聚合桶的范围,下面跟我一起来看下如何使用到圆点距离的聚合

  • 创建一个索引保存故宫周围地铁站的信息

    PUT /gugong_map
    {
      "mappings": {
        "properties": {
          "location": {
            "type": "geo_point"
          }
        }
      }
    }
    
  • 插入故宫周围地铁站信息

    聚合 的字段必须显示设置为geo_point类型,

    POST /gugong_map/_bulk?refresh
    {"index":{"_id":1}}
    {"location": [116.426259,39.91488], "name": "东单站"}
    {"index":{"_id":2}}
    {"location": [116.426259,39.91488], "name": "天安门东站"}
    {"index":{"_id":3}}
    {"location": [116.397873,39.913828], "name": "天安门西站"}
    {"index":{"_id":4}}
    {"location": [116.383284,39.913441], "name": "西单站"}
    {"index":{"_id":5}}
    {"location": [116.379979,39.922184], "name": "灵境胡同站"}
    {"index":{"_id":6}}
    {"location": [116.379979,39.922184], "name": "西四站"}
    {"index":{"_id":7}}
    {"location": [116.39313,39.939668], "name": "北海北站"}
    {"index":{"_id":8}}
    {"location": [116.410665,39.939944], "name": "南锣鼓巷站"}
    {"index":{"_id":9}}
    {"location": [116.417061,39.92224], "name": "金鱼胡同站"}
    {"index":{"_id":10}}
    {"location": [116.418067,39.916097], "name": "王府井站"}
    
  • 根据地铁站距离到故宫的距离进行聚合查询

    聚合的默认距离单位是m,我们可以通过unit参数指定

    POST /gugong_map/_search?size=0
    {
      "aggs": {
        "rings_around_amsterdam": {
          "geo_distance": {
            "field": "location",
            "origin": [116.403119,39.923568],
            "ranges": [
              { "to": 1000 },
              { "from": 1000, "to": 2000 },
              { "from": 2000 }
            ]
          }
        }
      }
    }
    
  • 通过指定unit距离单位聚合

    unit 支持mi(miles),in(inches),yd(yards),km(kilometers),cm(centimeters),mm(millimeters),m(meters)

    POST /gugong_map/_search?size=0
    {
      "aggs": {
        "rings": {
          "geo_distance": {
            "field": "location",
            "origin": [116.403119,39.923568],
            "unit": "m", 
            "ranges": [
              { "to": 1000 },
              { "from": 1000, "to": 2000 },
              { "from": 2000 }
            ]
          }
        }
      }
    }
    
  • 指定聚合计算方式,精确度

    arc,默认,精确度高

    plane 精确度低,距离仅时推荐使用,比如5km

    POST /gugong_map/_search?size=0
    {
      "aggs": {
        "rings": {
          "geo_distance": {
            "field": "location",
            "origin": [116.403119,39.923568],
            "unit": "m",
            "distance_type": "plane",
            "ranges": [
              { "to": 1000 },
              { "from": 1000, "to": 2000 },
              { "from": 2000 }
            ]
          }
        }
      }
    }
    
  • 对聚合桶返回唯一的key

    设置keyedtrue返回唯一的key与聚合桶绑定,如果为false返回的就是聚合桶的数组

    POST /gugong_map/_search?size=0
    {
      "aggs": {
        "rings_around_amsterdam": {
          "geo_distance": {
            "field": "location",
            "origin": [
              116.403119,
              39.923568
            ],
            "ranges": [
              {
                "to": 1000
              },
              {
                "from": 1000,
                "to": 2000
              },
              {
                "from": 2000
              }
            ],
            "keyed": true
          }
        }
      }
    }
    

    还可以 自定义聚合桶的名称

    POST /gugong_map/_search?size=0
    {
      "aggs": {
        "rings_around_amsterdam": {
          "geo_distance": {
            "field": "location",
            "origin": [
              116.403119,
              39.923568
            ],
            "ranges": [
              { "to": 1000, "key": "first_ring" },
              { "from": 1000, "to": 2000, "key": "second_ring" },
              { "from": 2000, "key": "third_ring" }
            ],
            "keyed": true
          }
        }
      }
    }
    

将地理位置信息加入到分数计算中

https://www.elastic.co/guide/en/elasticsearch/reference/8.1/query-dsl-function-score-query.html

  • 首先字段必须是geo_point类型
  • 提供一个圆心点的位置坐标
  • scaleoffset必须设置

满足以上三点就可以加入到分数计算中,gauss 使用高斯函数来衰减,具体参考官网,后面专门出一期衰减函数的文章

GET gugong_map/_search
{
  "query": {
    "function_score": {
      "gauss": {
        "location": {
          "origin": [116.385728,39.870814], 
          "scale": "3000m",// origin + offset,衰减率,也就得分衰减的速度
          "offset": "3000m",// 3000m以内的文档得分不处理,3000m以外的文档得分慢慢衰减  
          "decay": 0.5  // 从 origin 衰减到 scale 所得的评分_score,默认为0.5         
        }
      }
    }
  }
}

基于距离信息的排序

https://www.elastic.co/guide/en/elasticsearch/reference/8.1/sort-search-results.html#geo-sorting

需要指定圆点的坐标信息,根据到原点的距离进行排序,比如如下语句,查询name字段带的,根据到北京南站(116.385728,39.870814)的距离升序输出,坐标信息,与geo_point支持的方式相同,可以为数组字符串对象geohash

  • _geo_distance指定为geo类型字段排序的类型

  • order 升序还是降序 ,ascdesc

  • unit 距离排序使用的单位,默认m

  • mode 如果一个地点值的字段包含多个地理位置信息,怎么取值,如果升序排序时选最短距离,降序排序时选最长距离的。支持min,max,median,avg

  • distance_type默认 arc 精确度高,plane 速度快,再靠近两级附近或者距离远时精度低

  • ignore_unmapped 字段不存在映射时是否报错,默认false,未匹配时报错,如果为true,等于使用unmapped_type 的字段排序

    地理位置排序时,如果文档中没有地理位置信息,距离被默认为 infinity

GET gugong_map/_search
{
  "sort" : [
    {
      "_geo_distance" : {
          "location" : [116.385728,39.870814],
          "order" : "asc",
          "unit" : "km",
          "mode" : "min",
          "distance_type" : "arc",
          "ignore_unmapped": true
      }
    }
  ],
  "query" : {
    "term" : { "name" : "站" }
  }
}

总结

在上面的章节中,我们由浅入深的学习了geo_pointgeo_shape两种geo数据类型,以及如何通过对这两字段进行检索,聚合排序,相信大家读到这也基本有了一个概念了,再深点还是要去看官方文档了,不过实践是检验真理的唯一标准,多实操吧,总没有错,加油!!!

如果有写的不对的地方欢迎指出哦!!!共同进步才能走的更远!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容