座標點地圖匹配方法

1.概述

1.1.背景

隨着公司業務發展,車定位軌跡數據愈來愈多,目前發現軌跡數據存在偏移問題,本文主要目的在於用圖數據庫和空間數據庫來解決偏移問題,作到軌跡糾偏,將偏移的定位點放置到附近的道路上。
座標點地圖匹配方法
例如在上圖中有個點不在軌跡上,也不在道路上,本文將經過計算的方法給它找到附近最近的道路上的點。node

1.2.綁路方法

綁路的方法主要分爲如下幾個步驟:
一、獲取路網數據保存至Neo4j數據庫
二、將路網中的點數據保存到Postgresql數據庫
三、輸入一組經緯度點,分別找到每一個點附近出現的在路上的點,在圖數據庫中查找這些點之間是否有連線關係,組成周圍存在的路。
四、從周圍存在的路中找到該點對於每一個路線段的垂線在該路網線段的垂足,並計算垂足到該點的距離。
五、找出最近的距離的垂足做爲該點綁路的點。
座標點地圖匹配方法python

如上圖中,就有三個備選點,分別對綁路的點對三個路網線段作垂線得到備選點,而後求垂足A、B、C與綁路的點的距離,選取A、B、C中到該點距離最短的做爲結果輸出。web

2.數據清洗與保存

2.1.數據格式

獲取的路網數據通過清洗格式以下圖所示:
座標點地圖匹配方法
座標點地圖匹配方法算法

上圖中第一幅表示連線關係第二幅爲點座標。
將連線關係和點的經緯度保存至圖數據庫中,將點的經緯度保存至空間數據庫中。
空間數據庫表格式:
CREATE TABLE "public"."roadpoint" (
"id" serial NOT NULL,
"lat" float8 DEFAULT 0,
"lng" float8 DEFAULT 0,
"gisp" point
)
WITH (OIDS=FALSE)
;sql

ALTER TABLE "public"."roadpoint" OWNER TO "bmkpdev";數據庫

COMMENT ON COLUMN "public"."roadpoint"."lat" IS '緯度';json

COMMENT ON COLUMN "public"."roadpoint"."lng" IS '經度';flask

CREATE INDEX "georoadpointgis_idx" ON "public"."roadpoint" USING gist ("gisp") WITH (fillfactor = 85);
其中gisp是空間數據類型,後創建空間索引georoadpointgis_idx,用於檢索最近的路網當中的點。
圖數據庫中建立點的Cypher語句以下:
建立點
create (:point {lng:114.3086811059,lat:30.335394066259});
create (:point {lng:114.30853126854,lat:30.336053775889});緩存

建立點的關係
match (a:point),(b:point)
where a.lng=114.3086811059 and a.lat=30.335394066259 and b.lng=114.30853126854 and b.lat=30.336053775889
 create (a)-[r:to]->(b)
 return r;
從Neo4j的web管理界面上能夠查找到該點:座標點地圖匹配方法markdown

爲了加快在圖數據庫中的查詢速度 須要在圖數據庫中創建索引,索引語句以下:
create index on:node(lat)
create index on:node(lng)

2.2.Python鏈接Neo4j

獲取圖數據庫中的點與點之間是否有連線關係,查詢代碼以下:
座標點地圖匹配方法

find_relationship = test_graph.match_one(start_node=find_code_1,end_node=find_code_3,bidirectional=False)
print find_relationship

2.3.Python鏈接Postgresql

示例代碼以下:
import psycopg2# 數據庫鏈接參數
conn = psycopg2.connect(database="platoon", user="postgres", password="postgres", host="192.168.10.80", port="5432")
cur = conn.cursor()
cur.execute("CREATE TABLE test(id serial PRIMARY KEY, num integer,data varchar);")# insert one item
cur.execute("INSERT INTO test(num, data)VALUES(%s, %s)", (1, 'aaa'))
cur.execute("INSERT INTO test(num, data)VALUES(%s, %s)", (2, 'bbb'))
cur.execute("INSERT INTO test(num, data)VALUES(%s, %s)", (3, 'ccc'))
cur.execute("SELECT * FROM test;")
rows = cur.fetchall() # all rows in tableprint(rows)for i in rows:
print(i)
conn.commit()
cur.close()
conn.close()

2.4.垂足計算公式

假設圖中O點爲待綁定的點,AB爲周圍的路網點:
座標點地圖匹配方法

垂足座標以下:
座標點地圖匹配方法

y座標和x座標對稱。

篩選選數據庫小數點後面兩位不同就拋棄

找出最近的十個點的SQL:
select *,geo_distance(gisp,point(114.404498,30.511795)) as distance from roadpoint where geo_distance(gisp,point(114.404498,30.511795)) < 0.62 ORDER BY distance limit 10

2.5.計算結果展現

未進行綁路的點的分佈如圖
座標點地圖匹配方法
座標點地圖匹配方法

從上圖看出效果比較理想,實現了定位點到道路點的轉化
程序有幾個能夠優化的地方:
一、根據方向判斷在哪條路上
二、計算點到直線的距離有更快的方法
三、選擇子集有更快的方法
求(x0, y0)到通過(x1, y1); (x2, y2)直線的距離。
直線方程中 
A = y2 - y1,
B = x1- x2,
C = x2 y1 - x1 y2;
點的直線的距離公式爲: 
double d = (fabs((y2 - y1) x0 +(x1 - x2) y0 + ((x2 y1) -(x1 y2)))) / (sqrt(pow(y2 - y1, 2) + pow(x1 - x2, 2)));

2.6.Neo4j快速導入數據的方法

數據格式以下:
座標點地圖匹配方法

導入語句以下:
using periodic commit 1000 load csv from "file:///pointinfo.csv" as line create (:point {toFloat(lng:line[0]), lat:toFloat(line[1])})

3.性能提高方法

爲告終局在空間數據庫中快速匹配到附近的道路點,特採用geohash算法:
導入數據的roadpoint表結構以下:
座標點地圖匹配方法

創建空間索引的sql以下:
update roadpoint set gisp = point(lng,lat) where gisp is null;
增長新列geohashcode的sql以下:
ALTER table roadpoint add geohashcode varchar(20) DEFAULT NULL;
CREATE INDEX "geohash_index" ON "public"."roadpoint" USING hash ("geohashcode");

3.1.感性認識GeoHash

首先來點感性認識,http://openlocation.org/geohash/geohash-js/ 提供了在地圖上顯示geohash編碼的功能。
1)GeoHash將二維的經緯度轉換成字符串,好比下圖展現了北京9個區域的GeoHash字符串,分別是WX4ER,WX4G二、WX4G3等等,每個字符串表明了某一矩形區域。也就是說,這個矩形區域內全部的點(經緯度座標)都共享相同的GeoHash字符串,這樣既能夠保護隱私(只表示大概區域位置而不是具體的點),又比較容易作緩存,好比左上角這個區域內的用戶不斷髮送位置信息請求餐館數據,因爲這些用戶的GeoHash字符串都是WX4ER,因此能夠把WX4ER看成key,把該區域的餐館信息看成value來進行緩存,而若是不使用GeoHash的話,因爲區域內的用戶傳來的經緯度是各不相同的,很難作緩存。座標點地圖匹配方法

2)字符串越長,表示的範圍越精確。如圖所示,5位的編碼能表示10平方公里範圍的矩形區域,而6位編碼能表示更精細的區域(約0.34平方公里)
座標點地圖匹配方法

3)字符串類似的表示距離相近(特殊狀況後文闡述),這樣能夠利用字符串的前綴匹配來查詢附近的POI信息。以下兩個圖所示,一個在城區,一個在郊區,城區的GeoHash字符串之間比較類似,郊區的字符串之間也比較類似,而城區和郊區的GeoHash字符串類似程度要低些。
座標點地圖匹配方法
座標點地圖匹配方法

城區 郊區
經過上面的介紹咱們知道了GeoHash就是一種將經緯度轉換成字符串的方法,而且使得在大部分狀況下,字符串前綴匹配越多的距離越近,回到咱們的案例,根據所在位置查詢來查詢附近餐館時,只須要將所在位置經緯度轉換成GeoHash字符串,並與各個餐館的GeoHash字符串進行前綴匹配,匹配越多的距離越近。

3.2.GeoHash算法的步驟

下面以北海公園爲例介紹GeoHash算法的計算步驟
座標點地圖匹配方法
2.1. 根據經緯度計算GeoHash二進制編碼
地球緯度區間是[-90,90], 北海公園的緯度是39.928167,能夠經過下面算法對緯度39.928167進行逼近編碼:
1)區間[-90,90]進行二分爲[-90,0),[0,90],稱爲左右區間,能夠肯定39.928167屬於右區間[0,90],給標記爲1;
2)接着將區間[0,90]進行二分爲 [0,45),[45,90],能夠肯定39.928167屬於左區間 [0,45),給標記爲0;
3)遞歸上述過程39.928167老是屬於某個區間[a,b]。隨着每次迭代區間[a,b]總在縮小,並愈來愈逼近39.928167;
4)若是給定的緯度x(39.928167)屬於左區間,則記錄0,若是屬於右區間則記錄1,這樣隨着算法的進行會產生一個序列1011100,序列的長度跟給定的區間劃分次數有關。
根據緯度算編碼
bit min mid max
1 -90.000 0.000 90.000
0 0.000 45.000 90.000
1 0.000 22.500 45.000
1 22.500 33.750 45.000
1 33.7500 39.375 45.000
0 39.375 42.188 45.000
0 39.375 40.7815 42.188
0 39.375 40.07825 40.7815
1 39.375 39.726625 40.07825
1 39.726625 39.9024375 40.07825
同理,地球經度區間是[-180,180],能夠對經度116.389550進行編碼。
根據經度算編碼
bit min mid max
1 -180 0.000 180
1 0.000 90 180
0 90 135 180
1 90 112.5 135
0 112.5 123.75 135
0 112.5 118.125 123.75
1 112.5 115.3125 118.125
0 115.3125 116.71875 118.125
1 115.3125 116.015625 116.71875
1 116.015625 116.3671875 116.71875
2.2. 組碼
經過上述計算,緯度產生的編碼爲10111 00011,經度產生的編碼爲11010 01011。偶數位放經度,奇數位放緯度,把2串編碼組合生成新串:11100 11101 00100 01111。
最後使用用0-九、b-z(去掉a, i, l, o)這32個字母進行base32編碼,首先將11100 11101 00100 01111轉成十進制,對應着2八、2九、四、15,十進制對應的編碼就是wx4g。同理,將編碼轉換成經緯度的解碼算法與之相反,具體再也不贅述。座標點地圖匹配方法

3.3.GeoHash Base32編碼長度與精度

下表摘自維基百科:http://en.wikipedia.org/wiki/Geohash
能夠看出,當geohash base32編碼長度爲8時,精度在19米左右,而當編碼長度爲9時,精度在2米左右,編碼長度須要根據數據狀況進行選擇。
座標點地圖匹配方法

3.4.GeoHash算法

上文講了GeoHash的計算步驟,僅僅說明是什麼而沒有說明爲何?爲何分別給經度和維度編碼?爲何須要將經緯度兩串編碼交叉組合成一串編碼?本節試圖回答這一問題。
如圖所示,咱們將二進制編碼的結果填寫到空間中,當將空間劃分爲四塊時候,編碼的順序分別是左下角00,左上角01,右下腳10,右上角11,也就是相似於Z的曲線,當咱們遞歸的將各個塊分解成更小的子塊時,編碼的順序是自類似的(分形),每個子快也造成Z曲線,這種類型的曲線被稱爲Peano空間填充曲線。
這種類型的空間填充曲線的優勢是將二維空間轉換成一維曲線(事實上是分形維),對大部分而言,編碼類似的距離也相近, 但Peano空間填充曲線最大的缺點就是突變性,有些編碼相鄰但距離卻相差很遠,好比0111與1000,編碼是相鄰的,但距離相差很大。
座標點地圖匹配方法

座標點地圖匹配方法
除Peano空間填充曲線外,還有不少空間填充曲線,如圖所示,其中效果公認較好是Hilbert空間填充曲線,相較於Peano曲線而言,Hilbert曲線沒有較大的突變。爲何GeoHash不選擇Hilbert空間填充曲線呢?多是Peano曲線思路以及計算上比較簡單吧,事實上,Peano曲線就是一種四叉樹線性編碼方式。

3.5.使用注意點

1)因爲GeoHash是將區域劃分爲一個個規則矩形,並對每一個矩形進行編碼,這樣在查詢附近POI信息時會致使如下問題,好比紅色的點是咱們的位置,綠色的兩個點分別是附近的兩個餐館,可是在查詢的時候會發現距離較遠餐館的GeoHash編碼與咱們同樣(由於在同一個GeoHash區域塊上),而較近餐館的GeoHash編碼與咱們不一致。這個問題每每產生在邊界處。
座標點地圖匹配方法

解決的思路很簡單,咱們查詢時,除了使用定位點的GeoHash編碼進行匹配外,還使用周圍8個區域的GeoHash編碼,這樣能夠避免這個問題。
2)咱們已經知道現有的GeoHash算法使用的是Peano空間填充曲線,這種曲線會產生突變,形成了編碼雖然類似但距離可能相差很大的問題,所以在查詢附近餐館時候,首先篩選GeoHash編碼類似的POI點,而後進行實際距離計算。

4.服務發佈

本程序採用flask框架部署上線:
Flask是一個使用 Python 編寫的輕量級 Web 應用框架。其 WSGI 工具箱採用 Werkzeug ,模板引擎則使用 Jinja2 。Flask使用 BSD 受權。
Flask也被稱爲 「microframework」 ,由於它使用簡單的核心,用 extension 增長其餘功能。Flask沒有默認使用的數據庫、窗體驗證工具。
訪問url示例以下:
http://127.0.0.1:8881/latlonPoint?lng=114&lat=30

Python示例程序以下:
#!/usr/bin/env python

-- coding: utf-8 --

## 對flask框架模塊調用
## 要注意Python Interpreter選取、virtualenv虛擬環境
## 詳見Flask環境搭建、目的是隔離出一個獨立環境
## 查詢須要數據庫支撐,MySQL

 import os
 import json
 from flask import Flask
 from flask import request
 from flask import redirect
 from flask import jsonify
 from flask_cors import CORS

 app = Flask(__name__)
 CORS(app)

 @app.route('/latlonPoint' , methods=['GET', 'POST'])
 def index():
     #接收POST參數
     #接收參數類型爲json格式
     #字符串格式須要將 undecode轉化爲 UTF-8
     #若是接收方式爲post方法,則直接接收數據
     if request.method == 'GET' :
         #rev_data = json.loads(request.get_data())
         #lng = float(rev_data.get('lng'))                 # 字符串:lng
         #lat = float(rev_data.get('lat'))                 # 字符串:lat
         lng = request.args.get('lng')
         lat = request.args.get('lat')

         datalist = {"lng" :lng,"lat" :lat }
         return json.dumps(datalist)
     else:
         return '只接受post請求!'

 @app.route('/')
 def user():
     return'Please input your detail route!'

 if __name__ =='__main__':
     #app.run(host = '0.0.0.0',port=8881,ssl_context='adhoc')
     # app.run(host = '0.0.0.0',port=8881)
     app.run(port=8881, debug=True,threaded=True)

解決橋上橋下的問題

位置定位數據中,一直有個關鍵問題就是當定位數據處於立交橋的交匯處,如何判斷,該定位點在橋上仍是橋下。此時須要輸入前面的定位數據,經過以上的綁路算法 判斷在哪一條路上 ,而後在圖數據庫中搜索與之相關聯的路,做爲備選,這一切的前提是圖數據庫中的數據點之間是可以關聯上的。

相關文章
相關標籤/搜索