使用 PyQGIS 和 OSRM 將 GPS 捕捉軌跡應用到道路
如果您收集了 GPS 軌跡,您就會知道結果可能具有不同的準確性。沿路線收集的軌跡點并不總是在路上,可能會很緊張。
如果您是物流、送貨或出租車公司——這會帶來一個大問題。使用這些點計算的距離將不準確——尤其是如果這些點是間隔開的。此外,您無法比較在不同設備或人員處收集的軌跡,因為即使它們在同一條路線上,它們的幾何形狀也會不同。
此問題的解決方案是將每個點捕捉到最近的路段。雖然這在原則上聽起來很容易,但準確地做到這一點是具有挑戰(zhàn)性的。你不能為一個點選擇最近的路段——因為最近的點可能在交叉的街道上。您需要考慮上一個點和下一個點之間的路線,以找到最合理的捕捉位置。
幸運的是,一個名為Open Source Routing Machine (OSRM)的開源項目通過快速且可擴展的算法解決了這個問題。我們可以使用 OSRM 的匹配服務將 GPS 點捕捉到最合適的路段。OSRM 引擎使用來自 OpenStreetMap (OSM) 項目的數(shù)據(jù)。OSM 在世界大部分地區(qū)擁有相當不錯的街道網(wǎng)絡覆蓋,并且還在不斷改進。通過利用來自 OSM 的開放數(shù)據(jù)和來自 OSRM 的開放路由算法,我們可以實現(xiàn)捕捉服務。
OSRM 的工作原理是通過HTTP API獲取輸入,計算結果并通過 JSON 對象返回它們。
運行 OSRM 服務
OSRM 提供了一個演示服務器和一個演示 HTTP 服務。但是我發(fā)現(xiàn)演示服務器經(jīng)常過載,不適合用于偶爾測試以外的用途。
如果您想在您的項目中使用 OSRM 引擎,最好的選擇是在您的計算機或服務器上運行您自己的服務。運行您自己的服務實例可能聽起來很嚇人,但使用 Docker 設置它非常簡單。該文檔有很好的說明。以下是我使用印度班加羅爾市的數(shù)據(jù)運行本地實例的步驟。
獲取數(shù)據(jù)
在城市級別獲取 OpenStreetMap 提取的一種簡單方法是Interline。如果您需要國家和大陸級別的數(shù)據(jù),可以從GeoFabrik下載。
我注冊了一個免費的 API 密鑰,并為班加羅爾下載了作為begaluru_india.osm.pbf文件的提取物。我在我的系統(tǒng)上創(chuàng)建了一個新文件夾,將數(shù)據(jù)文件復制到那里,啟動 Docker 并在終端中運行以下命令。文檔中唯一的變化是–max-matching-size參數(shù),我將其增加到 5000,以便我們可以匹配大型 GPS 軌跡。
docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-extract -p /opt/car.lua /data/bengaluru_india.osm.pbf
docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-partition /data/bengaluru_india.osrm
docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-customize /data/bengaluru_india.osrm
docker run -t -i -p 5000:5000 -v "${PWD}:/data" osrm/osrm-backend osrm-routed --algorithm mld --max-matching-size 5000 /data/bengaluru_india.osrm
運行最后一條命令后,服務器將在您的機器上啟動,它可以接受 URL?http://127.0.0.1:5000 的匹配請求
匹配請求的格式如下,其中關鍵部分是 {coordinates} 參數(shù),它是軌跡上每個點的坐標,格式為longitude1, latitude1;longitude2, latitude2。
docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-extract -p /opt/car.lua /data/bengaluru_india.osm.pbf
docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-partition /data/bengaluru_india.osrm
docker run -t -v "${PWD}:/data" osrm/osrm-backend osrm-customize /data/bengaluru_india.osrm
docker run -t -i -p 5000:5000 -v "${PWD}:/data" osrm/osrm-backend osrm-routed --algorithm mld --max-matching-size 5000 /data/bengaluru_india.osrm
我們需要通過讀取 GPS 軌跡以編程方式編譯此 URL,并將其發(fā)送到我們在上一步中啟動的本地匹配服務。還需要對結果進行處理并轉換為軌跡線進行可視化。這就是 QGIS 的用武之地。使用 PyQGIS,我們可以編寫一個處理腳本,使這種交互變得簡單直觀。
匹配 GPS 軌跡
打開 QGIS。轉到處理 → 工具箱 → 創(chuàng)建新腳本
在腳本編輯器中復制/粘貼以下代碼并將其保存為snap_to_road.py
import requests
from PyQt5.QtCore import QCoreApplication
from qgis.core import (QgsProcessing, QgsProcessingAlgorithm,
QgsProcessingParameterFeatureSource, QgsProcessingParameterFeatureSink,
QgsProcessingParameterString, QgsProcessingParameterNumber, QgsWkbTypes,
QgsGeometry, QgsFeatureSink, QgsFields, QgsPoint, QgsFeature)
from PyQt5.QtXml import QDomDocument
class ExportLayoutAlgorithm(QgsProcessingAlgorithm):
"""Exports the current map view to PDF"""
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
SERVICE = 'SERVICE'
TOLERANCE = 'TOLERANCE'
def flags(self):
return super().flags() | QgsProcessingAlgorithm.FlagNoThreading
def initAlgorithm(self, config=None):
self.addParameter(
QgsProcessingParameterFeatureSource(
'INPUT',
self.tr('Input vector layer'),
types=[QgsProcessing.TypeVectorPoint]
)
)
self.addParameter(
QgsProcessingParameterString(
self.SERVICE,
self.tr('OSRM Service URL'),
'http://127.0.0.1:5000'
)
)
self.addParameter(
QgsProcessingParameterNumber(
self.TOLERANCE,
self.tr('Snapping Tolerance (meters)'),
QgsProcessingParameterNumber.Integer,
10
)
)
self.addParameter(
QgsProcessingParameterFeatureSink(
self.OUTPUT,
'Snapped Line',
QgsProcessing.TypeVectorLine
)
)
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context)
service = self.parameterAsString(parameters, self.SERVICE, context)
tolerance = self.parameterAsInt(parameters, self.TOLERANCE, context)
sink, dest_id = self.parameterAsSink(
parameters,
self.OUTPUT,
context,
QgsFields(),
QgsWkbTypes.LineString,
source.sourceCrs()
)
# Compute the number of steps to display within the progress bar and
# get features from source
total = 100.0 / source.featureCount() if source.featureCount() else 0
features = source.getFeatures()
coordinate_list = []
for current, f in enumerate(features):
# Stop the algorithm if cancel button has been clicked
if feedback.isCanceled():
break
geom = f.geometry().asPoint()
coordinates = '{},{}'.format(geom.x(), geom.y())
coordinate_list.append(coordinates)
feedback.setProgress(int(current * total))
coordinate_str = ';'.join(coordinate_list)
radius = ['{}'.format(tolerance)]
radius_str = ';'.join(radius*len(coordinate_list))
service_url = '/match/v1/driving/{}'.format(coordinate_str)
request_url = service + service_url
payload = {'geometries': 'geojson', 'steps': 'false', 'radiuses': radius_str}
r = requests.get(request_url, params=payload)
results = r.json()
for match in results['matchings']:
coords = match['geometry']['coordinates']
point_list = [QgsPoint(coord[0], coord[1]) for coord in coords]
out_f = QgsFeature()
out_f.setGeometry(QgsGeometry.fromPolyline(point_list))
sink.addFeature(out_f, QgsFeatureSink.FastInsert)
return {self.OUTPUT: sink}
def name(self):
return 'snap_to_roads'
def displayName(self):
return self.tr('Snap to Roads')
def shortHelpString(self):
return self.tr('Snaps GPS Trackpoints to OSM roads using OSRM service')
def group(self):
return self.tr(self.groupId())
def groupId(self):
return ''
def tr(self, string):
return QCoreApplication.translate('Processing', string)
def createInstance(self):
return ExportLayoutAlgorithm()
保存后,新算法將出現(xiàn)在 Processing → Toolbox → Scripts → Snap To Roads 中。在 QGIS 中加載您的 GPS 跟蹤點并雙擊腳本以運行它。
生成的捕捉道路線將添加到 QGIS 圖層面板。您可以看到 OSRM 的工作非常有魅力,并且結果正如人們所期望的那樣。
如果您想試用該算法,可以下載sample_gps_track.gpx。從 Interline獲取Bengaluru OSM 摘錄。如果您遇到問題,請發(fā)表評論并告訴我。
API
版權聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權內(nèi)容。