PyQt5のQGraphicsViewにGPXファイルの軌跡を描画する
2020/10/25 categories:PyQt5| tags:Python|QGraphicsView|GPX|PyQt5|
GPSロガーなどで使用されているGPXファイルをPyQt5に表示するプログラムを作成してみました。
使用したGPXファイル
ヤマレコで公開されている富士山の登山ルートのデータを使用しました。富士山(富士宮口五合目~八合目~御殿場ルート~剣ヶ峰~御殿場下り六合~宝永山~富士宮口合目)のもので、下記画像のような感じのデータです。このデータをQGraphicsViewに描画してみます。
GPXファイルの読み込み
GPXファイルはPythonのライブラリであるgpxpyを使用して読み込みました。読み込んだデータはdatasというオブジェクトにnumpy配列として格納しています。
import gpxpy
import numpy as np
gpx_file = open('track-1155.gpx', mode='r', encoding='utf8')
gpx = gpxpy.parse(gpx_file)
datas = []
for track in gpx.tracks:
datas.append([])
for segment in track.segments:
array = np.array(
[
[p.longitude for p in segment.points],
[p.latitude for p in segment.points]
]
)
datas[-1].append(array)
GPXファイルを表示用のデータに処理
GPXファイルの緯度と経度をグラフにしてみるとそれぞれ以下のような感じになります。これをそのままQGraphicsViewに描画すると全ての点が1ピクセル以内になるのでどこに描画されたか分かりません。
そこでまずは全ての点を最小値との差分をとって以下のようにオフセットしました。これで座標の最小値が0になるようにしています。
lon_def = datas[0][0][0] - min(datas[0][0][0])
lat_def = datas[0][0][1] - min(datas[0][0][1])
結果
その後、最大値がQGraphicsSceneのサイズに合わせて下記のように拡大しました。
if max(lon_def) > max(lat_def):
ratio = 600 / max(lon_def)
else:
ratio = 600 / max(lat_def)
datas[0][0][0] = (lon_def) * ratio
datas[0][0][1] = 600 - (lat_def) * ratio
結果
このデータを表示した結果は以下の通りです。
ビューのコントロール
マウスのホイール回転でズームインとズームアウト、ホイールのドラッグで表示位置の変更が可能です。
ソースコード
main.py
import gpxpy
import numpy as np
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from ui import MainWindow
def save(ls, name):
text = name + '\n' + '\n'.join([ str(i) for i in ls ])
with open(name, 'w') as f:
f.write(text)
def main():
# GPX File
gpx_file = open('track-1155.gpx', mode='r', encoding='utf8')
gpx = gpxpy.parse(gpx_file)
datas = []
for track in gpx.tracks:
datas.append([])
for segment in track.segments:
array = np.array(
[
[p.longitude for p in segment.points],
[p.latitude for p in segment.points]
]
)
datas[-1].append(array)
lon_def = datas[0][0][0] - min(datas[0][0][0])
lat_def = datas[0][0][1] - min(datas[0][0][1])
if max(lon_def) > max(lat_def):
ratio = 600 / max(lon_def)
else:
ratio = 600 / max(lat_def)
datas[0][0][0] = (lon_def) * ratio
datas[0][0][1] = 600 - (lat_def) * ratio
polygon = QtGui.QPolygonF()
for lat, lon in zip(datas[0][0][0], datas[0][0][1]):
polygon.append( QtCore.QPointF(lat, lon) )
# GUI
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
scene = window.graphicsView.scene()
scene.addItem(QtWidgets.QGraphicsPolygonItem(polygon))
window.show()
app.exec()
if __name__ == '__main__':
main()
ui.py
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.centralwidget = QtWidgets.QWidget(self)
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.graphicsView = GraphicsView(self.centralwidget)
self.verticalLayout.addWidget(self.graphicsView)
self.setCentralWidget(self.centralwidget)
self.resize(800, 600)
self.graphicsView.setScene( QtWidgets.QGraphicsScene(0, 0, 600, 600, self.graphicsView) )
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, *argv, **keywords):
super(GraphicsView, self).__init__(*argv, **keywords)
self._numScheduledScalings = 0
def animation_finished(self):
if self._numScheduledScalings > 0:
self._numScheduledScalings -= 1
else:
self._numScheduledScalings += 1
def wheelEvent(self, event):
numDegrees = event.angleDelta().y() / 8
numSteps = numDegrees / 15
self._numScheduledScalings += numSteps
if self._numScheduledScalings * numSteps < 0:
self._numScheduledScalings = numSteps
anim = QtCore.QTimeLine(350, self)
anim.setUpdateInterval(20)
anim.valueChanged.connect(self.scaling_time)
anim.finished.connect(self.animation_finished)
anim.start()
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MidButton:
self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
event = QtGui.QMouseEvent(
QtCore.QEvent.GraphicsSceneDragMove,
event.pos(),
QtCore.Qt.MouseButton.LeftButton,
QtCore.Qt.MouseButton.LeftButton,
QtCore.Qt.KeyboardModifier.NoModifier
)
QtWidgets.QGraphicsView.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
QtWidgets.QGraphicsView.mouseReleaseEvent(self, event)
self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
def scaling_time(self, x):
factor = 1.0 + float(self._numScheduledScalings) / 300.0
self.scale(factor, factor)