用postgreSQL计算距离

  |  

选择–Cube and EarthDistance 架构

CREATE EXTENSION cube;
CREATE EXTENSION earthdistance;

基于立方体的地球距离函数

文档
在这里插入图片描述

例子

  1. 计算 2 个坐标之间的距离
1
2
3
SELECT events.id events.name, eaerthdiatance(ll_to_earth({currentuserlat}, {currentuserlng}), llto_earth(events.lat, events.lng))
as distancefromcurrentlocation FROM events
ORDER BY distancefromcurretnlocation ASC;
  1. 找到某个半径范围内的记录
1
2
SELECT events.id, events.name FROM events
WHERE earth_box({currentuserlat}, {currentuserlng}, {radiusinmetres}) @> ll_to_earth(events.lat, events.lng);

提高查询速度

你可能会发现上面的查询有不小的开销。最好对一些字段建立索引。 (下面这条语句假定你又 events 表, 同时 events 表有字段 lat 和 lng)

1
CREATE INDEX ${nameofindex} on events USING gits(lltoearth(lat, lng));

以下代码来源:https://blog.csdn.net/wusuopubupt/article/details/21621477

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
/*
* postgreSQL之earthdistance学习笔记
* author: wusuopubupt
* date: 2013-03-31
*/

/*创建表*/
CREATE TABLE picture (
id serial PRIMARY KEY ,
p_uid char(12) NOT NULL,
p_key char(23) NOT NULL,
lat real not null,
lng real NOT NULL,
up int NOT NULL,
down int NOT NULL,
ip varchar(15) DEFAULT NULL,
address varchar(256) DEFAULT NULL
);

/*插入记录*/
INSERT INTO picture(p_uid, p_key, lat, lng, up, down, ip, address)
VALUES('aaaabbbbcccc', '2014032008164023279.png', 40.043945, 116.413668, 0, 0, '', '');

/*插入记录*/
INSERT INTO picture(p_uid, p_key, lat, lng, up, down, ip, address)
VALUES('xxxxccccmmmm', '2014032008164023111.png', 40.067183, 116.415230, 0, 0, '', '');

/*选择记录*/
SELECT * FROM picture;

/*更新记录*/
UPDATE picture SET address='LiShuiqiao' WHERE id=1;
UPDATE picture SET address='TianTongyuan' WHERE id=2;

/*对经纬度列创建索引*/
CREATE INDEX ll_idx on picture USING gist(ll_to_earth(lat, lng));

/*根据半径(1000米)选择记录*/
SELECT * FROM picture where earth_box(ll_to_earth(40.059286,116.418773),1000) @> ll_to_earth(picture.lat, picture.lng);

/*选择距离当前用户的距离*/
SELECT picture.id, earth_distance(ll_to_earth(picture.lat, picture.lng), ll_to_earth(40.059286,116.418773))
AS dis FROM picture
ORDER BY dis ASC;

/*
* 以下内容是网上的一篇教程
* 地址:http://www.cse.iitb.ac.in/dbms/Data/Courses/CS631/PostgreSQL-Resources/postgresql-9.2.4/contrib/earthdistance/expected/earthdistance.out
*/
--
-- Test earthdistance extension
--
-- In this file we also do some testing of extension create/drop scenarios.
-- That's really exercising the core database's dependency logic, so ideally
-- we'd do it in the core regression tests, but we can't for lack of suitable
-- guaranteed-available extensions. earthdistance is a good test case because
-- it has a dependency on the cube extension.
--
CREATE EXTENSION earthdistance; -- fail, must install cube first
ERROR: required extension "cube" is not installed
CREATE EXTENSION cube;
CREATE EXTENSION earthdistance;
--
-- The radius of the Earth we are using.
--
SELECT earth()::numeric(20,5);
earth
---------------
6378168.00000
(1 row)

--
-- Convert straight line distances to great circle distances.把直线距离转成大圆距离
--
SELECT (pi()*earth())::numeric(20,5);
numeric
----------------
20037605.73216
(1 row)

SELECT sec_to_gc(0)::numeric(20,5);
sec_to_gc
-----------
0.00000
(1 row)


--
-- Convert great circle distances to straight line distances.
--
SELECT gc_to_sec(0)::numeric(20,5);
gc_to_sec
-----------
0.00000
(1 row)

SELECT gc_to_sec(sec_to_gc(2*earth()))::numeric(20,5);
gc_to_sec
----------------
12756336.00000
(1 row)


--
-- Set coordinates using latitude and longitude.
-- Extract each coordinate separately so we can round them.
--
SELECT cube_ll_coord(ll_to_earth(0,0),1)::numeric(20,5),
cube_ll_coord(ll_to_earth(0,0),2)::numeric(20,5),
cube_ll_coord(ll_to_earth(0,0),3)::numeric(20,5);
cube_ll_coord | cube_ll_coord | cube_ll_coord
---------------+---------------+---------------
6378168.00000 | 0.00000 | 0.00000
(1 row)

SELECT cube_ll_coord(ll_to_earth(360,360),1)::numeric(20,5),
cube_ll_coord(ll_to_earth(360,360),2)::numeric(20,5),
cube_ll_coord(ll_to_earth(360,360),3)::numeric(20,5);
cube_ll_coord | cube_ll_coord | cube_ll_coord
---------------+---------------+---------------
6378168.00000 | 0.00000 | 0.00000
(1 row)


--
-- Test getting the latitude of a location.
--
SELECT latitude(ll_to_earth(0,0))::numeric(20,10);
latitude
--------------
0.0000000000
(1 row)

SELECT latitude(ll_to_earth(45,0))::numeric(20,10);
latitude
---------------
45.0000000000
(1 row)

--
-- Test getting the longitude of a location.
--
SELECT longitude(ll_to_earth(0,0))::numeric(20,10);
longitude
--------------
0.0000000000
(1 row)

SELECT longitude(ll_to_earth(45,0))::numeric(20,10);
longitude
--------------
0.0000000000
(1 row)


--
-- For the distance tests the following is some real life data.
--
-- Chicago has a latitude of 41.8 and a longitude of 87.6.
-- Albuquerque has a latitude of 35.1 and a longitude of 106.7.
-- (Note that latitude and longitude are specified differently
-- in the cube based functions than for the point based functions.)
--
--
-- Test getting the distance between two points using earth_distance.
--
SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,0))::numeric(20,5);
earth_distance
----------------
0.00000
(1 row)

SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,180))::numeric(20,5);
earth_distance
----------------
20037605.73216
(1 row)

--
-- Test getting the distance between two points using geo_distance.
--
SELECT geo_distance('(0,0)'::point,'(0,0)'::point)::numeric(20,5);
geo_distance
--------------
0.00000
(1 row)

SELECT geo_distance('(0,0)'::point,'(180,0)'::point)::numeric(20,5);
geo_distance
--------------
12436.77274
(1 row)


--
-- Test getting the distance between two points using the <@> operator.
--
SELECT ('(0,0)'::point <@> '(0,0)'::point)::numeric(20,5);
numeric
---------
0.00000
(1 row)

SELECT ('(0,0)'::point <@> '(180,0)'::point)::numeric(20,5);
numeric
-------------
12436.77274
(1 row)


--
-- Test for points that should be in bounding boxes.
--
SELECT earth_box(ll_to_earth(0,0),
earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*1.00001) @>
ll_to_earth(0,1);
?column?
----------
t
(1 row)

SELECT earth_box(ll_to_earth(0,0),
earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*1.00001) @>
ll_to_earth(0,0.1);
?column?
----------
t
(1 row)


--
-- Test for points that shouldn't be in bounding boxes. Note that we need
-- to make points way outside, since some points close may be in the box
-- but further away than the distance we are testing.
--
SELECT earth_box(ll_to_earth(0,0),
earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*.57735) @>
ll_to_earth(0,1);
?column?
----------
f
(1 row)

SELECT earth_box(ll_to_earth(0,0),
earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*.57735) @>
ll_to_earth(0,0.1);
?column?
----------
f
(1 row)

在 sequelize 中的使用实例

1
2
3
4
5
6
7
8
9
10
 let where = `("orgs"."deletedAt" > now() OR "orgs"."deletedAt" IS NULL)  AND  "orgs"."id" IN (${org_ids}) AND "orgs"."ncb" = true  `;

let teacherOrgList = await ctx.Models.query(`
SELECT "id", "hash", "logo", "city", "province", "area", "name", "address","address_lat", "address_lng", earth_distance(ll_to_earth(orgs.address_lat, orgs.address_lng), ll_to_earth(${lat},${lng})) AS dis
FROM orgs
WHERE ${where}
`, {
'raw': true,
'type': 'SELECT',
});

文档:
PostgreSQL 10.1 手册

文章目录
  1. 1. 选择–Cube and EarthDistance 架构
  2. 2. 基于立方体的地球距离函数
  3. 3. 例子
  4. 4. 提高查询速度
  5. 5. 在 sequelize 中的使用实例