Elasticsearch 地理坐标
何为坐标
我们的日常生活中时时刻刻都在应用坐标技术,比如酒店预订,可以搜索展示附近的酒店,再比如搜索附近的餐厅. 衣食住行,所有的一切都离不开坐标,es提供了基于地理坐标的搜索,并和我们的生活喜喜相关.
经度是平面上的位置,从本初子午线作为零点,左右分别是[0,-180]和[0,+180],纬度是上半球[0,90] 和下半球[0,-90], 所以一个经纬度点(x,y)在地球上位移确定了一个位置. x可想象成球面坐标从0点的偏移量,y表示上下位置的便宜量,见上图.
一.elasticsearch 的地理坐标坐标分两种
Elasticsearch支持两种类型的地理数据:
- geo_point:是一个(lon,lat) 经度/纬度 点
- geo_shape: 是对geo_point不足之处的拓展.支持点、线、圆、多边形、多多边形.
很多初次接触的童鞋,不理解geo_shape. 想象一下,一个学校在地理坐标中怎么表示? geo_point只是一个点位,无法表示一片区域, 所以这个时候就需要可以表示区域的数据结构,这就是geo_shape,这里先说到这里,后面还有详解.
二.geo_point的搜索
先不说怎么搜索,先来聊聊这个point,点顾名思义就是一个经纬度点. 也就是我们的es中存储的每条数据有一个字段存储的是一个经纬度的点. 对于数据中的经纬度点,我们能想到的就是查询的时候提供一个范围,然后判断目标索引中数据的经纬度是否在我提供的范围内, 或者我提供一个点,然后查找目标索引中距离我提供的点在一定距离内. 事实上es确实也是这么做的, 下面是es提供的集中搜索方式:
2.1 geo_bounding_box (矩形查询)
查询经纬度落在指定矩形内的数据,在坐标数据中,一个矩形可以由两个点表示. 两个点做平行线即可,在市级地理坐标中,则表示的在地球上围成的矩形范围. 平面几何图如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021063020234345.png#pic_center)
下面摘自官网的例子:
//构造mappings
PUT /my_locations
{
"mappings": {
"properties": {
"pin": {
"properties": {
"location": {
"type": "geo_point" //指定location字段类型是经纬度点坐标.
}
}
}
}
}
}
//放入一条数据
PUT /my_locations/_doc/1
{
"pin": {
"location": {
"lat": 40.12,
"lon": -71.34
}
}
}
//查询: 意思是找出索引my_locations 中经纬度点在指定的矩形范围内的数据
GET my_locations/_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
}
}
}
}
}
}
}
2.2 geo_distance (距离查询)
顾名思义,距离查询的意思是用户指定一个经纬度,然后指定一个距离.表示查询出目标索引中 距离我指定的点 在我指定的范围内的所有数据.
实际上就是以我指定的点为中心,其他的点距离中心点不超过我指定的距离,几何图如下(实际上就是以指定的点为圆心,距离为半径画出的圆,所有在圆内的点都是符合条件的数据):
官方案例如下:
//建立mapping映射
PUT /my_locations
{
"mappings": {
"properties": {
"pin": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
}
}
//插入一条数据
PUT /my_locations/_doc/1
{
"pin": {
"location": {
"lat": 40.12,
"lon": -71.34
}
}
}
//查询指定半径
GET /my_locations/_search
{
"query": {
"bool": {
"must": {
"match_all": {}
},
"filter": {
"geo_distance": {
"distance": "200km",//我指定的距离半径
"pin.location": { //我指定的圆心坐标点
"lat": 40,
"lon": -70
}
}
}
}
}
}
2.3 geo_polygon(范围查询)
顾名思义就是我们是顶一个范围,去查询目标索引中落在指定范围内的点,将符合查询条件的数据查出来,这个范围是由多个点组成的,几何意义上是一个任意的不规则的范围.
官网案例: 三种写法
//第一种写法
GET /_search
{
"query": {
"bool": {
"must": {
"match_all": {}
},
"filter": {
"geo_polygon": {
"person.location": {
"points": [
{ "lat": 40, "lon": -70 },
{ "lat": 30, "lon": -80 },
{ "lat": 20, "lon": -90 }
]
}
}
}
}
}
}
//第二种写法(注意格式是: [经度,纬度] )
GET /_search
{
"query": {
"bool": {
"must": {
"match_all": {}
},
"filter": {
"geo_polygon": {
"person.location": {
"points": [
[ -70, 40 ],
[ -80, 30 ],
[ -90, 20 ]
]
}
}
}
}
}
}
//第三种写法(注意格式是: 纬度,经度)
GET /_search
{
"query": {
"bool": {
"must": {
"match_all": {}
},
"filter": {
"geo_polygon": {
"person.location": {
"points": [
"40, -70",
"30, -80",
"20, -90"
]
}
}
}
}
}
}
2.4 geo_point 总结
geo_point的搜索方式有四种,上面讲了三种,第四种暂且不提,放在后米娜,前面三种搜索方式如下:
- geo_bounding_box (矩形查询)
- geo_distance (距离查询)
- geo_polygon(范围查询)
三.geo_shape 数据类型介绍
geo_shape 数据类型上面也说了是存储的一个范围, 不同于geo_point,geo_point 存储的是一个点,geo_point的缺陷就是无法表示一片范围. geo_shape 其实是由多个geo_point表示的一个范围. 在空间上表示多个经纬度的点, 围城的一片不规则的区域. 这个区域数据存储在es中表现为一个geo_point组成的数组.
ELASTICSEARCH 官网对geo_shape 的解释如下:
geo_shape 促使es可以存储以及可以搜索任意的地理类型,比如矩形和多边形. 当被存储或者被搜索的是 多边形而不是geo_point一个点的时候,es的数据类型应该设计成geo_shape. 当你搜索geo_shape类型 的时候可以用query: [geo_shape Query] 我们后面会讲 geo_shape Query. 本节只说geo_shape 数据类型.
(原文:The geo_shape data type facilitates the indexing of and searching with arbitrary geo shapes such as rectangles and polygons. It should be used when either the data being indexed or the queries being executed contain shapes other than just points. You can query documents using this type using geo_shape Query.)
3.1 geo_shape mapping
geo_shape映射将 geo_json 几何对象映射到es中定义的 geo_shape 类型。要启用它,用户必须将字段显式映射到 geo_shape 类型。(这句话是说, 当我们往es put数据的时候, 地理形状数据的put格式可以是geo_json格式, es会自动将geo_json格式的地理数据映射成es索引的geo_shape数据类型, 所以在put 地理数据的时候必须要提前在es中将数据类型声明为geo_shape 类型)
假设现在elasticsearch 存在一个字段location其定义如下:
{
"properties":{
"location":{
"type":"geo_shape"
}
}
}
//上面的定义 表示location 数据可以存储任意多边形地理坐标
//该location可以是一个点, 线, 面.
3.2 往location 插入点(point)
上面说过,geo_shape 可以识别geo_json格式,那么我们这个点可以写成geo_json就行了,当然除了geo_json格式也可以是 Well-Known Text (WKT)格式, 下面是两种塞入方式.
//geo_json 格式
POST /example/_doc
{
"location" : {
"type" : "point",
"coordinates" : [-77.03653, 38.897676]
}
}
//WKL格式
POST /example/_doc
{
"location" : "POINT (-77.03653 38.897676)"
}
3.3 往location 插入多个点(multipoint)
location的数据类型是geo_shape, 可以存储点,线,面. 对点来说不仅可以存储一个点(类似于数据类型geo_point),当然也可以存储多个点.
//geo_json格式插入
POST /example/_doc
{
"location" : {
"type" : "multipoint",
"coordinates" : [
[102.0, 2.0], [103.0, 2.0]
]
}
}
//WKL格式插入
POST /example/_doc
{
"location" : "MULTIPOINT (102.0 2.0, 103.0 2.0)"
}
3.4 往location 插入线(linestring)
linestring由两个或多个位置的点组成。通过只指定两个点,linestring将表示一条直线。指定两个以上的点将创建任意曲线. 下面是数据塞入方式.
//geo_json格式
POST /example/_doc
{
"location" : {
"type" : "linestring",
"coordinates" : [[-77.03653, 38.897676], [-77.009051, 38.889939]]
}
}
//WKL格式
POST /example/_doc
{
"location" : "LINESTRING (-77.03653 38.897676, -77.009051 38.889939)"
}
3.4 往location 插入多条线(multilinestring)
既然可以存多个点,自然也可以存多条线,这一切都是符合人类逻辑的.
//geo_json格式
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] ]
]
}
}
//WKL格式
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))"
}
3.5 往location 插入多边形(polygon)
任意多边形,意思就是一个面. 一个多边形是由一系列的point 数组定义的。 point数组中的***第一个点和最后一个点***必须相同(多边形必须是闭合的)
//geo_json 格式
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] ]
]
}
}
//WKL格式
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))"
}
``
#### 3.4 往location 插入多边形(polygon) 带孔的形状
第一个数组表示外边界,第二个数组便是内边界,几何图如下图中的黑色部分.
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210701152141450.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2MDY2MDM5,size_16,color_FFFFFF,t_70#pic_center)
```python
//geo_json 插入方式
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] ]
]
}
}
//WKL插入方式
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))"
}
注意:上面的带孔图形,在插入图形数据的时候.WKT不强制顶点的特定顺序。GeoJSON要求外多边形必须是逆时针方向的,内部形状必须是顺时针方向的,这与开放地理空间联盟(OGC)的顶点排序简单特征访问规范一致。
如果出现不跨越国际日期变更线(即他们跨经度的小于180°)Elasticsearch接受顺时针和逆时针多边形. 但如果多边形,跨越国际日期变更线(或其他多边形超出180°)Elasticsearch要求顶点顺序遵循OGC和GeoJSON规范。否则,可能会创建一个意想不到的多边形,并返回意想不到的查询/筛选结果。
当设置geo_shape映射时,可以定义一个方位参数(orientation)。这将为映射的geo_shape字段的坐标列表定义默认的顶点顺序(顺时针/逆时针)。当然也可以在塞入数据的时候指定顺时针逆时针 比如:
POST /example/_doc
{
"location" : {
"type" : "polygon",
"orientation" : "clockwise",
"coordinates" : [
[ [100.0, 0.0], [100.0, 1.0], [101.0, 1.0], [101.0, 0.0], [100.0, 0.0] ]
]
}
}
orientation:参数用于定义形状坐标列表的顶点顺序,不区分大小写.
顺时针:
- right
- counterclockwise
- ccw
逆时针:
- left
- clockwise
- cw
默认顺时针,但是是符合OGC标准的,如果你传入数据是一个带孔的图形,则外圈会被自动解析成逆时针,内圈会解析成顺时针. 不需要手动处理.
3.6 往location 插入多个多边形(multipolygon)
//geo_json格式
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]] ]
]
}
}
//WKL格式
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)))"
}
3.6 往location 插入点/线/面 集合
意思是location这个图形可能是点和线组成的,也可能是点和面组成的,也可以是多线面组成的. 总之我们上面说的可以自由组合. 之前也说了geo_shape是任意类型,可以是点,可以是多个点,可以是线,可以是多条线,可以是面,可以是多个面. 自然也可以是点和面,可以是线和面,等.要发挥想象力.
//geo_json格式
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] ]
}
]
}
}
//WKL格式
POST /example/_doc
{
"location" : "GEOMETRYCOLLECTION (POINT (100.0 0.0), LINESTRING (101.0 0.0, 102.0 1.0))"
}
3.7 往location插入envelope类型
通过仅指定左上角和右下角点来指定的边界矩形(或包络线)。
//geo_json
POST /example/_doc
{
"location" : {
"type" : "envelope",
"coordinates" : [ [100.0, 1.0], [101.0, 0.0] ]
}
}
//KWL 格式
POST /example/_doc
{
"location" : "BBOX (100.0, 102.0, 2.0, 0.0)"
}
3.8 往location中插入圆circle
圆由一个圆心点,和一个距离半径组成.
POST /circle-example/_doc
{
"location" : {
"type" : "circle",
"coordinates" : [101.0, 1.0],
"radius" : "100m"
}
}
3.9附上官网对各种图形的类型定义
四:geo_shape query
上面介绍了,geo_point 的查询, geo_shape 类型数据的录入,但是却没有介绍geo_shape 数据的查询. 这一节来说这个.
geo_shape _query.可以查询geo_point 也可以查geo_shape类型.
geo_shape _query查询支持两种定义查询形状的方法:
- 一种是提供整个形状定义(形状可能很复杂,点比较多,构造query的时候麻烦,所以可以将要查询的形状录入另一个索引,查询的时候直接从这个索引中拿出来然后去查询,这就是第二种.)
- 另一种是引用在另一个索引中预先编入索引的形状的名称。
4.1 第一种
PUT /example
{
"mappings": {
"properties": {
"location": {
"type": "geo_shape"
}
}
}
}
//查询一个点
POST /example/_doc?refresh
{
"name": "Wind & Wetter, Berlin, Germany",
"location": {
"type": "point",
"coordinates": [ 13.400544, 52.530286 ]
}
}
//查询一个矩形
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"
}
}
}
}
}
}
4.2第二种
理论:预先将要查询的***条件*** 存入一个索引,该条件就是一个任意图形,可以是点/面/线/多点/多线/多面/矩形/圆形/组合类型. 然后查询的时候从这个索引中找到这个图形,以此构造我们的查询query语句.
只需要提供:
- id - 包含预索引形状的文档的 ID。
- index- 预索引形状所在的索引名称。默认为 shape。
- path- 指定为包含预索引形状的路径的字段。默认为shape。
- routing - 如果需要,形状文件的路由。
下面是例子:
PUT /shapes
{
"mappings": {
"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",
"id": "deu",
"path": "location"
}
}
}
}
}
}
}
4.3 relation参数
INTERSECTS-(默认)返回其geo_shape或geo_point字段与查询几何相交的所有文档。
DISJOINT- 返回其geo_shape或geo_point字段与查询几何没有共同之处的所有文档。
WITHIN- 返回其geo_shape或geo_point字段在查询几何范围内的所有文档。不支持线几何。
CONTAINS- 返回其geo_shape或geo_point字段包含查询几何的所有文档。
假设我传入的图形是A, 要查询的索引中的图形是B. (注意这里说的图形:可以是点/面/线/多点/多线/多面/矩形/圆形/组合类型)
- INTERSECTS A B 有相交的区域
- DISJOINT A B 无相交的区域
- WITHIN A 包含 B
- CONTAINS B 包含 A