1.点与图形
地理位置信息,对于这个功能,目前大多数的中间件基本都已经开始支持,比如MongoDB中就提供了2d和2dsphere索引,redis中提供了geo相关的指令集合。
同样,es中也提供了地理位置相关的功能,包括以下两种数据类型
- geo_point:以经纬度形式进行存储,用于存储地理位置坐标点,常用与计算坐标点之间的距离以及点聚合等相关功能
- geo_shape:以geojson形式进行存储,用于存储地理位置点、线、图形等。用于计算图形之间的交集以及图形中包含了哪些点等等
2.索引点
geo_point其实相比较于geo_shape更为常用,且用起来更为简单,接下来开始讲解geo_point的使用
建立索引
建立索引时,指定数据类型为geo_point即可。
下面以省份坐标为例,指定location数据类型为geo_point
PUT omission
{
"mappings": {
"properties": {
"name":{
"type": "text",
"analyzer": "ik_max_word"
},
"location":{
"type": "geo_point"
}
}
}
}
存储数据
样例数据准备
接下来准备6个省份的坐标信息,如下图
- 北京:(经度:116.20,纬度:39.56)
- 山东:(经度:114.19,纬度:34.22)
- 湖北:(经度:108.21,纬度:29.01)
- 湖南:(经度:108.47,纬度:24.38)
- 云南:(经度:97.31,纬度:21.8)
-
数据存储格式
es中对于geo_point的数据支持三种数据格式,如下
字符串形式:以逗号分割,纬度在前,经度在后。示例: “location”: “39.56, 116.20”
- Json形式:指定经纬度即可,lat代表纬度,lon代表经度。示例:{“location”:{“lat”:114.19,”lon”:34.22}}
- 数组形式:经度在前,纬度在后。示例: “location”: [108.21,29.01]
一定要注意的是,字符串类型纬度在前,经度在后。数组类型经度在前,纬度在后
接下来将上述准备的省份坐标录入索引
PUT omission/_bulk
{"create":{}}
{"name":"北京","location":"39.56, 116.20"}
{"create":{}}
{"name":"山东","location":"34.22, 114.19"}
{"create":{}}
{"name":"湖北","location":{"lat":29.01,"lon":108.21}}
{"create":{}}
{"name":"湖南","location":{"lat":24.38,"lon":108.47}}
{"create":{}}
{"name":"云南","location":[97.31,21.8]}
{"create":{}}
{"name":"新疆","location":[86.37,42.45]}
3.查询点
es中提供了多种对于地理位置信息查询的方法,最常用的包括以下两种
- geo_bounding_box:给出矩形的左上和右下点坐标,获取矩形内的点
- geo_distance:给出一个点以及最远距离,获取距离圆心点距离在最远距离以内的所有点
geo_bounding_box
命令格式
geo_bounding_box命令格式如下
GET omission/_search
{
"query": {
"constant_score": {
"filter": {
"geo_bounding_box": {
"FIELD": {
"top_left": {
"lat": 40.73,
"lon": -74.1
},
"bottom_right": {
"lat": 40.717,
"lon": -73.99
}
}
}
}
}
}
}
- FIELD:查询字段
- top_left:左上角经纬度坐标
-
示例
以宁夏为左上角,台湾为右下角,可以获取到山东,湖南,湖北的相关信息
宁夏坐标点:[104.17,35.14]
台湾坐标点:[119.18,20.45]
因此查询表达式如下:GET omission/_search
{
"query": {
"constant_score": {
"filter": {
"geo_bounding_box": {
"location": {
"top_left": {
"lat": 35.14,
"lon": 104.17
},
"bottom_right": {
"lat": 20.45,
"lon": 119.18
}
}
}
}
}
}
}
geo_distance
命令格式
GET omission/_search
{
"query": {
"constant_score": {
"filter": {
"geo_distance": {
"distance": "100km",
"distance_type": "arc",
"FIELD": {
"lat": 40.73,
"lon": -74.1
}
}
}
}
}
}
FIELD:查询字段以及指定圆心点的经纬度坐标
distance:指定的最远距离,其中距离支持如下单位 :::success
英里:mi/miles
- 码:yd/yards
- 英尺:ft/feet
- 英寸:in/inch
- 公里(千米):km/kilometers
- 米:m/meters
- 厘米:cm/centimeters
- 毫米:mm/millimeters
海里:NM/nmi/nauticalmiles :::
distance_type:距离计算方式,包括arc和plane两种 :::success
arc:基于球面运算,效率较低但精准度较高。当然也不是完全准确,毕竟地球不是一个圆滑的球
plane:基于平面运算,效率较高但精度较低。在赤道附近的位置精度最好,而靠近两极则变差。 :::
示例
已知北京距离其他省份距离信息,从小到大排序如下
北京-山东距离:411.5km
- 北京-湖北距离:1112.3km
- 北京-湖南距离:1342km
- 北京-云南距离:2200km
- 北京-新疆距离:2768.5km
以北京为圆心,获取2000km内的省份,理论上应该返回北京,山东,湖北,以及湖南4个省份
GET omission/_search
{
"query": {
"constant_score": {
"filter": {
"geo_distance": {
"distance": "2000km",
"location": {
"lat": 39.56,
"lon": 116.2
}
}
}
}
}
}
排序
有的时候,我们不仅仅想要获取响应的坐标点,我们还想要结果根据一定的顺序排序,根据地理位置排序与其他字段排序不同。es中同样提供了根据地理位置排序的功能。同时,基于地理位置排序,es还会返回与期望圆心点之间的距离
命令格式
"sort": [
{
"_geo_distance": {
"FIELD": {
"lat": 40,
"lon": -70
},
"order": "asc",
"unit": "km",
"distance_type": "arc"
}
}
]
- _geo_distance:固定写法枚举值,表明按照坐标点排序
- FIELD:排序字段以及中心点经纬度
- order:正序还是倒序
- unit:距离单位
distance_type:距离计算方式,包括arc和plane两种
示例
还是以北京为圆心,获取2000km内的省份,理论上应该返回北京,山东,湖北,以及湖南4个省份以及其与北京的距离
GET omission/_search
{
"query": {
"constant_score": {
"filter": {
"geo_distance": {
"distance": "2000km",
"location": {
"lat": 39.56,
"lon": 116.2
}
}
}
}
},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 39.56,
"lon": 116.2
},
"order": "desc",
"unit": "km",
"distance_type": "plane"
}
}
]
}
如图,返回结果通过与北京的距离倒序排序,并且sort字段中返回了对应的距离注意
地理坐标过滤器使用代价昂贵 ,所以最好在文档集合尽可能少的场景下使用。
可以先使用那些简单快捷的过滤器,比如 term 或 range ,来过滤掉尽可能多的文档,最后才交给地理坐标过滤器处理。
布尔型过滤器 boolfilter 很适合做这件事。它会优先让那些基于“bitset”的简单过滤器(见 关于缓存 )来过滤掉尽可能多的文档,然后依次才是更昂贵的地理坐标过滤器或者脚本类的过滤器。4.GeoHash
什么是geohash
众所周知,对于地理位置信息,常用的保存格式就是经纬度数组以及geojson。那么有没有这样一种数据结构,可以通过一维的方式来表示地理位置的二维坐标呢?答案是,有
geohash就是这样一种数据,它通过二分,二进制解析,二进制转10进制以及base32等步骤,将一个经纬度坐标,转成了wx4g0e这样的字符串。而这种类型的字符串,也就是geohash的格式了。通常来说,字符串越长,代表精确度越高
Geohashes 是一种将经纬度坐标( lat/lon )编码成字符串的方式。这么做的初衷只是为了让地理位置在 url 上呈现的形式更加友好,但现在 geohashes 已经变成一种在数据库中有效索引地理坐标点和地理形状的方式。
Geohashes 把整个世界分为 32 个单元的格子 —— 4 行 8 列
换句话说, geohash 的长度越长,它的精度就越高。如果两个 geohashes 有一个共同的前缀— gcpuuz—就表示他们挨得很近。共同的前缀越长,距离就越近。
这也意味着,两个刚好相邻的位置,可能会有完全不同的 geohash 。比如,伦敦 Millenium Dome 的 geohash 是 u10hbp ,因为它落在了 u 这个单元里,而紧挨着它东边的最大的单元是 g 。geohash的算法
首先要明确知道的两点
经度的范围:-180~180
- 纬度的范围:-90~90
接下来开始演示,如何讲一个经纬度坐标转化为一个geohash。
计算经度:最终2进制数=11010(PS:正常来说geoHash长度=4要算10次,时间宝贵,这里只做演示)
0区间 | 1区间 | 坐标对应区间 | 2进制值 | 坐标 |
---|---|---|---|---|
-180,0 | 0,180 | 1区间 | 1 | 116.20 |
0,90 | 90,180 | 1区间 | 1 | 116.20 |
90,135 | 135,180 | 0区间 | 0 | 116.20 |
90,112.5 | 112.5,135 | 1区间 | 1 | 116.20 |
112.5,123,75 | 123,75,135 | 0区间 | 0 | 116.20 |
计算纬度:最终2进制数=10111(PS:正常来说geoHash长度=4要算10次,时间宝贵,这里只做演示)
0区间 | 1区间 | 坐标对应区间 | 2进制值 | 坐标 |
---|---|---|---|---|
-90,0 | 0,90 | 1区间 | 1 | 39.56 |
0,45 | 45,90 | 0区间 | 0 | 39.56 |
0,22.5 | 22.5,45 | 1区间 | 1 | 39.56 |
22.5,33,75 | 33.75,45 | 1区间 | 1 | 39.56 |
33.75.39.375 | 39.375,45 | 1区间 | 1 | 39.56 |
经纬度2进制数整合
整合原则:奇数位为纬度,偶数位为经度
因此上述经纬度组合后:1101101110
2进制数转10进制
转化原则:5个一组,转为10进制。(PS:为什么是5个一组?因为最后一步要计算base32对应,因此最大数为31,也就是5位2进制数)
因此上述2进制转换后:27,14
base32计算10进制数
对照表如下:
因此上述10进制转换后:vf
最终计算完成,北京:(经度:116.20,纬度:39.56)转换为geohash后变为vf
上图说话
可能上述的算法还不是很直观,接下来用图解来形象的描述geohash的算法
如图
- 将世界地图的一部分按照4 行 8 列划分成了32块,将设左上角第一块标注为a
- 此时对于左上角第一块再进行4 行 8 列划分,再取其左上角第一块,标注为b
- ……依次递归,经度越高,划分的块越小。最终拼接得到的字符串越长,经度就越高
- 假设每一个被划分的块都有对应层级的字母标识,那么最终就可以通过趋近于无限的划分,将整个地图,用无数个像素点描绘出来
- 同理,任何一个位置,都可以用足够长的字符串标识出来
-
Geohash经度对照表
可以看出,当geohash长度为9时,可以将经度细化到5米左右,也就是每个像素块的面积为5m*5m5.聚合点
经过了geohash的学习,接下来就可以了解es中提供的地理位置聚合了。es中共提供了以下几种聚合方法
geo_distance(地理位置距离):将文档按照距离围绕一个中心点来分组聚合
- geohash_grid(网格聚合):将文档按照 geohash 范围来分组聚合,用来显示在地图上
- geo_bounds(地理位置边界):返回一个包含所有坐标点的边界的矩形左上右下经纬度坐标,这对显示地图时缩放比例的选择非常有用。
geo_distance(地理位置距离)
给定圆心和聚合范围集合,可以统计出距离圆心递进距离区间内的点的数量。当然可以结合递归聚合做任何事情
命令格式
GET omission/_search
{
"aggs": {
"geo_distance_example": {
"geo_distance": {
"field": "location",
"origin": {
"lat": 39.56,
"lon": 116.20
},
"ranges": [
{
"from": 0,
"to": 1000
},
{
"from": 1000,
"to": 2000
}
],
"unit": "km",
"distance_type": "plane"
}
}
}
}
- field:聚合字段、
- origin:圆心点经纬度坐标
- ranges:统计范围,例如示例中,统计了0~1000km范围、1000km到2000km范围
- unit:范围单位
- distance_type:距离计算方式,包括arc和plane两种
示例
统计距离北京(包括北京)0~1000km范围、1000km到2000km范围内的省市 ```json GET omission/_search { “aggs”: { “geo_distance_example”: { “geo_distance”: {
} } } }"field": "location",
"origin": {
"lat": 39.56,
"lon": 116.20
},
"ranges": [
{
"from": 0,
"to": 1000
},
{
"from": 1000,
"to": 2000
}
],
"unit": "km",
"distance_type": "plane"
![image.png](https://cdn.nlark.com/yuque/0/2022/png/28218714/1660728296544-29a6baf3-bf5b-4cc1-bcc9-f79e0a157f78.png#clientId=u42b9ee89-1f79-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=689&id=uc5666a40&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1033&originWidth=1584&originalType=binary&ratio=1&rotation=0&showTitle=false&size=173247&status=done&style=none&taskId=u1710bff0-f0db-405d-b7e1-75df1ea0269&title=&width=1056)<br />如图,0~1000km范围内有两个城市:北京,山东。1000~2000km内有两个城市:湖北,湖南
<a name="dHP1p"></a>
### geohash_grid(网格聚合)
通过geohash,将相同像素块内的坐标点聚合。聚合结果的key通过geohash来表示
<a name="Po7hE"></a>
#### 命令格式
```json
GET omission/_search
{
"aggs": {
"geohash_grid_example":{
"geohash_grid": {
"field": "location",
"precision": 1
}
}
}
}
- field:聚合字段
precision:geohash字符串长度,长度越长,精度越高
示例
网格聚合,指定geohash长度为1
GET omission/_search
{
"aggs": {
"geohash_grid_example":{
"geohash_grid": {
"field": "location",
"precision": 1
}
}
}
}
可以看到,聚合结果为2个像素块,w包含5个点,t包含1个点
此时我们扩大精确度为4
可以看到因为精度提高,每个像素块的面积变小,因此结果更为精细,聚合结果为6个像素块。geo_bounds(地理位置边界)
有时候,我们需要一个范围,包括所有存储的点,而且这个范围越小越好,越小经度越高。
es中提供了geo_bounds来计算出一个矩形地理位置边界,聚合结果会返回矩形的左上和右下坐标命令格式
GET omission/_search
{
"aggs": {
"geo_bound_example":{
"geo_bounds": {
"field": "location"
}
}
}
}
-
示例
得到一个可以包含所有已存储数据的矩形
GET omission/_search
{
"aggs": {
"geo_bound_example":{
"geo_bounds": {
"field": "location"
}
}
}
}
6.索引图形
es中使用geo_shape来存储一个地理位置图形,在有些场景下,我们可以利用这个功能,计算路线的交叉点,图形中包含了哪些点等信息
geo-shape 不能用于计算距离、排序、打分以及聚合。建立索引
建立索引时指定字段类型为geo_shape即可,如下示例:
PUT shape_index
{
"mappings": {
"properties": {
"name":{
"type": "text",
"analyzer": "ik_max_word"
},
"location":{
"type": "geo_shape"
}
}
}
}
存储数据
geo_shape数据类型支持的数据结构为geoJson,其中geo_json的基础数据类型为
{
"type":"Point",
"coordinates":[
100,
0
]
}
type:geojson类型 | Elasticsearch 类型 | 说明 | | —- | —- | | point | 一个单独的经纬度坐标点 | | linestring | 任意的线条,由两到多个点组成 | | polygon | 由 N+1 个点组成的封闭 N 边形 | | multipoint | 一组不连续但有可能相关联的点 | | multilinestring | 多条不关联的线 | | multipolygon | 多个不关联的多边形 | | geometrycollection | 几何对象的集合 | | envelop | 由左上角坐标或右下角坐标确定的封闭矩形 | | circle | 由圆心和半径确定的圆,默认单位为米 |
coordinates:坐标,不同类型对应的坐标组合也不同
参考下图录入数据
录入北京坐标点信息
PUT /shape_index/_doc/bei_jing_point
{
"name" : "北京",
"location" : {
"type" : "point",
"coordinates" : [116.20,39.56]
}
}
录入第一个三角形信息,包括三个点,需要注意的是,多边形必须闭合
- 甘肃:92.13,32.31
- 黑龙江:125.03,50.49
台湾:119.18,20.45
PUT /shape_index/_doc/ght_triangle
{
"name": "甘肃黑龙江台湾三角形",
"location": {
"type": "polygon",
"coordinates": [
[
[
92.13,
32.31
],
[
125.03,
50.49
],
[
119.18,
20.45
],
[
92.13,
32.31
]
]
]
}
}
录入第二个三角形信息,包括三个点,需要注意的是,多边形必须闭合
宁夏:105.49,38.08
- 新疆:96.37,42.45
西藏:80.24,31.29
PUT /shape_index/_doc/nxx_triangle
{
"name": "宁夏新疆西藏三角形",
"location": {
"type": "polygon",
"coordinates": [
[
[
105.49,
38.08
],
[
96.37,
42.45
],
[
80.24,
31.29
],
[
105.49,
38.08
]
]
]
}
}
7.查询形状
不同形状查询
es中通过geo_shape命令来查询地理位置图形,可以用来查询重叠图形,完全不重叠图形,完全包含图形
命令格式
GET shape_index/_search
{
"query": {
"geo_shape": {
"location": {
"shape": {
"type": "polygon",
"coordinates": [
[
[
105.49,
38.08
],
[
96.37,
42.45
],
[
80.24,
31.29
],
[
105.49,
38.08
]
]
]
},
"relation": "disjoint"
}
}
}
}
geo_shape:枚举类型固定
- location:查询字段名称
- shape:枚举类型固定,里面的图形符合geojson即可
relation:图形关系,分为以下三种 :::success
intersects:(默认)查询的形状与索引的形状有重叠。
- disjoint:查询的形状与索引的形状完全不重叠。
within:索引的形状完全被包含在查询的形状中。不支持直线几何图形。 :::
示例
查询甘肃黑龙江台湾三角形完全包含的,理论上返回北京点
GET shape_index/_search
{
"query": {
"geo_shape": {
"location": {
"shape": {
"type": "polygon",
"coordinates": [
[
[
92.13,
32.31
],
[
125.03,
50.49
],
[
119.18,
20.45
],
[
92.13,
32.31
]
]
]
},
"relation": "within"
}
}
}
}
如图,完全包含北京,与预期结果一致查询甘肃黑龙江台湾三角形有重叠的,理论上返回北京点,甘肃黑龙江台湾三角形,宁夏新疆西藏三角形
GET shape_index/_search
{
"query": {
"geo_shape": {
"location": {
"shape": {
"type": "polygon",
"coordinates": [
[
[
92.13,
32.31
],
[
125.03,
50.49
],
[
119.18,
20.45
],
[
92.13,
32.31
]
]
]
},
"relation": "intersects"
}
}
}
}
如图,全部返回,与预期结果一致查询宁夏新疆西藏三角形完全不包含的,理论上返回北京点
GET shape_index/_search
{
"query": {
"geo_shape": {
"location": {
"shape": {
"type" : "polygon",
"coordinates" : [
[
[
105.49,
38.08
],
[
96.37,
42.45
],
[
80.24,
31.29
],
[
105.49,
38.08
]
]
]
},
"relation": "disjoint"
}
}
}
}
使用已经存储的图形
命令格式
GET shape_index/_search
{
"query": {
"geo_shape": {
"location": {
"relation": "within",
"indexed_shape": {
"index": "shape_index",
"id": "ght_triangle",
"path": "location"
}
}
}
}
}
location:查询字段
- relation:图形关系
- indexed_shape:枚举类型固定字段
- index:索引
- id:文档ID
- path:文档字段
示例
查询甘肃黑龙江台湾三角形完全包含的点,理论上返回北京点GET shape_index/_search
{
"query": {
"geo_shape": {
"location": {
"relation": "within",
"indexed_shape": {
"index": "shape_index",
"id": "ght_triangle",
"path": "location"
}
}
}
}
}
如图,返回结果与预期结果一致