-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.py
More file actions
executable file
·150 lines (118 loc) · 6.54 KB
/
main.py
File metadata and controls
executable file
·150 lines (118 loc) · 6.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
from qgis.PyQt.QtWidgets import QAction, QFileDialog, QMessageBox
from qgis.core import QgsProject, QgsFeature, QgsGeometry, QgsPointXY, QgsVectorLayer, QgsField, QgsFields
from PyQt5.QtCore import QMetaType, QVariant, QFileInfo
from PyQt5.QtGui import QIcon
from fitparse import FitFile
import os
class FitLoaderPlugin:
def __init__(self, iface):
"""Initialize the plugin with the QGIS interface."""
self.iface = iface
self.action = None
def initGui(self):
"""Create and add the plugin's action to the QGIS GUI."""
icon_path = os.path.join(os.path.dirname(__file__), "icons", "fit_loader.png")
# Create an action in the main QGIS window
self.action = QAction(QIcon(icon_path), "Load FIT Activity", self.iface.mainWindow())
# Connect the action to the run method
self.action.triggered.connect(self.run)
# Add the action to the QGIS plugin menu under "FitLoader"
self.iface.addPluginToMenu("&FitLoader", self.action)
self.iface.addToolBarIcon(self.action)
def unload(self):
"""Remove the plugin's action from the QGIS GUI when the plugin is unloaded."""
# Remove the action from the plugin menu
self.iface.removePluginMenu("&FitLoader", self.action)
self.iface.removeToolBarIcon(self.action)
del self.action
def run(self):
"""Open a dialog to select a FIT file and process it."""
# Open a file dialog for selecting a FIT file
fit_path, _ = QFileDialog.getOpenFileName(None, "Select FIT File", "", "FIT Files (*.fit)")
if not fit_path:
return # If no file was selected, exit the function
try:
# Load the selected FIT file using the fitparse library
fitfile = FitFile(fit_path)
# Determine the base name of the input file
fit_name = QFileInfo(fit_path).completeBaseName()
# Call the function to create a layer from the FIT file data
self.create_layer_from_fit(fitfile, fit_name)
except Exception as e:
# Display an error message if loading the file fails
QMessageBox.critical(None, "Error", f"Failed to load FIT file: {e}")
def create_layer_from_fit(self, fitfile, layer_name):
"""Create a point layer in QGIS based on the records from the FIT file."""
# Create a new memory-based point layer with EPSG:4326 (WGS84)
layer = QgsVectorLayer("Point?crs=EPSG:4326", layer_name, "memory")
if not layer.isValid():
# Display an error message if the layer creation fails
QMessageBox.critical(None, "Error", "Failed to create layer")
return
# Get the data provider for the layer (for adding attributes and features)
pr = layer.dataProvider()
# Add fields (attributes) to the layer for timestamp, latitude, longitude, speed, and cadence
# Prepare to store the features (points) extracted from the FIT file
field_set = set()
# loop to get all the keys in all the records
for record in fitfile.get_messages("record"):
# from record to a dictionary
dict_record = record.get_values()
if 'position_lat' not in dict_record:
continue
field_set = field_set.union(set(dict_record.keys()))
fields_list = list(field_set)
fields = QgsFields()
for field in fields_list:
if field == 'activity_type':
fields.append( QgsField(field, QVariant.String) )
elif field == 'timestamp':
#fields.append( QgsField(field) )
fields.append( QgsField(field, QVariant.DateTime) )
else:
fields.append( QgsField(field, QVariant.Double) )
# Integer from 0 that identifies the subtrack that correspond
# to stop and restart in the recording data
fields.append( QgsField('seg_num', QVariant.Int) )
pr.addAttributes(fields)
layer.updateFields()
curr_seg_num = 0
no_records = True # True iff no records before next event start
# Loop through the "record" messages in the FIT file (these contain activity data)
for msg in fitfile.messages:
if msg.name == 'record':
record = msg
# from record to a dictionary
dict_record = record.get_values()
#QMessageBox.critical(None, "Qua")
# we are interested in georeferenced records
if 'position_lat' not in dict_record:
continue
dict_record['position_lat'] = dict_record['position_lat']*180/2**31
dict_record['position_long'] = dict_record['position_long']*180/2**31
#QgsMessageLog.logMessage(str(dict_record['position_lat']), 'FitLoader', 0)
# Create a point geometry with longitude and latitude
point = QgsPointXY(dict_record['position_long'], dict_record['position_lat'])
# Create a new feature for the point
feature = QgsFeature()
feature.setFields(fields)
# Set the feature's attributes (timestamp, lat, lon, speed, and cadence)
for x in dict_record:
#QgsMessageLog.logMessage(x , 'FitLoader', 0)
if x in ('timestamp', 'activity_type'):
feature.setAttribute(x, str(dict_record[x]) )
else:
feature.setAttribute(x, dict_record[x] )
# Add the feature to the list of features
feature.setGeometry(QgsGeometry.fromPointXY(point))
feature.setAttribute('seg_num', curr_seg_num)
pr.addFeature(feature)
no_records = False
elif no_records == False and msg.name == 'event' and\
msg.get_values()['event'] == 'timer' and msg.get_values()['event_type'] == 'start':
curr_seg_num += 1
no_records = True
# Add all the features to the layer
#pr.addFeatures(features)
# Add the layer to the current QGIS project
QgsProject.instance().addMapLayer(layer)