rowingdata 3.6.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rowingdata/__init__.py +2 -0
- rowingdata/__main__.py +2 -0
- rowingdata/boatedit.py +15 -0
- rowingdata/checkdatafiles.py +216 -0
- rowingdata/copystats.py +22 -0
- rowingdata/crewnerdplot.py +37 -0
- rowingdata/crewnerdplottime.py +37 -0
- rowingdata/csvparsers.py +3114 -0
- rowingdata/ergdataplot.py +31 -0
- rowingdata/ergdataplottime.py +31 -0
- rowingdata/ergdatatotcx.py +32 -0
- rowingdata/ergstickplot.py +31 -0
- rowingdata/ergstickplottime.py +32 -0
- rowingdata/ergsticktotcx.py +32 -0
- rowingdata/example.csv +5171 -0
- rowingdata/gpxtools.py +70 -0
- rowingdata/gpxwrite.py +151 -0
- rowingdata/konkatenaadje.py +19 -0
- rowingdata/laptesting.py +293 -0
- rowingdata/obsolete.py +654 -0
- rowingdata/otherparsers.py +718 -0
- rowingdata/painsled_desktop_plot.py +30 -0
- rowingdata/painsled_desktop_plottime.py +29 -0
- rowingdata/painsled_desktop_toc2.py +30 -0
- rowingdata/painsledplot.py +27 -0
- rowingdata/painsledplottime.py +27 -0
- rowingdata/painsledtoc2.py +23 -0
- rowingdata/roweredit.py +15 -0
- rowingdata/rowingdata.py +6941 -0
- rowingdata/rowproplot.py +31 -0
- rowingdata/rowproplottime.py +31 -0
- rowingdata/speedcoachplot.py +31 -0
- rowingdata/speedcoachplottime.py +31 -0
- rowingdata/speedcoachtoc2.py +36 -0
- rowingdata/tcxplot.py +38 -0
- rowingdata/tcxplot_nogeo.py +38 -0
- rowingdata/tcxplottime.py +33 -0
- rowingdata/tcxplottime_nogeo.py +33 -0
- rowingdata/tcxtoc2.py +30 -0
- rowingdata/tcxtools.py +417 -0
- rowingdata/trainingparser.py +302 -0
- rowingdata/utils.py +135 -0
- rowingdata/windcorrected.py +48 -0
- rowingdata/writetcx.py +312 -0
- rowingdata-3.6.8.dist-info/LICENSE +21 -0
- rowingdata-3.6.8.dist-info/METADATA +1149 -0
- rowingdata-3.6.8.dist-info/RECORD +49 -0
- rowingdata-3.6.8.dist-info/WHEEL +5 -0
- rowingdata-3.6.8.dist-info/top_level.txt +1 -0
rowingdata/tcxtools.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# pylint: disable=C0103
|
|
2
|
+
from __future__ import absolute_import
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
import unicodedata
|
|
7
|
+
import codecs
|
|
8
|
+
import xmltodict as xd
|
|
9
|
+
from dateutil import parser
|
|
10
|
+
import arrow
|
|
11
|
+
import gzip
|
|
12
|
+
import numpy as np
|
|
13
|
+
import string
|
|
14
|
+
from six import unichr
|
|
15
|
+
from datetime import datetime, timedelta
|
|
16
|
+
|
|
17
|
+
from lxml import etree
|
|
18
|
+
from lxml.etree import XMLSyntaxError
|
|
19
|
+
from docopt import docopt
|
|
20
|
+
import xml.etree.ElementTree as ET
|
|
21
|
+
|
|
22
|
+
ns1 = 'http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2'
|
|
23
|
+
ns2 = 'http://www.garmin.com/xmlschemas/ActivityExtension/v2'
|
|
24
|
+
|
|
25
|
+
import unicodedata
|
|
26
|
+
def clean_string(input):
|
|
27
|
+
if input:
|
|
28
|
+
RE_XML_ILLEGAL = u'([\u0000-\u0008\u000b-\u000c\u000e-\u001f\ufffe-\uffff])' + \
|
|
29
|
+
u'|' + \
|
|
30
|
+
u'([%s-%s][^%s-%s])|([^%s-%s][%s-%s])|([%s-%s]$)|(^[%s-%s])' % \
|
|
31
|
+
(unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff),
|
|
32
|
+
unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff),
|
|
33
|
+
unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
input = re.sub(RE_XML_ILLEGAL, "", input)
|
|
37
|
+
input = input.replace(RE_XML_ILLEGAL,'').strip()
|
|
38
|
+
return input
|
|
39
|
+
|
|
40
|
+
def clean_xml_content(xml_content):
|
|
41
|
+
# Clean the XML content of potentially problematic characters
|
|
42
|
+
return ''.join(char for char in xml_content if char.isprintable())
|
|
43
|
+
|
|
44
|
+
def remove_control_characters(s):
|
|
45
|
+
return "".join(ch for ch in s if unicodedata.category(ch)[0]!="C")
|
|
46
|
+
|
|
47
|
+
def strip_control_characters(input):
|
|
48
|
+
if input:
|
|
49
|
+
|
|
50
|
+
# unicode invalid characters
|
|
51
|
+
RE_XML_ILLEGAL = u'([\u0000-\u0008\u000b-\u000c\u000e-\u001f\ufffe-\uffff])' + \
|
|
52
|
+
u'|' + \
|
|
53
|
+
u'([%s-%s][^%s-%s])|([^%s-%s][%s-%s])|([%s-%s]$)|(^[%s-%s])' % \
|
|
54
|
+
(unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff),
|
|
55
|
+
unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff),
|
|
56
|
+
unichr(0xd800),unichr(0xdbff),unichr(0xdc00),unichr(0xdfff),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
input = re.sub(RE_XML_ILLEGAL, "", input)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
#input = "".join(ch for ch in input if unicodedata.category(ch)[0]!="C")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ascii control characters
|
|
67
|
+
#input = re.sub(r"[\x01-\x1F\x7F]", "", input)
|
|
68
|
+
|
|
69
|
+
return input
|
|
70
|
+
|
|
71
|
+
def tcx_getdict(path):
|
|
72
|
+
extension = path[-3:].lower()
|
|
73
|
+
if extension == '.gz':
|
|
74
|
+
with gzip.open(path,'r') as f:
|
|
75
|
+
input = f.read()
|
|
76
|
+
input = strip_control_characters(input)
|
|
77
|
+
d = xd.parse(input)
|
|
78
|
+
else:
|
|
79
|
+
with open(path, 'r') as f:
|
|
80
|
+
input = f.read()
|
|
81
|
+
input = strip_control_characters(input)
|
|
82
|
+
d = xd.parse(input)
|
|
83
|
+
return d['TrainingCenterDatabase']
|
|
84
|
+
|
|
85
|
+
def tcxgetactivities(d):
|
|
86
|
+
try:
|
|
87
|
+
return d['Activities']['Activity']
|
|
88
|
+
except KeyError:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def tcxactivitygetid(d):
|
|
92
|
+
try:
|
|
93
|
+
return d['Id']
|
|
94
|
+
except KeyError:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
def tcxactivitygetlaps(d):
|
|
98
|
+
try:
|
|
99
|
+
return d['Lap']
|
|
100
|
+
except KeyError:
|
|
101
|
+
return None
|
|
102
|
+
except TypeError:
|
|
103
|
+
try:
|
|
104
|
+
return d[0]['Lap']
|
|
105
|
+
except KeyError:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def tcxlapgettrack(d):
|
|
109
|
+
try:
|
|
110
|
+
return d['Track']
|
|
111
|
+
except KeyError:
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
def tcxtrackgettrackpoint(d):
|
|
115
|
+
try:
|
|
116
|
+
return d['Trackpoint']
|
|
117
|
+
except KeyError:
|
|
118
|
+
return None
|
|
119
|
+
except TypeError:
|
|
120
|
+
try:
|
|
121
|
+
return d[0]['Trackpoint']
|
|
122
|
+
except KeyError:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
def getvalue(x,key):
|
|
126
|
+
try:
|
|
127
|
+
return x[key]
|
|
128
|
+
except TypeError:
|
|
129
|
+
return np.nan
|
|
130
|
+
|
|
131
|
+
def tcxtrack_getdata(track):
|
|
132
|
+
trackpoints = tcxtrackgettrackpoint(track)
|
|
133
|
+
df = pd.DataFrame(trackpoints)
|
|
134
|
+
datetime = df['Time'].apply(lambda x: parser.parse(x, fuzzy=True))
|
|
135
|
+
df['timestamp'] = datetime.apply(
|
|
136
|
+
lambda x: arrow.get(x).timestamp()+arrow.get(x).microsecond/1.e6
|
|
137
|
+
)
|
|
138
|
+
try:
|
|
139
|
+
#df['latitude'] = df['Position'].apply(lambda x: x['LatitudeDegrees'])
|
|
140
|
+
#df['longitude'] = df['Position'].apply(lambda x: x['LongitudeDegrees'])
|
|
141
|
+
df['latitude'] = df['Position'].apply(
|
|
142
|
+
lambda x: getvalue(x,'LatitudeDegrees'))
|
|
143
|
+
df['longitude'] = df['Position'].apply(
|
|
144
|
+
lambda x: getvalue(x,'LongitudeDegrees'))
|
|
145
|
+
except KeyError:
|
|
146
|
+
pass
|
|
147
|
+
except TypeError:
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
for key in df.keys():
|
|
151
|
+
v = df[key].dropna().values
|
|
152
|
+
try:
|
|
153
|
+
if len(v) and 'Value' in v[0]:
|
|
154
|
+
l = df[key].apply(pd.Series)
|
|
155
|
+
df[key] = l['Value']
|
|
156
|
+
except TypeError:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
if key == 'Extensions':
|
|
160
|
+
extensionsdf = df[key].apply(pd.Series)
|
|
161
|
+
thekeys = list(extensionsdf.keys())
|
|
162
|
+
for counter, key in enumerate(thekeys):
|
|
163
|
+
if key:
|
|
164
|
+
df['extension'+str(counter)] = key
|
|
165
|
+
l = extensionsdf[key].apply(pd.Series)
|
|
166
|
+
if 'Extensions' in list(l.keys()):
|
|
167
|
+
#print 'aap'
|
|
168
|
+
l = l.apply(pd.Series)['Extensions'].apply(pd.Series)
|
|
169
|
+
for kk in l.keys():
|
|
170
|
+
if kk != 0 and 'xmlns' not in kk:
|
|
171
|
+
df[kk] = l[kk]
|
|
172
|
+
|
|
173
|
+
return df
|
|
174
|
+
|
|
175
|
+
def tcxtodf(path):
|
|
176
|
+
|
|
177
|
+
data = tcx_getdict(path)
|
|
178
|
+
activity = tcxgetactivities(data)
|
|
179
|
+
|
|
180
|
+
laps = tcxactivitygetlaps(activity)
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
track = tcxlapgettrack(laps)
|
|
184
|
+
df = tcxtrack_getdata(track)
|
|
185
|
+
except TypeError:
|
|
186
|
+
df = pd.DataFrame()
|
|
187
|
+
for nr, lap in enumerate(laps):
|
|
188
|
+
track = tcxlapgettrack(lap)
|
|
189
|
+
dfi = tcxtrack_getdata(track)
|
|
190
|
+
dfi['lapid'] = nr
|
|
191
|
+
df = pd.concat([df, dfi])
|
|
192
|
+
|
|
193
|
+
return df
|
|
194
|
+
|
|
195
|
+
def process_trackpoint(trackpoint):
|
|
196
|
+
trackp = {}
|
|
197
|
+
for child in trackpoint:
|
|
198
|
+
for elem in child.iter():
|
|
199
|
+
if elem.tag == '{%s}Time'%ns1:
|
|
200
|
+
trackp['time'] = elem.text
|
|
201
|
+
if elem.tag == '{%s}DistanceMeters'%ns1:
|
|
202
|
+
trackp['distance'] = float(elem.text)
|
|
203
|
+
if elem.tag == '{%s}Cadence'%ns1:
|
|
204
|
+
try:
|
|
205
|
+
trackp['cadence'] = float(elem.text)
|
|
206
|
+
except TypeError:
|
|
207
|
+
trackp['cadence'] = 0.
|
|
208
|
+
if elem.tag == '{%s}HeartRateBpm'%ns1:
|
|
209
|
+
for hrchild in elem:
|
|
210
|
+
if hrchild.tag == '{%s}Value'%ns1:
|
|
211
|
+
try:
|
|
212
|
+
trackp['hr'] = int(hrchild.text)
|
|
213
|
+
except TypeError:
|
|
214
|
+
trackp['hr'] = 0
|
|
215
|
+
if elem.tag == '{%s}Extensions'%ns1:
|
|
216
|
+
for extchild in elem:
|
|
217
|
+
if extchild.tag == '{%s}TPX'%ns2:
|
|
218
|
+
for pchild in extchild:
|
|
219
|
+
if pchild.tag == '{%s}Watts'%ns2:
|
|
220
|
+
try:
|
|
221
|
+
trackp['power'] = float(pchild.text)
|
|
222
|
+
except TypeError:
|
|
223
|
+
trackp['power'] = 0
|
|
224
|
+
if elem.tag == '{%s}Position'%ns1:
|
|
225
|
+
for poschild in elem:
|
|
226
|
+
if poschild.tag == '{%s}LatitudeDegrees'%ns1:
|
|
227
|
+
trackp['latitude'] = float(poschild.text)
|
|
228
|
+
if poschild.tag == '{%s}LongitudeDegrees'%ns1:
|
|
229
|
+
trackp['longitude'] = float(poschild.text)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
return trackp
|
|
233
|
+
|
|
234
|
+
import os
|
|
235
|
+
|
|
236
|
+
def tcxtodf2(path):
|
|
237
|
+
extension = path[-3:].lower()
|
|
238
|
+
#p = etree.XMLParser(recover=True)
|
|
239
|
+
p = etree.XMLParser()
|
|
240
|
+
try:
|
|
241
|
+
if extension == '.gz':
|
|
242
|
+
with gzip.open(path,'r') as f:
|
|
243
|
+
input = f.read()
|
|
244
|
+
input = input.lstrip()
|
|
245
|
+
input = strip_control_characters(input)
|
|
246
|
+
else:
|
|
247
|
+
with open(path, 'r') as f:
|
|
248
|
+
input = f.read()
|
|
249
|
+
input = input.lstrip()
|
|
250
|
+
input = strip_control_characters(input)
|
|
251
|
+
|
|
252
|
+
with open('temp_xml.tcx','w') as f:
|
|
253
|
+
f.write(input)
|
|
254
|
+
|
|
255
|
+
tree = etree.parse('temp_xml.tcx',parser=p)
|
|
256
|
+
os.remove('temp_xml.tcx')
|
|
257
|
+
except (TypeError,XMLSyntaxError):
|
|
258
|
+
tree = etree.parse(path,parser=p)
|
|
259
|
+
|
|
260
|
+
root = tree.getroot()
|
|
261
|
+
|
|
262
|
+
tracks = []
|
|
263
|
+
lapnr = 0
|
|
264
|
+
|
|
265
|
+
if root is None:
|
|
266
|
+
return pd.DataFrame()
|
|
267
|
+
for element in root.iter():
|
|
268
|
+
if element.tag == '{%s}Lap'%ns1:
|
|
269
|
+
lapnr += 1
|
|
270
|
+
if element.tag == '{%s}Track'%ns1:
|
|
271
|
+
tracks.append((lapnr,element))
|
|
272
|
+
|
|
273
|
+
t = []
|
|
274
|
+
d = []
|
|
275
|
+
hr = []
|
|
276
|
+
power = []
|
|
277
|
+
lat = []
|
|
278
|
+
cadence = []
|
|
279
|
+
lon = []
|
|
280
|
+
lapid = []
|
|
281
|
+
|
|
282
|
+
for lapnr,element in tracks:
|
|
283
|
+
for child in element:
|
|
284
|
+
if child.tag == '{%s}Trackpoint'%ns1:
|
|
285
|
+
trackp = process_trackpoint(child)
|
|
286
|
+
try:
|
|
287
|
+
time = parser.parse(trackp['time'])
|
|
288
|
+
timestamp = arrow.get(time).timestamp()+arrow.get(time).microsecond/1.e6
|
|
289
|
+
t.append(timestamp)
|
|
290
|
+
except KeyError:
|
|
291
|
+
t.append(np.nan)
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
d.append(trackp['distance'])
|
|
295
|
+
except KeyError:
|
|
296
|
+
d.append(np.nan)
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
cadence.append(trackp['cadence'])
|
|
300
|
+
except KeyError:
|
|
301
|
+
cadence.append(np.nan)
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
hr.append(trackp['hr'])
|
|
305
|
+
except KeyError:
|
|
306
|
+
hr.append(0)
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
power.append(trackp['power'])
|
|
310
|
+
except KeyError:
|
|
311
|
+
power.append(0)
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
lat.append(trackp['latitude'])
|
|
315
|
+
except KeyError:
|
|
316
|
+
lat.append(np.nan)
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
lon.append(trackp['longitude'])
|
|
320
|
+
except KeyError:
|
|
321
|
+
lon.append(np.nan)
|
|
322
|
+
|
|
323
|
+
lapid.append(lapnr)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
df = pd.DataFrame(
|
|
327
|
+
{
|
|
328
|
+
'timestamp':t,
|
|
329
|
+
'HeartRateBpm':hr,
|
|
330
|
+
'DistanceMeters':d,
|
|
331
|
+
'Cadence':cadence,
|
|
332
|
+
'Watts':power,
|
|
333
|
+
'latitude':lat,
|
|
334
|
+
'longitude':lon,
|
|
335
|
+
'lapid':lapid,
|
|
336
|
+
}
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
df['Speed'] = df['DistanceMeters'].diff()/df['timestamp'].diff()
|
|
340
|
+
df.loc[0,'Speed'] = 0
|
|
341
|
+
|
|
342
|
+
return df
|
|
343
|
+
|
|
344
|
+
def tcxtodf3(path):
|
|
345
|
+
try:
|
|
346
|
+
if path.endswith('.gz'):
|
|
347
|
+
with gzip.open(path, 'rt', encoding='utf-8', errors='replace') as gz_file:
|
|
348
|
+
# Parse XML data from the gzipped TCX file
|
|
349
|
+
xml_content = gz_file.read()
|
|
350
|
+
cleaned_xml_content = clean_string(xml_content)
|
|
351
|
+
tree = ET.fromstring(cleaned_xml_content)
|
|
352
|
+
else:
|
|
353
|
+
# Parse XML data from the regular TCX file
|
|
354
|
+
with open(path, 'r', encoding='utf-8', errors='replace') as tcx_file:
|
|
355
|
+
xml_content = tcx_file.read()
|
|
356
|
+
cleaned_xml_content = clean_string(xml_content)
|
|
357
|
+
tree = ET.fromstring(cleaned_xml_content)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
activity_data = []
|
|
361
|
+
lap_id = 0
|
|
362
|
+
for lap_node in tree.findall(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Lap"):
|
|
363
|
+
lap_id += 1
|
|
364
|
+
intensity_node = lap_node.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Intensity")
|
|
365
|
+
workoutstate = 4
|
|
366
|
+
intensity = clean_string(intensity_node.text) if intensity_node is not None else ""
|
|
367
|
+
if intensity == "Actve":
|
|
368
|
+
workoutstate = 4
|
|
369
|
+
elif intensity == "Resting":
|
|
370
|
+
workoutstate = 3
|
|
371
|
+
for trackpoint in lap_node.findall(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Trackpoint"):
|
|
372
|
+
time_node = trackpoint.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Time")
|
|
373
|
+
timestamp_str = clean_string(time_node.text)
|
|
374
|
+
time = parser.parse(timestamp_str)
|
|
375
|
+
timestamp = arrow.get(time).timestamp()+arrow.get(time).microsecond/1.e6
|
|
376
|
+
#timestamp = datetime.strptime(timestamp_str, "%Y-%m-%dT%H:%M:%S%z")
|
|
377
|
+
|
|
378
|
+
watts_node = trackpoint.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Watts")
|
|
379
|
+
watts = float(clean_string(watts_node.text)) if watts_node is not None else 0
|
|
380
|
+
|
|
381
|
+
speed_node = trackpoint.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Speed")
|
|
382
|
+
speed = float(clean_string(speed_node.text)) if speed_node is not None else 0
|
|
383
|
+
|
|
384
|
+
cadence_node = trackpoint.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Cadence")
|
|
385
|
+
cadence = int(clean_string(cadence_node.text)) if cadence_node is not None else 0
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
hr_node = trackpoint.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}HeartRateBpm/{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Value")
|
|
389
|
+
heart_rate = int(clean_string(hr_node.text)) if hr_node is not None else 0
|
|
390
|
+
|
|
391
|
+
distance_node = trackpoint.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}DistanceMeters")
|
|
392
|
+
distance = float(clean_string(distance_node.text)) if distance_node is not None else 0
|
|
393
|
+
|
|
394
|
+
latitude_node = trackpoint.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Position/{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}LatitudeDegrees")
|
|
395
|
+
longitude_node = trackpoint.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Position/{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}LongitudeDegrees")
|
|
396
|
+
latitude = float(clean_string(latitude_node.text)) if latitude_node is not None else 0
|
|
397
|
+
longitude = float(clean_string(longitude_node.text)) if longitude_node is not None else 0
|
|
398
|
+
|
|
399
|
+
activity_data.append({
|
|
400
|
+
'timestamp': timestamp,
|
|
401
|
+
'latitude': latitude,
|
|
402
|
+
'longitude': longitude,
|
|
403
|
+
'HeartRateBpm': heart_rate,
|
|
404
|
+
'DistanceMeters': distance,
|
|
405
|
+
'Cadence': cadence,
|
|
406
|
+
'Watts': watts,
|
|
407
|
+
'lapid': lap_id,
|
|
408
|
+
'Speed': speed,
|
|
409
|
+
' WorkoutState': workoutstate
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
# Create a Pandas DataFrame from the activity data
|
|
413
|
+
df = pd.DataFrame(activity_data)
|
|
414
|
+
return df
|
|
415
|
+
except ET.ParseError as e:
|
|
416
|
+
print(e)
|
|
417
|
+
return pd.DataFrame()
|