最近在做一個項目,有一個功能想要實現類似於查詢附近的人的功能。由於項目的原因數據庫只能使用 postgresql
,空間查詢就使用了 postgis
來實現。
具體業務像這樣:業務需要返回附近距自己 1000
米的人的一個列表,返回列表中要帶上距離,就是說某人距你多少米?
使用定位方式是什么?
我們使用 gps
定位發現定位精度,時間,誤差和抖動都需要發大量的時間去處理。
於是采用了百度的定位 SDK
,百度的上面幾處都處理不錯。
有一個小問題,從百度拿到的坐標是 經度和緯度,這個坐標系應該是地理坐標,由於也是使用 gps
,其基礎坐標應該是 wgs84
。所以拿到的值應該是:wgs84
+ 國家偏移 + 百度偏移 = 百度坐標
后面兩項是沒有太好的辦法解決,我們拿百度坐標與 google
的大致對比了一下,誤差 50
米以內,業務可以接受。
而且這個后面兩項是系統偏移,不是隨機的,這樣的話,計算距離這種相對計算,應該可以消除部分系統偏移的影響。
再說說postgis
計算距離
postgis
是一個地理空間查詢引擎,計算地理距離這很方便。
- 開始我們在表中建一列用於存地理類型的
geometry
。
SELECT AddGeometryColumn ('public','chest','position',4326,'POINT',2, false);
- 然后保存的時候,將 經緯度和 經緯度變成
geometry
進行保存,也就是三個字段,其中保存geometry
是用下面的sql
:
ST_GeomFromText('POINT(${lng} ${lat})', 4326)
sql
中的 4326
是指的wsg84
的系統。
- 然后對保存后的值進行距離查詢,使用
ST_distance
進行,后面有二個坑等着我們:
第一個:geometry
的問題
postgis
中 geomery
使用 ST_distance
計算出來的單位竟然是弧度
,不是 米
,找了一段時間問題,發現是沒有使用投影坐標引起的。
於是在計算的時候,先將 點
變成 投影坐標
,再來計算。
ST_Transform(geo, 2346)
這里的 2346
是中國西安 80 高斯克里格投影
的編號,是分帶的,使用的中央經線在南京。
加上這個,我們能正確計算出來距離,也是 米
。
然后,一段時間后,把應用發到別的城市,也就是蘇州,出現了問題
第二個: 投影帶號的問題
原因是蘇州不在 2346
中的帶號里面,計算就出錯了!
這樣就有問題了,全國這么多的帶號,不可能寫在程序里,動態計算啊?再說萬一出國了,沒有中國的投影怎么辦?
回頭再去找方案,這個計算距離應該要支持最低就是全國,最好是全世界。
還真找到了,就是 geography
。
解釋一下: geography
和 geometry
都是 postgis
中的數據類型,翻繹過來:地理圖形和幾何圖形,用 postgis
的說法就是, geography
就是使用 wgs84
坐標系 的圖形。
postgis
可以直接使用 geography
進行計算,支持全球。於是這個問題,終於有解決方案了,就是把我們的 geometry
轉成 geography
,然后進行距離計算。
下面是我們使用 sql 語句
select
(ST_distance(position::geography, ST_GeomFromText('POINT(121 32)', 4326)::geography) as distance
from chest
where ST_dwithin(position::geography, ST_GeomFromText('POINT(121 32})', 4326)::geography, 1000)
orderBy distance desc
其中:position
是我存在數據庫中的字段,使用的是 geometry
的類型, (121,32 )
是我輸入的中心點。
::geography
就是postgis 中的 轉換類型語法,把 geometry
轉成 geography
,然后計算,這樣全球都能使用。
總結
這些問題的解決,算是把以前的知識在復習了一下。 在項目中,我們也可以直接把坐標存成 geography
,使用如下的 sql
:
ST_GeographyFromText('SRID=4326;POINT(-110 30)')
數據就不用轉來轉去了。
只是項目已有數據,不能這樣做了。