PyQt5のQGraphicsViewにGPXファイルの軌跡を描画する

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)

記事の共有

関連記事

コメント

comments powered by Disqus