avatar


ElasticSearch和PostgreSQL中的地理坐标

ElasticSearch

数据类型

数据类型:geo_point

插入数据

插入数据有两种方式:

  1. 指明经纬度
  2. 纬度在前,经度在后,逗号分隔

指明经纬度

1
2
3
4
5
6
7
8
9
POST ent_info/_doc/91320925140612674L/_update
{
"doc": {
"location": {
"lat": 39.456789,
"lon": 67.456788
}
}
}
  • lat:纬度,latitude,对应X轴。
  • lon:经度,longitude,对应Y轴。

纬度在前,经度在后,逗号分隔

1
2
3
4
5
6
POST ent_info/_doc/91371102MA3F2BJ40Q/_update
{
"doc": {
"location": "39.456789,67.456788"
}
}

这两种方式,都可以插入数据,需要注意的是,这两种方式插入的数据,查询出来的内容不一样。

查询结果

对于指明经纬度的,查出来的也是指明了经纬度的。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
GET ent_info/_doc/_search
{
"query":{
"term": {
"id": {
"value": "91320925140612674L"
}
}
},
"_source": ["ent_name","location"]
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 10.778748,
"hits": [
{
"_index": "ent_info",
"_type": "_doc",
"_id": "91320925140612674L",
"_score": 10.778748,
"_source": {
"location": {
"lon": 67.456788,
"lat": 39.456789
},
"ent_name": "建湖县园林建设工程有限公司"
}
}
]
}
}

没有指明经纬度的,查出来也是没有指明经纬度。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
GET ent_info/_doc/_search
{
"query":{
"term": {
"id": {
"value": "91371102MA3F2BJ40Q"
}
}
},
"_source": ["ent_name","location"]
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 10.778748,
"hits": [
{
"_index": "ent_info",
"_type": "_doc",
"_id": "91371102MA3F2BJ40Q",
"_score": 10.778748,
"_source": {
"location": "39.456789,67.456788",
"ent_name": "山东鼎泰价格评估有限公司"
}
}
]
}
}

个人更推荐第一种方式,指明经纬度,这种方式更清晰。

范围查询

查询语句

以某点为圆心,周围一米:

1
2
3
4
5
6
7
8
9
10
11
12
GET ent_info/_doc/_search
{
"query": {
"geo_distance": {
"location": {
"lon":67.456788,
"lat":39.456789
},
"distance": 1
}
}
}

以某点为圆心,周围一公里:

1
2
3
4
5
6
7
8
9
10
11
12
GET ent_info/_doc/_search
{
"query": {
"geo_distance": {
"location": [
67.456788,
39.456789
],
"distance": 1000
}
}
}

Java实现

以某点为圆心,周围一米:

1
2
3
QueryBuilders.geoDistanceQuery("location")
.distance(geoDistance, DistanceUnit.METERS)
.point(geoLat,geoLon)

以某点为圆心,周围一公里:

1
2
3
QueryBuilders.geoDistanceQuery("location")
.distance(geoDistance, DistanceUnit.KILOMETERS)
.point(geoLat, geoLon)

解释说明:

  • location:地理坐标字段
  • geoDistance:距离
  • geoLat:纬度
  • geoLon:经度
  • DistanceUnit.METERSDistanceUnit.KILOMETERS,距离的单位。
    在Java实现中,我们可以指定单位。但是在查询语句中,不能指定单位。

排序

一般排序场景只有一种,以与圆心的距离,从近到远,进行排序。

查询语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GET ent_info/_doc/_search
{
"query": {
"geo_distance": {
"location": {
"lon":121.63024,
"lat":31.25322
},
"distance": 5000
}
},
"sort": [
{
"_geo_distance": {
"location": [
{
"lat": 31.25322,
"lon": 121.63024
}
]
}
}
]
}

Java实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
QueryBuilders.geoDistanceQuery("location")
.distance(geoDistance, DistanceUnit.KILOMETERS)
.point(geoLat, geoLon);

// 排序
GeoDistanceSortBuilder sort = SortBuilders.geoDistanceSort("location", geoLat,geoLon);

// 添加排序
SearchRequestBuilder searchRequestBuilder = client.prepareSearch("ent_info")
.setTypes("_doc")
.setQuery(query)
.addSort(sort);

SearchResponse searchResponse = searchRequestBuilder.get();

解释说明:

  • location:地理坐标字段
  • geoDistance:距离
  • geoLat:纬度
  • geoLon:经度

多边形查询

多边形查询:画一个多边形,查询在多边形范围内的。

查询语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
GET ent_info/_doc/_search
{
"query": {
"geo_polygon": {
"location": {
"points": [
{
"lat": 31.27173508703935,
"lon": 121.61240677660302
},
{
"lat": 31.24841664295535,
"lon": 121.64701484913262
},
{
"lat": 31.239087636484996,
"lon": 121.63344305598375
},
{
"lat": 31.242741775632748,
"lon": 121.6387213002787
}
]
}
}
}
}

Java实现

1
2
3
4
5
6
7
GeoPoint gp1 = new GeoPoint(gpLat_1, gpLon_1);
geoPointList.add(gp1);
GeoPoint gp2 = new GeoPoint(gpLat_2, gpLon_2);
geoPointList.add(gp2);
GeoPoint gp3 = new GeoPoint(gpLat_3, gpLon_3);
geoPointList.add(gp3);
QueryBuilders.geoPolygonQuery("location", geoPointList)
  • gpLat_1:多边形的第一个点的纬度
  • gpLon_1:多边形的第一个点的经度
  • gpLat_2gpLon_2:多边形的第二个点的纬度、经度
  • gpLat_3gpLon_3:多边形的第三个点的纬度、经度

关于ElasticSearch的更多内容,可以参考在《ElasticSearch实战入门(6.X)》的讨论。

PostgreSQL

数据类型

在PostgreSQL中,没有专门的地理坐标的数据类型,可以考虑采用 point 这个数据类型。

插入数据

1
2
insert into ent_info (location)
values ('(116.309339,40.040582)')

范围查询

在PostgreSQL下,没有直接可以用的地理坐标方法。
同时,鉴于,不同经度下,一单位的纬度,对应的长度(米)是不一样的,比如,赤道和南北极,差距会很大。
所以,需要进行额外运算。
示例代码:

1
2
3
4
5
6
7
8
9
SELECT *
FROM ent_info
acos(
sin(radians(location[1]))
* sin(radians(${lat}))
+ cos(radians(location[1]))
* cos(radians(${lat}))
* cos(radians(location[0]) - radians(${lon}))
) * 6371 <= ${distance}
  • location[1]:被查询字段的纬度
  • location[0]:被查询字段的经度
  • ${lat}:入参,纬度
  • ${lon}:入参,经度
  • ${distance}:入参,距离

那么,怎么排序呢?
上文的一串三角函数,就是在计算与圆心的距离。

PostgreSQLMySQL,同属于关系型数据库,整体语法差别不大。
关于PostgreSQL的一些语法,可以参考:

文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/19902
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区