ElasticSearch地理类型字段-geo_point和geo_shape应用示例
ElasticSearch提供了两种与地理相关的数据类型,geo_point和geo_shape,前者用于保存地理位置,即一个具体的坐标,如 121.690096,31.202253 ;而后者则用于保存地理形状,如矩形和多边形。
一、 geo_point类型
众所周知,地理位置由经度和纬度共同定义,所以geo_point定义地理位置坐标最基本的形式也是通过提供经度和纬度来实现。
关于geo_point类型,应用比较简单,而且官网讲解的也比较清楚,这里就不再赘述了。可以直接查看如下的官网来查询如何应用:
1. https://www.elastic.co/guide/cn/elasticsearch/guide/current/geopoints.html
(注:该链接上虽然是2.x版本,但是本知识点并不过时,依然可以看)
將上面文档中比较重要的部分截取如下: 有四种地理坐标点相关的过滤器可以用来选中或者排除文档:
这些过滤器判断点是否落在指定区域时的计算方法稍有不同,但过程类似。 指定的区域被转换成一系列以quad/geohash为前缀的tokens,并被用来在倒排索引中搜索拥有相同tokens的文档。
地理坐标过滤器使用代价昂贵 — 所以最好在文档集合尽可能少的场景下使用。 可以先使用那些简单快捷的过滤器,比如 布尔型过滤器 然后依次才是更昂贵的地理坐标过滤器或者脚本类的过滤器。 |
2. https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-geo-distance-query.html
(geo-distance查询: 以给定位置为圆心画一个圆,来找出那些地理坐标落在其中的doc)
3. https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-geo-polygon-query.html
(geo_polygon 查询:在地图上画一个多边形,搜索包含在多边形内部的doc)
4. https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-geo-bounding-box-query.html
(geo_bounding_box 查询,查询包含在某个矩形中的doc)
二、 geo_shape类型
官网:https://www.elastic.co/guide/en/elasticsearch/reference/7.x/geo-shape.html
geo_shape类型的字段,用于存储地理形状,支持GeoJSON及WKT中描述的大多数地理形状。
所谓的GeoJSON及WKT(Well-Known Text),指的是用来表示GeoShape数据的【形式】。比如,同样表示一个点(point),用GeoJson来表示,则是:
{ "type": "Point", "coordinates": [125.6, 10.1] }
而如果是用WKT来表示,则是:
POINT(125.6 10.1)
因此,在使用geo_shape类型的时候,不用纠结于使用GeoJSON还是WKT,我这里选择的是GeoJson格式,下面的演示中,使用的也是GeoJson格式。
geo_shape支持存储的常用形状数据如下(可查看官网:https://www.elastic.co/guide/en/elasticsearch/reference/7.x/geo-shape.html):
- 点(point)
- 多个点(multipoint)
- 圆形(circle)
- 矩形(envelope)
- 多边形 (polygon)
- 多个多边形(multipolygon)
- 线(LineString)
- 多条线(MultiLineString)
- 复合型的形状 GeometryCollection (按照我的理解,这个类型就是,比如某个字段中,可以同时存放point和polygon)
下面,我将对常用的或者说我目前已经使用到的类型进行一些常规的计算测试。包括point、multipoint、polygon、multipolygon。
为了测试方便操作,我们先定义个index template:
{ "order": 0, "index_patterns": [ "test-geo-*" ], "mappings": { "properties": { "id": { "type": "long" }, "name": { "type": "keyword" }, "location_point": { "type": "geo_point" }, "location_shape": { "type": "geo_shape" } } }, "aliases": {}
注:上面的index template规定了,但凡是以test-geo作为前缀的index,index里面名为location_poit的字段,类型将被映射为geo_point类型,名为location_shape的字段,将会被设置为geo_shape类型。
如下图:
2.1 Point类型测试
我们往名为test-geo-01的index中,插入具有如下位置关系的两个经纬度点:
使用如下命令批量插入数据:
###### geo_shape point测试
# 插入数据
POST test-geo-01/_bulk
{"index": {"_id": 1}}
{ "id" : 1,"name": "天安门","location_shape" : {"type": "point", "coordinates": [116.3974,39.90888] }}
{"index": {"_id": 2}}
{ "id" : 2,"name": "故宫博物院","location_shape" : {"type": "point", "coordinates": [116.396971,39.915464] }}
2.1.1 point与polygon的关系测试
先判断这两个point,与如下图所示的polygon的位置关系:
注:正如上图所示,查询用的polygon,在关系上就是包含了id=2的doc。因此:
1)当做within或者intersects查询时,查出来的是id=2的doc
2)若做的是disjoint查询,则查询出来的则是id=1的doc
查询命令如下:
GET test-geo-01/_search { "query": { "geo_shape": { "location_shape": { "relation": "within", "shape": { "type": "polygon", "coordinates": [[ [116.392379,39.917702], [116.393752,39.913933], [116.402206,39.913999], [116.399632,39.918838], [116.392379,39.917702] ]] } } } } }
查询结果截图:
2.1.2 point与circle的关系测试
接下來判断,index中的两个point,与如下图的circle的位置关系
可以看到,circle包含了id=1的点,但不包含id=2的点。
查询如下:
可惜报错了。报错显示不支持circle!!!
由官网也可以看到相关描述:
因此,我们照着官网,我们先修改一下test-geo-01的mapping (新增一个location_shape2的字段)
# 修改index的mapping,新增一个location_shape2字段 PUT test-geo-01/_mapping { "properties": { "location_shape2": { "type": "geo_shape", "strategy": "recursive" } } }
如下图:
接着更新test-geo-01的index的数据,把location_shape字段的数据,更新到location_shape2字段里面去
命令:
# 给test-geo-01添加一个location_shape2的字段,并且字段的值跟location_shape字段一致 POST test-geo-01/_bulk { "update": { "_id": "1"} } { "doc" : {"location_shape2" : {"type": "point", "coordinates": [116.3974,39.90888] }} } { "update": { "_id": "2"} } { "doc" : {"location_shape2" : {"type": "point", "coordinates": [116.396971,39.915464] }} }
结果如下:
此时,location_shape2字段的类型,就是符合circle查询规则的了。
更新完成之后,test-geo-01这个index中的内容如下:
接下来,我们再在location_shape2字段上,执行circle查询!就可以查询出来了:
命令:
GET test-geo-01/_search { "query": { "geo_shape": { "location_shape2": { "relation": "within", "shape": { "type": "circle", "radius": "500m", "coordinates": [116.3974,39.90888] } } } } }
执行结果:
2.2 MultiPoint类型测试
往geo-test-02的index中,插入如有如下位置关系的数据
命令:
###### geo_shape multipoint测试 # 插入数据 POST test-geo-02/_bulk {"index": {"_id": 1}} { "id" : 1,"name": "天安门,故宫博物院","location_shape" : {"type": "multipoint", "coordinates": [[116.3974,39.90888], [116.396971,39.915464]] }} {"index": {"_id": 2}} { "id" : 2,"name": "普度寺,王府井大厦,凝和庙","location_shape" : {"type": "multipoint", "coordinates": [[116.404653,39.913028], [116.410017,39.914213], [116.402979,39.919019]] }}
通过上面的命令,我们把:1和2两个point,插入到id=1的doc中,3、4、5这3个point, 插入到id=2的doc中
2.2.1 multipoint与polygon的关系测试
2.2.1.1 与如下图的polygon的关系判断结果:
由图也可以看出,这个查询的多边形与id=1和id=2的文档中的几个点,都没有任何的包含相交关系,所以无论是做within还是intersects关系查询,都是查询不到这两个doc的,如下图:
而如果是做 disjoint查询,则可以把这两条数据查询出来,如下图:
2.2.1.2 与如下图的polygon的关系判断结果
可以看到,这个多边形,把id=2的doc中的3和4这两个point给框住了,但是还有个5的point没框住
此时执行within查询,结果如下:
如上图,进行within关系查询,则啥也差不到。之所以差不到id=2的doc,是因为该doc中,还有一个point(即序号为5的point)没包含在多边形中。
执行intersects查询,结果如下:
执行disjoint查询,结果如下:
综上,可以得出结论:
对于multipoint而言,
1)当进行within查询时,之后某条doc,只有字段内的所有point都包含在多边形内,才会被查询出来;
2)进行intersects查询时,只要文档中有一个point包含在了多边形内,这条doc就会被查询出来
3)当进行disjoint查询时,只有文档中的所有point都不在多边形内,这条doc才会被查询出来。
2.3 Polygon 类型测试
作为测试,我们往test-geo-03的index中,插入具有如下位置关系的两个polygon:
其中,多边形1插入id=1的doc中,多边形2插入到id=2的doc中,命令如下:
###### geo_shape polygon测试 # 插入数据 POST test-geo-03/_bulk {"index": {"_id": 1}} { "id" : 1,"name": "天安门西站","location_shape" : {"type": "polygon", "coordinates": [[[116.388259,39.908847], [116.388173,39.906033], [116.393666,39.906033], [116.395082,39.908864], [116.388259,39.908847]]] }} {"index": {"_id": 2}} { "id" : 2,"name": "天安门东站","location_shape" : {"type": "polygon", "coordinates": [[[116.400318,39.909078], [116.400318,39.905934], [116.404567,39.906757], [116.400318,39.909078]]] }}
2.3.1 polygon与polygon的关系测试
2.3.1.1 下面,将测试这两条doc,与如下图所示的多边形的位置关系
正如上图所示,查询的多边形,与索引中的两个多边形,都完全不相交。
这个时候很显然,通过within或者intersects查询,都查询不到这两条doc。
但是,若是disjoint查询,则可以查询到这两条数据,如下图:
2.3.1.2 下面,将测试这两条doc,与如下图所示的多边形的位置关系
可以看到,查询的多边形,与id=2的doc中的polygon,是有相交的。
那么此时,如果是做within查询,则这两条doc都不会被查询出来,但是如果是做intersects查询,则可以把id=2的doc查询出来;如果是做disjoint查询,则可以把id=1的doc查询出来。
如下图:
2.3.1.3 测试这两条doc,与如下图所示的多边形的位置关系
可以看到,查詢的多边形,把id=2的doc中的多边形给完全包含起来了。
此时显而易见的,若是intersects或者within查询,则都可以把id=2的文档查询出来,而如果是做disjoint查询,则可以把id=1的文档查询出来。
执行结果如下:
2.4 MultiPolygon 类型测试
作为测试,我们往test-geo-04的index中,插入如下位置关系的几个多边形,其中,多边形1和2,插入到id=1的doc中,多边形3、4和5,插入到id=2的doc中
命令如下:
###### geo_shape multipolygon测试 # 插入数据 POST test-geo-04/_bulk {"index": {"_id": 1}} { "id" : 1,"name": "天安门西站,天安门东站","location_shape" : {"type": "multipolygon", "coordinates": [[[[116.388259,39.908847], [116.388173,39.906033], [116.393666,39.906033], [116.395082,39.908864], [116.388259,39.908847]]], [[[116.400318,39.909078], [116.400318,39.905934], [116.404567,39.906757], [116.400318,39.909078]]]] }} {"index": {"_id": 2}} { "id" : 2,"name": "故宫,普度寺,王府井","location_shape" : {"type": "multipolygon", "coordinates": [[[[116.395297,39.916155],[116.395469,39.914295],[116.397357,39.914197],[116.398344,39.914526],[116.398344,39.915842],[116.396756,39.916369],[116.395297,39.916155]]], [[[116.403666,39.914048],[116.403537,39.912551],[116.408043,39.912419],[116.403666,39.914048]]], [[[116.408858,39.9152],[116.408558,39.913407],[116.413193,39.913143],[116.408858,39.9152]]]] }}
2.4.1 multipolygon与polygon的关系测试
2.4.1.1 测试这两条doc,与如下图所示的多边形的位置关系
由上图可知,查询的多边形,与index中的多边形,都没有相交或者包含关系。
显然,此时若是做within或者intersects查询,则都查询不到这两条doc,如果是做disjoint查询,则可以查询到这两条数据,如下图:
2.4.1.2 测试这两条doc,与如下图所示的多边形的位置关系
如上图,此时,查询的多边形,包含了id=2的doc中序号为5的polygon,并与序号为4的polyogn相交,但与序号为3的polygon不相交。
此时进行within查询,则一个文档也查询不到!
进行intersects查询,则可以把id=2的doc查询出来!
进行disjoint查询,则可以把id=1的doc查询出来
2.4.1.3 测试这两条doc,与如下图所示的多边形的位置关系
可以看到,查询的多边形,把id=2的doc中的几个多边形(序号为3、4和5),都包含了起来。
此时,进行within和intersects查询,则都可以把id=2的doc查询出来,如下图
进行disjoint查询,则可以把id=1的doc查询出来,如下图
综上,可以得出结论:
对于multipolygon而言,
1)当进行within查询时,对于某条doc,只有doc内的所有polygon都包含在多边形内,才会被查询出来;
2)进行intersects查询时,只要文档中有一个polygon与查询的多边形相交,则这条doc就会被查询出来
3)当进行disjoint查询时,只有文档中的所有polygon都不与查询多边形相交,这条doc才会被查询出来。