用postgreSQL做基於地理位置的app(zz)


前言:項目中用到了postgreSQL中的earthdistance()函數功能計算地球上兩點之間的距離,中文的資料太少了,我找到了一篇英文的、講的很好的文章,特此翻譯,希望能夠幫助到以后用到earthdistance的同學。

一、兩種可用的選擇
當我們想用Postgres作為GEO函數使用時,我們通常有2中選擇(據我所知):

1.PostGIS: 為postgreSQL提供了高級GEO函數功能。我用了它一段時間,但是它對於我的需求來說太笨重了。
2.Cube和Earthdistance: 這兩個拓展為輕量級的Geo關系實體提供了簡單、快速的實現方法。


二、為什么在數據庫服務器端做計算
這是件非常明顯的事。服務器存儲了所有的數據,服務器拓展是用C/C++實現的,非常快。為數據表做索引也能加快計算速度。

三、使用我的選擇--Cube and EarthDistance
作為開始,你應該先建一個數據庫(我想你知道該怎么做),然后使它們能用我們的架構。 執行:

[sql]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
  1. CREATE EXTENSION cube;  
  2. CREATE EXTENSION earthdistance;  
上面的命令創建了大約40個函數,以后我們做數據查詢的時候就可以用了。
在我們的例子中,我創建了名為events的表,字段有:id(serial), name(varchar 255), lat(double), lng(double)。(別忘了~~)

四、計算2個坐標之間的距離

計算2個坐標之間的距離,我們要用到earth_distance(ll_to_earth($latlngcube), ll_to_earth($latlng_cube))這個函數。 earth_distance()函數接受2組坐標值,返回值一個以米為單位的的數值。這能用於很多場景,比如根據某一位置找到離其最近的發生的新聞事件的列表。


【譯者注】這里要提下幾個重要的函數:(參考:http://www.postgresql.org/docs/8.3/static/earthdistance.html

Table F-3. Cube-based earthdistance functions

Function Returns Description
earth() float8 Returns the assumed radius of the Earth.
sec_to_gc(float8) float8 Converts the normal straight line (secant) distance between between two points on the surface of the Earth to the great circle distance between them.
gc_to_sec(float8) float8 Converts the great circle distance between two points on the surface of the Earth to the normal straight line (secant) distance between them.
ll_to_earth(float8, float8) earth Returns the location of a point on the surface of the Earth given its latitude (argument 1) and longitude (argument 2) in degrees.
latitude(earth) float8 Returns the latitude in degrees of a point on the surface of the Earth.
longitude(earth) float8 Returns the longitude in degrees of a point on the surface of the Earth.
earth_distance(earth, earth) float8 Returns the great circle distance between two points on the surface of the Earth.
earth_box(earth, float8) cube Returns a box suitable for an indexed search using the cube @> operator for points within a given great circle distance of a location. Some points in this box are further than the specified great circle distance from the location, so a second check using earth_distance should be included in the query.

數據庫的操作可能就像下面這樣:

[sql]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
  1. SELECT events.id events.name, eaerthdiatance(ll_to_earth({currentuserlat}, {currentuserlng}), llto_earth(events.lat, events.lng))   
  2. as distancefromcurrentlocation FROM events   
  3. ORDER BY distancefromcurretnlocation ASC;  

這將給我們一個很nice的新聞事件列表,按他們的離我們當前位置的距離由近到遠排序。第一個是離我們最近的。


五、找到某個半徑范圍內的記錄

Cube和Earthdiatance拓展提供的另一個偉大的函數是earth_box(ll_to_earch($latlngcub), $radiusinmetres)。 這個函數通過簡單的比較就能到找到某個半徑范圍內的所有記錄。它是靠返回2點之間的“大圓距離”實現的。


【譯者注】大圓距離(Great circle disstance)指的是從球面的一點A出發到達球面上另一點B,所經過的最短路徑的長度。一般說來,球面上任意兩點A和B都可以與球心確定唯一的大圓,這個大圓被稱為黎曼圓,而在大圓上連接這兩點的較短的一條弧的長度就是大圓距離。如果想了解更多,請看wiki:大圓距離

它能用於查詢我們城市中所有的新聞事件:

[sql]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
  1. SELECT events.id, events.name FROM events   
  2. WHERE earth_box({currentuserlat}, {currentuserlng}, {radiusinmetres}) @> ll_to_earth(events.lat, events.lng);  

這條查詢語句僅僅會返回在radius_in_metres指定的半徑范圍內的記錄,非常簡單吧!



六、提高查詢速度

你可能會發現上面的查詢有不小的開銷。以我的經驗,最好對一些字段建立索引。 (下面這條語句假定你又events表, 同時events表有字段lat和lng)
[sql]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
  1. CREATE INDEX ${nameofindex} on events USING gits(lltoearth(lat, lng));  

七、數據類型

我的應用比較簡單,所以我把經緯度(lat和lng)都設成了double類型。這使得我用Node.js開發起來更加快速,而不用再去自己定制針對GIST類型的解決方案。

八、就這些!

很神奇,對么?!?我們僅僅用常用的數據類型(double)就足以去用一些GEO函數創建基於地理位置的社交app(【譯者注】 知乎上的一個回答 )!

---------------------------
英語水平有限,如有翻譯不周之處,請您指點!


------------------------------

update:

我使用的postgreSQL語句總結:

[sql]  view plain copy 在CODE上查看代碼片 派生到我的代碼片
  1. /*  
  2. * postgreSQL之earthdistance學習筆記  
  3. * author: wusuopubupt  
  4. date: 2013-03-31  
  5. */  
  6.   
  7. /*創建表*/  
  8. CREATE TABLE picture (  
  9.   id serial PRIMARY KEY ,  
  10.   p_uid char(12) NOT NULL,  
  11.   p_key char(23) NOT NULL,  
  12.   lat real not null,  
  13.   lng real NOT NULL,  
  14.   up int NOT NULL,  
  15.   down int NOT NULL,  
  16.   ip varchar(15) DEFAULT NULL,  
  17.   address varchar(256) DEFAULT NULL  
  18. );  
  19.   
  20. /*插入記錄*/  
  21. INSERT INTO picture(p_uid, p_key, lat, lng, up, down, ip, address)   
  22. VALUES('aaaabbbbcccc''2014032008164023279.png', 40.043945, 116.413668, 0, 0, '''');  
  23.   
  24. /*插入記錄*/  
  25. INSERT INTO picture(p_uid, p_key, lat, lng, up, down, ip, address)   
  26. VALUES('xxxxccccmmmm''2014032008164023111.png', 40.067183, 116.415230, 0, 0, '''');  
  27.   
  28. /*選擇記錄*/  
  29. SELECT * FROM picture;  
  30.   
  31. /*更新記錄*/  
  32. UPDATE picture SET address='LiShuiqiao' WHERE id=1;  
  33. UPDATE picture SET address='TianTongyuan' WHERE id=2;  
  34.   
  35. /*對經緯度列創建索引*/  
  36. CREATE INDEX ll_idx on picture USING gist(ll_to_earth(lat, lng));  
  37.   
  38. /*根據半徑(1000米)選擇記錄*/  
  39. SELECT * FROM picture where earth_box(ll_to_earth(40.059286,116.418773),1000) @> ll_to_earth(picture.lat, picture.lng);   
  40.   
  41. /*選擇距離當前用戶的距離*/  
  42. SELECT picture.id, earth_distance(ll_to_earth(picture.lat, picture.lng), ll_to_earth(40.059286,116.418773))   
  43. AS dis FROM picture   
  44. ORDER BY dis ASC;  
  45.   
  46. /*  
  47.  * 以下內容是網上的一篇教程  
  48.  * 地址:http://www.cse.iitb.ac.in/dbms/Data/Courses/CS631/PostgreSQL-Resources/postgresql-9.2.4/contrib/earthdistance/expected/earthdistance.out  
  49.  */  
  50. --  
  51. --  Test earthdistance extension  
  52. --  
  53. -- In this file we also do some testing of extension create/drop scenarios.  
  54. -- That's really exercising the core database's dependency logic, so ideally  
  55. -- we'd do it in the core regression tests, but we can't for lack of suitable  
  56. -- guaranteed-available extensions.  earthdistance is a good test case because  
  57. -- it has a dependency on the cube extension.  
  58. --  
  59. CREATE EXTENSION earthdistance;  -- fail, must install cube first  
  60. ERROR:  required extension "cube" is not installed  
  61. CREATE EXTENSION cube;  
  62. CREATE EXTENSION earthdistance;  
  63. --  
  64. -- The radius of the Earth we are using.  
  65. --  
  66. SELECT earth()::numeric(20,5);  
  67.      earth       
  68. ---------------  
  69.  6378168.00000  
  70. (1 row)  
  71.   
  72. --  
  73. -- Convert straight line distances to great circle distances.把直線距離轉成大圓距離  
  74. --  
  75. SELECT (pi()*earth())::numeric(20,5);  
  76.     numeric       
  77. ----------------  
  78.  20037605.73216  
  79. (1 row)  
  80.   
  81. SELECT sec_to_gc(0)::numeric(20,5);  
  82.  sec_to_gc   
  83. -----------  
  84.    0.00000  
  85. (1 row)  
  86.   
  87.   
  88. --  
  89. -- Convert great circle distances to straight line distances.  
  90. --  
  91. SELECT gc_to_sec(0)::numeric(20,5);  
  92.  gc_to_sec   
  93. -----------  
  94.    0.00000  
  95. (1 row)  
  96.   
  97. SELECT gc_to_sec(sec_to_gc(2*earth()))::numeric(20,5);  
  98.    gc_to_sec      
  99. ----------------  
  100.  12756336.00000  
  101. (1 row)  
  102.   
  103.   
  104. --  
  105. -- Set coordinates using latitude and longitude.  
  106. -- Extract each coordinate separately so we can round them.  
  107. --  
  108. SELECT cube_ll_coord(ll_to_earth(0,0),1)::numeric(20,5),  
  109.  cube_ll_coord(ll_to_earth(0,0),2)::numeric(20,5),  
  110.  cube_ll_coord(ll_to_earth(0,0),3)::numeric(20,5);  
  111.  cube_ll_coord | cube_ll_coord | cube_ll_coord   
  112. ---------------+---------------+---------------  
  113.  6378168.00000 |       0.00000 |       0.00000  
  114. (1 row)  
  115.   
  116. SELECT cube_ll_coord(ll_to_earth(360,360),1)::numeric(20,5),  
  117.  cube_ll_coord(ll_to_earth(360,360),2)::numeric(20,5),  
  118.  cube_ll_coord(ll_to_earth(360,360),3)::numeric(20,5);  
  119.  cube_ll_coord | cube_ll_coord | cube_ll_coord   
  120. ---------------+---------------+---------------  
  121.  6378168.00000 |       0.00000 |       0.00000  
  122. (1 row)  
  123.   
  124.   
  125. --  
  126. -- Test getting the latitude of a location.  
  127. --  
  128. SELECT latitude(ll_to_earth(0,0))::numeric(20,10);  
  129.    latitude     
  130. --------------  
  131.  0.0000000000  
  132. (1 row)  
  133.   
  134. SELECT latitude(ll_to_earth(45,0))::numeric(20,10);  
  135.    latitude      
  136. ---------------  
  137.  45.0000000000  
  138. (1 row)  
  139.   
  140. --  
  141. -- Test getting the longitude of a location.  
  142. --  
  143. SELECT longitude(ll_to_earth(0,0))::numeric(20,10);  
  144.   longitude     
  145. --------------  
  146.  0.0000000000  
  147. (1 row)  
  148.   
  149. SELECT longitude(ll_to_earth(45,0))::numeric(20,10);  
  150.   longitude     
  151. --------------  
  152.  0.0000000000  
  153. (1 row)  
  154.   
  155.   
  156. --  
  157. -- For the distance tests the following is some real life data.  
  158. --  
  159. -- Chicago has a latitude of 41.8 and a longitude of 87.6.  
  160. -- Albuquerque has a latitude of 35.1 and a longitude of 106.7.  
  161. -- (Note that latitude and longitude are specified differently  
  162. -- in the cube based functions than for the point based functions.)  
  163. --  
  164. --  
  165. -- Test getting the distance between two points using earth_distance.  
  166. --  
  167. SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,0))::numeric(20,5);  
  168.  earth_distance   
  169. ----------------  
  170.         0.00000  
  171. (1 row)  
  172.   
  173. SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,180))::numeric(20,5);  
  174.  earth_distance   
  175. ----------------  
  176.  20037605.73216  
  177. (1 row)  
  178.   
  179. --  
  180. -- Test getting the distance between two points using geo_distance.  
  181. --  
  182. SELECT geo_distance('(0,0)'::point,'(0,0)'::point)::numeric(20,5);  
  183.  geo_distance   
  184. --------------  
  185.       0.00000  
  186. (1 row)  
  187.   
  188. SELECT geo_distance('(0,0)'::point,'(180,0)'::point)::numeric(20,5);  
  189.  geo_distance   
  190. --------------  
  191.   12436.77274  
  192. (1 row)  
  193.   
  194.   
  195. --  
  196. -- Test getting the distance between two points using the <@> operator.  
  197. --  
  198. SELECT ('(0,0)'::point <@> '(0,0)'::point)::numeric(20,5);  
  199.  numeric   
  200. ---------  
  201.  0.00000  
  202. (1 row)  
  203.   
  204. SELECT ('(0,0)'::point <@> '(180,0)'::point)::numeric(20,5);  
  205.    numeric     
  206. -------------  
  207.  12436.77274  
  208. (1 row)  
  209.   
  210.   
  211. --  
  212. -- Test for points that should be in bounding boxes.  
  213. --  
  214. SELECT earth_box(ll_to_earth(0,0),  
  215.        earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*1.00001) @>  
  216.        ll_to_earth(0,1);  
  217.  ?column?   
  218. ----------  
  219.  t  
  220. (1 row)  
  221.   
  222. SELECT earth_box(ll_to_earth(0,0),  
  223.        earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*1.00001) @>  
  224.        ll_to_earth(0,0.1);  
  225.  ?column?   
  226. ----------  
  227.  t  
  228. (1 row)  
  229.   
  230.   
  231. --  
  232. -- Test for points that shouldn't be in bounding boxes. Note that we need  
  233. -- to make points way outside, since some points close may be in the box  
  234. -- but further away than the distance we are testing.  
  235. --  
  236. SELECT earth_box(ll_to_earth(0,0),  
  237.        earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*.57735) @>  
  238.        ll_to_earth(0,1);  
  239.  ?column?   
  240. ----------  
  241.  f  
  242. (1 row)  
  243.   
  244. SELECT earth_box(ll_to_earth(0,0),  
  245.        earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*.57735) @>  
  246.        ll_to_earth(0,0.1);  
  247.  ?column?   
  248. ----------  
  249.  f  
  250. (1 row)  





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM