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.
Files changed (49) hide show
  1. rowingdata/__init__.py +2 -0
  2. rowingdata/__main__.py +2 -0
  3. rowingdata/boatedit.py +15 -0
  4. rowingdata/checkdatafiles.py +216 -0
  5. rowingdata/copystats.py +22 -0
  6. rowingdata/crewnerdplot.py +37 -0
  7. rowingdata/crewnerdplottime.py +37 -0
  8. rowingdata/csvparsers.py +3114 -0
  9. rowingdata/ergdataplot.py +31 -0
  10. rowingdata/ergdataplottime.py +31 -0
  11. rowingdata/ergdatatotcx.py +32 -0
  12. rowingdata/ergstickplot.py +31 -0
  13. rowingdata/ergstickplottime.py +32 -0
  14. rowingdata/ergsticktotcx.py +32 -0
  15. rowingdata/example.csv +5171 -0
  16. rowingdata/gpxtools.py +70 -0
  17. rowingdata/gpxwrite.py +151 -0
  18. rowingdata/konkatenaadje.py +19 -0
  19. rowingdata/laptesting.py +293 -0
  20. rowingdata/obsolete.py +654 -0
  21. rowingdata/otherparsers.py +718 -0
  22. rowingdata/painsled_desktop_plot.py +30 -0
  23. rowingdata/painsled_desktop_plottime.py +29 -0
  24. rowingdata/painsled_desktop_toc2.py +30 -0
  25. rowingdata/painsledplot.py +27 -0
  26. rowingdata/painsledplottime.py +27 -0
  27. rowingdata/painsledtoc2.py +23 -0
  28. rowingdata/roweredit.py +15 -0
  29. rowingdata/rowingdata.py +6941 -0
  30. rowingdata/rowproplot.py +31 -0
  31. rowingdata/rowproplottime.py +31 -0
  32. rowingdata/speedcoachplot.py +31 -0
  33. rowingdata/speedcoachplottime.py +31 -0
  34. rowingdata/speedcoachtoc2.py +36 -0
  35. rowingdata/tcxplot.py +38 -0
  36. rowingdata/tcxplot_nogeo.py +38 -0
  37. rowingdata/tcxplottime.py +33 -0
  38. rowingdata/tcxplottime_nogeo.py +33 -0
  39. rowingdata/tcxtoc2.py +30 -0
  40. rowingdata/tcxtools.py +417 -0
  41. rowingdata/trainingparser.py +302 -0
  42. rowingdata/utils.py +135 -0
  43. rowingdata/windcorrected.py +48 -0
  44. rowingdata/writetcx.py +312 -0
  45. rowingdata-3.6.8.dist-info/LICENSE +21 -0
  46. rowingdata-3.6.8.dist-info/METADATA +1149 -0
  47. rowingdata-3.6.8.dist-info/RECORD +49 -0
  48. rowingdata-3.6.8.dist-info/WHEEL +5 -0
  49. rowingdata-3.6.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,718 @@
1
+ # pylint: disable=C0103, C0303
2
+ from __future__ import absolute_import
3
+ import numpy as np
4
+ import pandas as pd
5
+ from pandas import DataFrame
6
+ from lxml import objectify
7
+ from fitparse import FitFile
8
+ try:
9
+ from . import tcxtools,gpxtools
10
+ from .utils import totimestamp, geo_distance
11
+ except (ValueError,ImportError): # pragma: no cover
12
+ import tcxtools,gpxtools
13
+ from utils import totimestamp, geo_distance
14
+
15
+ import sys
16
+ if sys.version_info[0]<=2: # pragma: no cover
17
+ pythonversion = 2
18
+ else:
19
+ pythonversion = 3
20
+
21
+ import gzip
22
+ import arrow
23
+ import shutil
24
+ from datetime import datetime
25
+ from six.moves import range
26
+ import json
27
+
28
+ NAMESPACE = 'http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2'
29
+
30
+ def strip_non_ascii(string):
31
+ ''' Returns the string without non ASCII characters'''
32
+ stripped = (c for c in string if 0 < ord(c) < 127)
33
+ return ''.join(stripped)
34
+
35
+ def tofloat(x):
36
+ try:
37
+ return float(x)
38
+ except ValueError: # pragma: no cover
39
+ return np.nan
40
+
41
+ class ExcelTemplate(object):
42
+ def __init__(self,readfile): # pragma: no cover
43
+ self.readfile = readfile
44
+ xls_f = pd.ExcelFile(self.readfile)
45
+ self.xls_df = xls_f.parse('workout')
46
+
47
+ self.df = pd.DataFrame()
48
+ now = datetime.utcnow()
49
+ unixnow = arrow.get(now).timestamp
50
+ time = 0
51
+ totdistance = 0
52
+
53
+ for nr,row in self.xls_df.iterrows():
54
+ duration = row['Interval Time']
55
+ #duration = datetime.strptime(durationstring,"%M:%S.%f")
56
+ try:
57
+ seconds = 60*duration.minute+duration.second+duration.microsecond/1.e6
58
+ except AttributeError:
59
+ seconds = 0
60
+ if seconds:
61
+ spm = row['SPM']
62
+ if spm == np.nan:
63
+ spm = 10.
64
+ deltat = 60./spm
65
+ aantal = max(2,int(seconds/deltat)+1)
66
+ time_list = time+np.arange(aantal)*deltat
67
+ distance = row['Interval Distance']
68
+ deltad = distance/float(aantal-1)
69
+ d_list = np.arange(aantal)*deltad
70
+
71
+ velo = distance/float(seconds)
72
+ pace = 500./velo
73
+
74
+ data = pd.DataFrame({
75
+ 'time':time_list,
76
+ 'distance':d_list,
77
+ 'hr':row['Avg HR'],
78
+ 'spm':spm,
79
+ 'pace':pace,
80
+ 'velo':velo,
81
+ 'type':4,
82
+ ' lapIdx':nr
83
+ })
84
+
85
+
86
+
87
+ self.df = self.df.append(data)
88
+ time += seconds
89
+ totdistance = distance
90
+ if row['Rest Time'] != np.nan:
91
+ try:
92
+ restseconds = 60.*row['Rest Time'].minute
93
+ restseconds += row['Rest Time'].second
94
+ restseconds += row['Rest Time'].microsecond/1.0e6
95
+ except AttributeError:
96
+ restseconds = 0
97
+
98
+ if restseconds:
99
+ restdistance = row['Rest Distance']
100
+ deltat = 60./spm
101
+ aantal = int(restseconds/deltat)
102
+ time_list = time+np.arange(aantal)*deltat
103
+ try:
104
+ deltad = restdistance/float(aantal)
105
+ except ZeroDivisionError:
106
+ deltad = 0
107
+ d_list = totdistance+np.arange(aantal)*deltad
108
+
109
+ if restseconds:
110
+ try:
111
+ velo = restdistance/restseconds
112
+ pace = 500./velo
113
+ except ZeroDivisionError:
114
+ velo = 0
115
+ pace = 0
116
+ else:
117
+ velo = 0
118
+ pace = 0
119
+
120
+ data = pd.DataFrame({
121
+ 'time':time_list,
122
+ 'distance':d_list,
123
+ 'pace':pace,
124
+ 'velo':velo,
125
+ ' lapIdx':nr,
126
+ 'type':3,
127
+ })
128
+
129
+ self.df = self.df.append(data)
130
+ time += restseconds
131
+ totdistance += restdistance
132
+
133
+ try:
134
+ self.df['TimeStamp (sec)'] = unixnow+self.df['time']
135
+ except TypeError:
136
+ self.df['TimeStamp (sec)'] = self.df['time']
137
+ self.df['power'] = 2.8*self.df['velo']**3
138
+ mapping = {
139
+ 'time': ' ElapsedTime (sec)',
140
+ 'distance': ' Horizontal (meters)',
141
+ 'hr': ' HRCur (bpm)',
142
+ 'spm': ' Cadence (stokes/min)',
143
+ 'power': ' Power (watts)',
144
+ }
145
+
146
+ self.df.rename(columns = mapping, inplace=True)
147
+
148
+ def write_csv(self, *args, **kwargs): # pragma: no cover
149
+ isgzip = kwargs.pop('gzip', False)
150
+ writeFile = args[0]
151
+
152
+ data = self.df
153
+
154
+ if isgzip:
155
+ return data.to_csv(writeFile + '.gz', index_label='index',
156
+ compression='gzip')
157
+ else:
158
+ return data.to_csv(writeFile, index_label='index')
159
+
160
+ def fitsummarydata(*args, **kwargs): # pragma: no cover
161
+ from warnings import warn
162
+ warn("fitsummarydata was renamed to FitSummaryData")
163
+ return FitSummaryData(*args, **kwargs)
164
+
165
+ class FitSummaryData(object):
166
+ def __init__(self, readfile):
167
+ self.readfile = readfile
168
+ self.fitfile = FitFile(readfile, check_crc=False)
169
+ self.records = self.fitfile.messages
170
+
171
+ recorddicts = []
172
+ lapdict = []
173
+ lapcounter = 0
174
+ for record in self.records:
175
+ if record.name == 'record':
176
+ values = record.get_values()
177
+ values['lapid'] = lapcounter
178
+ recorddicts.append(values)
179
+ if record.name == 'lap':
180
+ lapcounter += 1
181
+ values = record.get_values()
182
+ values['lapid'] = lapcounter
183
+ lapdict.append(values)
184
+
185
+ self.df = pd.DataFrame(recorddicts)
186
+ self.lapdf = pd.DataFrame(lapdict)
187
+
188
+ self.summarytext = 'Work Details\n'
189
+
190
+
191
+ def setsummary(self, separator="|"):
192
+ lapcount = 0
193
+ self.summarytext += "#-{sep}SDist{sep}-Split-{sep}-SPace-{sep}-SPM-{sep}-Pwr-{sep}AvgHR{sep}MaxHR{sep}DPS-\n".format(
194
+ sep=separator
195
+ )
196
+
197
+ totaldistance = 0
198
+ totaltime = 0
199
+
200
+ dfgrouped = self.df.groupby('lapid')
201
+ for lapcount,group in dfgrouped:
202
+ intdist = int(
203
+ (self.lapdf[self.lapdf['lapid']==lapcount+1]['total_distance']).iloc[0]
204
+ )
205
+ if np.isnan(intdist): # pragma: no cover
206
+ intdist = 1
207
+ else:
208
+ intdist = int(intdist)
209
+ timestamps = group['timestamp'].apply(totimestamp)
210
+ inttime = self.lapdf[self.lapdf['lapid']==lapcount+1][
211
+ 'total_elapsed_time'
212
+ ]
213
+ inttime = float(inttime.iloc[0])
214
+ try:
215
+ intpower = int(group['power'].mean())
216
+ except KeyError:
217
+ intpower = 0
218
+ lapmin = int(inttime/60)
219
+ lapsec = int(int(10*(inttime-lapmin*60.))/10.)
220
+ try:
221
+ intvelo = group['enhanced_speed'].mean()
222
+ intpace = 500./intvelo
223
+ except KeyError: # pragma: no cover
224
+ try:
225
+ intvelo = group['speed'].mean()
226
+ intpace = 500./intvelo
227
+ except KeyError:
228
+ intvelo = 0
229
+ intpace = 0
230
+
231
+ pacemin = int(intpace/60)
232
+ pacesec = int(10*(intpace-pacemin*60.))/10.
233
+ pacestring = str(pacemin)+":"+str(pacesec)
234
+ intspm = group['cadence'].mean()
235
+ inthr = int(group['heart_rate'].mean())
236
+ intmaxhr = int(group['heart_rate'].max())
237
+ strokecount = intspm*inttime/60.
238
+ try:
239
+ intdps = intdist/float(strokecount)
240
+ except ZeroDivisionError: # pragma: no cover
241
+ intdps = 0.0
242
+
243
+ summarystring = "{nr:0>2}{sep}{intdist:0>5d}{sep}".format(
244
+ nr=lapcount+1,
245
+ sep=separator,
246
+ intdist=intdist
247
+ )
248
+
249
+ summarystring += " {lapmin:0>2}:{lapsec:0>2} {sep}".format(
250
+ lapmin=lapmin,
251
+ lapsec=lapsec,
252
+ sep=separator,
253
+ )
254
+
255
+ summarystring += "{pacemin:0>2}:{pacesec:0>3.1f}".format(
256
+ pacemin=pacemin,
257
+ pacesec=pacesec,
258
+ )
259
+
260
+ summarystring += "{sep} {intspm:0>4.1f}{sep}".format(
261
+ intspm=intspm,
262
+ sep=separator
263
+ )
264
+
265
+ summarystring += " {intpower:0>3d} {sep}".format(
266
+ intpower=intpower,
267
+ sep=separator
268
+ )
269
+
270
+ summarystring += " {inthr:0>3d} {sep}".format(
271
+ inthr=inthr,
272
+ sep=separator
273
+ )
274
+
275
+ summarystring += " {intmaxhr:0>3d} {sep}".format(
276
+ intmaxhr=intmaxhr,
277
+ sep=separator
278
+ )
279
+
280
+ summarystring += " {dps:0>3.1f}".format(
281
+ dps=intdps
282
+ )
283
+
284
+ summarystring += "\n"
285
+ self.summarytext += summarystring
286
+
287
+ # add total summary
288
+ try:
289
+ overallvelo = self.df['enhanced_speed'].mean()
290
+ except KeyError: # pragma: no cover
291
+ overallvelo = self.df['speed'].mean()
292
+
293
+ timestamps = self.df['timestamp'].apply(totimestamp)
294
+ totaltime = timestamps.max()-timestamps.min()
295
+
296
+ overallpace = 500./overallvelo
297
+
298
+ minutes = int(overallpace/60)
299
+ sec = int(10*(overallpace-minutes*60.))/10.
300
+ pacestring = str(minutes)+":"+str(sec)
301
+
302
+ totmin = int(totaltime/60)
303
+ totsec = int(int(10*(totaltime-totmin*60.))/10.)
304
+
305
+ avghr = self.df['heart_rate'].mean()
306
+ grandmaxhr = self.df['heart_rate'].max()
307
+ try:
308
+ avgpower = self.df['power'].mean()
309
+ except KeyError:
310
+ avgpower = 0
311
+ try:
312
+ avgspm = self.df['cadence'].mean()
313
+ except KeyError: # pragma: no cover
314
+ avgspm = 0
315
+ totaldistance = self.df['distance'].max()-self.df['distance'].min()
316
+ if np.isnan(totaldistance): # pragma: no cover
317
+ totaldistance = 1
318
+
319
+ strokecount = avgspm*totaltime/60.
320
+ try:
321
+ avgdps = totaldistance/strokecount
322
+ except ZeroDivisionError: # pragma: no cover
323
+ avgdps = 0
324
+
325
+
326
+ summarystring = "Workout Summary\n"
327
+ summarystring += "--{sep}{totaldistance:0>5}{sep}".format(
328
+ totaldistance=int(totaldistance),
329
+ sep=separator
330
+ )
331
+
332
+ summarystring += " {totmin:0>2}:{totsec:0>2} {sep} ".format(
333
+ totmin=totmin,
334
+ totsec=totsec,
335
+ sep=separator,
336
+ )
337
+
338
+ summarystring += pacestring+separator
339
+
340
+ summarystring += " {avgspm:0>4.1f}{sep}".format(
341
+ sep=separator,
342
+ avgspm=avgspm
343
+ )
344
+
345
+ summarystring += " {avgpower:0>3} {sep}".format(
346
+ sep=separator,
347
+ avgpower=int(avgpower)
348
+ )
349
+
350
+ summarystring += " {avghr:0>3} {sep} {grandmaxhr:0>3} {sep}".format(
351
+ avghr=int(avghr),
352
+ grandmaxhr=int(grandmaxhr),
353
+ sep=separator
354
+ )
355
+
356
+ summarystring += " {avgdps:0>3.1f}".format(
357
+ avgdps=avgdps
358
+ )
359
+
360
+ self.summarytext += summarystring
361
+
362
+ class FITParser(object):
363
+
364
+ def __init__(self, readfile):
365
+ extension = readfile[-3:].lower()
366
+ if extension == '.gz':
367
+ newfile = readfile[-3:]
368
+ with gzip.open(readfile,'rb') as f_in, open(newfile,'wb') as f_out:
369
+ shutil.copyfileobj(f_in, f_out)
370
+ self.readfile = newfile
371
+ else:
372
+ self.readfile = readfile
373
+
374
+ self.fitfile = FitFile(self.readfile, check_crc=False)
375
+
376
+ self.records = self.fitfile.messages
377
+
378
+ recorddicts = []
379
+ lapcounter = 0
380
+
381
+ for record in self.records:
382
+ if record.name == 'record':
383
+ values = record.get_values()
384
+ values['lapid'] = lapcounter
385
+ recorddicts.append(values)
386
+ if record.name == 'lap':
387
+ lapcounter += 1
388
+
389
+
390
+
391
+ self.df = pd.DataFrame(recorddicts)
392
+
393
+ # columns to lowercase - this should be easier
394
+ self.df.columns = [strip_non_ascii(x) for x in self.df.columns]
395
+ self.df.columns = [x.encode('ascii','ignore') for x in self.df.columns]
396
+ if pythonversion == 3:
397
+ # self.df.columns = [str(x) for x in self.df.columns]
398
+ self.df.columns = [x.decode('ascii') for x in self.df.columns]
399
+
400
+ self.df.rename(columns = str.lower,inplace=True)
401
+
402
+
403
+ # check column dimensions
404
+
405
+ for c in self.df.columns:
406
+ x = self.df[c]
407
+ if len(x.shape)>1: # pragma: no cover
408
+ newdf = pd.DataFrame({
409
+ c: x.iloc[:,0].values
410
+ })
411
+ self.df.drop(labels=c,axis=1,inplace=True)
412
+ self.df[c] = newdf[c]
413
+
414
+ try:
415
+ latitude = self.df['position_lat']*(180./2**31)
416
+ longitude = self.df['position_long']*(180./2**31)
417
+ except KeyError: # pragma: no cover
418
+ # no coordinates
419
+ latitude = 0
420
+ longitude = 0
421
+
422
+ try:
423
+ distance = self.df['distance']
424
+ except KeyError: # pragma: no cover
425
+ distance = pd.Series(np.zeros(len(self.df)))
426
+
427
+ self.df['position_lat'] = latitude
428
+ self.df['position_long'] = longitude
429
+
430
+
431
+ if pd.isnull(distance).all():
432
+ dist2 = np.zeros(len(distance))
433
+ for i in range(len(distance)-1):
434
+ res = geo_distance(
435
+ latitude[i],
436
+ longitude[i],
437
+ latitude[i+1],
438
+ longitude[i+1]
439
+ )
440
+ deltal = 1000.*res[0]
441
+ dist2[i+1] = dist2[i]+deltal
442
+ self.df['distance'] = dist2
443
+
444
+ try:
445
+ velo = self.df['enhanced_speed']
446
+ except KeyError: # pragma: no cover
447
+ try:
448
+ velo = self.df['speed']
449
+ except KeyError:
450
+ velo = pd.Series(np.zeros(len(self.df)))
451
+
452
+ try:
453
+ if velo.mean() >= 1000: # pragma: no cover
454
+ velo = velo/1000.
455
+ except TypeError: # pragma: no cover
456
+ pass
457
+
458
+
459
+ try:
460
+ timestamps = self.df['timestamp'].apply(totimestamp)
461
+ except AttributeError: # pragma: no cover
462
+ pass
463
+
464
+ try:
465
+ pace = 500./velo
466
+ except TypeError: # pragma: no cover
467
+ pace = pd.Series(np.zeros(len(self.df)))
468
+ elapsed_time = timestamps-timestamps.values[0]
469
+
470
+ self.df['TimeStamp (sec)'] = timestamps
471
+ self.df[' Stroke500mPace (sec/500m)'] = pace
472
+ self.df[' ElapsedTime (sec)'] = elapsed_time
473
+
474
+ hrname = 'heart_rate'
475
+ spmname = 'cadence'
476
+
477
+ if 'heart rate' in self.df.columns: # pragma: no cover
478
+ hrname = 'heart rate'
479
+
480
+ if 'stroke rate' in self.df.columns: # pragma: no cover
481
+ spmname = 'stroke rate'
482
+
483
+ newcolnames = {
484
+ 'power': ' Power (watts)',
485
+ hrname: ' HRCur (bpm)',
486
+ 'position_long': ' longitude',
487
+ 'position_lat': ' latitude',
488
+ spmname: ' Cadence (stokes/min)',
489
+ 'lapid': ' lapIdx',
490
+ 'distance': ' Horizontal (meters)'
491
+ }
492
+
493
+ self.df.rename(columns=newcolnames,inplace=True)
494
+
495
+ # timestamp
496
+ # distance
497
+ # pace
498
+ # elapsedtime
499
+
500
+
501
+ def write_csv(self, writefile="fit_o.csv", gzip=False):
502
+
503
+ if gzip: # pragma: no cover
504
+ return self.df.to_csv(writefile+'.gz', index_label='index',
505
+ compression='gzip')
506
+ else:
507
+ return self.df.to_csv(writefile, index_label='index')
508
+
509
+ class JSONParser(object): # pragma: no cover
510
+ def __init__(self, json_file):
511
+ df = pd.DataFrame()
512
+ with open(json_file,'r') as f:
513
+ data = json.load(f)
514
+
515
+ laps = data['laps']
516
+
517
+ for lap in laps:
518
+ points = lap['points']
519
+ ldf = pd.DataFrame.from_records(points)
520
+ df=df.append(ldf,ignore_index=True)
521
+
522
+
523
+ self.df = df
524
+
525
+ newcolnames = {
526
+ 'time':'TimeStamp (sec)',
527
+ 'hr':' HRCur (bpm)',
528
+ }
529
+
530
+ self.df.rename(columns=newcolnames,inplace=True)
531
+
532
+
533
+ def write_csv(self,writefile="json_o.csv", gzip = False):
534
+ if gzip:
535
+ return self.df.to_csv(writefile+'.gz', index_label='index',compression='gzip')
536
+ else:
537
+ return self.df.to_csv(writefile, index_label='index')
538
+
539
+ class TCXParserTester(object): # pragma: no cover
540
+ def __init__(self, tcx_file):
541
+ tree = objectify.parse(tcx_file)
542
+ self.root = tree.getroot()
543
+ self.activity = self.root.Activities.Activity
544
+
545
+ # need to select only trackpoints with Cadence, Distance,
546
+ # Time & HR data
547
+ self.selectionstring = '//ns:Trackpoint[descendant::ns:HeartRateBpm]'
548
+ self.selectionstring += '[descendant::ns:Cadence]'
549
+ self.selectionstring += '[descendant::ns:DistanceMeters]'
550
+ self.selectionstring += '[descendant::ns:Time]'
551
+
552
+
553
+ self.hr_values = self.root.xpath(self.selectionstring
554
+ +'//ns:HeartRateBpm/ns:Value',
555
+ namespaces={'ns': NAMESPACE})
556
+
557
+
558
+
559
+ self.distance_values = self.root.xpath(self.selectionstring
560
+ +'/ns:DistanceMeters',
561
+ namespaces={'ns': NAMESPACE})
562
+
563
+ self.spm_values = self.root.xpath(self.selectionstring
564
+ +'/ns:Cadence',
565
+ namespaces={'ns': NAMESPACE})
566
+
567
+ def getarray(self, str1, str2=''):
568
+ selectionstring = self.selectionstring
569
+ selectionstring = selectionstring+'//ns:'+str1
570
+ if str2 != '':
571
+ selectionstring = selectionstring+'/ns:'+str2
572
+
573
+ the_array = self.root.xpath(selectionstring,
574
+ namespaces={'ns': NAMESPACE})
575
+
576
+ return the_array
577
+
578
+
579
+ class GPXParser(object): # pragma: no cover
580
+ def __init__(self, gpx_file, *args, **kwargs):
581
+ self.df = gpxtools.gpxtodf2(gpx_file)
582
+
583
+ def write_csv(self, writefile='example.csv', window_size=5, gzip=False):
584
+ data = self.df
585
+ data = data.sort_values(by='TimeStamp (sec)', ascending=True)
586
+ data = data.ffill()
587
+
588
+ # drop all-zero columns
589
+ for c in data.columns:
590
+ if (data[c] == 0).any() and data[c].mean() == 0:
591
+ data = data.drop(c, axis=1)
592
+ if c == 'Position':
593
+ data = data.drop(c, axis=1)
594
+ if c == 'Extensions':
595
+ data = data.drop(c, axis=1)
596
+
597
+ if gzip:
598
+ return data.to_csv(writefile+'.gz', index_label='index',
599
+ compression='gzip')
600
+ else:
601
+ return data.to_csv(writefile, index_label='index')
602
+
603
+ class TCXParser(object):
604
+ def __init__(self, tcx_file, *args, **kwargs):
605
+ if 'alternative' in kwargs: # pragma: no cover
606
+ alternative = kwargs['alternative']
607
+ else:
608
+ alternative = False
609
+
610
+ if alternative: # pragma: no cover
611
+ self.df = tcxtools.tcxtodf(tcx_file)
612
+ else:
613
+ self.df = tcxtools.tcxtodf3(tcx_file)
614
+
615
+ try:
616
+ lat = self.df['latitude'].apply(tofloat).values
617
+ longitude = self.df['longitude'].apply(tofloat).values
618
+ except KeyError: # pragma: no cover
619
+ self.df['latitude'] = 0
620
+ self.df['longitude'] = 0
621
+ lat = self.df['latitude'].apply(tofloat).values
622
+ longitude = self.df['longitude'].apply(tofloat).values
623
+
624
+
625
+ unixtimes = self.df['timestamp'].values
626
+ try:
627
+ spm = self.df['Cadence'].apply(tofloat).values
628
+ except KeyError: # pragma: no cover
629
+ try:
630
+ spm = self.df['StrokeRate'].apply(tofloat).values
631
+ self.df['Cadence'] = self.df['StrokeRate']
632
+ except KeyError:
633
+ try:
634
+ spm = 0.0*self.df['Speed'].apply(tofloat).values
635
+ except KeyError:
636
+ spm = 0.0*unixtimes
637
+
638
+
639
+ try:
640
+ velo = self.df['Speed'].apply(tofloat)
641
+ dist2 = self.df['DistanceMeters'].apply(tofloat)
642
+ strokelength = velo*60./spm
643
+ except KeyError: # pragma: no cover
644
+ nr_rows = len(lat)
645
+ dist2 = np.zeros(nr_rows)
646
+ velo = np.zeros(nr_rows)
647
+ strokelength = np.zeros(nr_rows)
648
+ for i in range(nr_rows-1):
649
+ res = geo_distance(lat[i], longitude[i], lat[i+1], longitude[i+1])
650
+ deltal = 1000.*res[0]
651
+ dist2[i+1] = dist2[i]+deltal
652
+ try:
653
+ velo[i+1] = deltal/(1.0*(unixtimes[i+1]-unixtimes[i]))
654
+ except ZeroDivisionError:
655
+ velo[i+1] = velo[i]
656
+ if spm[i] != 0:
657
+ strokelength[i] = deltal*60/spm[i]
658
+ else: # pragma: no cover
659
+ strokelength[i] = 0.
660
+
661
+ try:
662
+ power = self.df['Watts']
663
+ except KeyError: # pragma: no cover
664
+ try:
665
+ power = self.df['ns3:Watts']
666
+ except KeyError:
667
+ power = 0*spm
668
+
669
+ self.df['Watts'] = power
670
+
671
+ p = 500./velo
672
+
673
+ self.df[' Horizontal (meters)'] = dist2
674
+ self.df[' StrokeDistance (meters)'] = strokelength
675
+ self.df[' Stroke500mPace (sec/500m)'] = p
676
+
677
+ # translate from standard TCX names to our naming convention
678
+ self.columns = {
679
+ 'timestamp':'TimeStamp (sec)',
680
+ 'Cadence': ' Cadence (stokes/min)',
681
+ 'HeartRateBpm' : ' HRCur (bpm)',
682
+ 'Watts': ' Power (watts)',
683
+ 'lapid': ' lapIdx',
684
+ 'latitude': ' latitude',
685
+ 'longitude': ' longitude',
686
+ }
687
+
688
+ self.df.rename(columns=self.columns, inplace=True)
689
+
690
+ cc = [value for key, value in self.columns.items()]
691
+
692
+ for c in cc:
693
+ if c != 'lapIdx':
694
+ try:
695
+ self.df[c] = self.df[c].astype(float)
696
+ except KeyError: # pragma: no cover
697
+ pass
698
+
699
+
700
+ def write_csv(self, writefile='example.csv', window_size=5, gzip=False):
701
+ data = self.df
702
+ data = data.sort_values(by='TimeStamp (sec)', ascending=True)
703
+ data = data.ffill()
704
+
705
+ # drop all-zero columns
706
+ for c in data.columns:
707
+ if (data[c] == 0).any() and data[c].mean() == 0:
708
+ data = data.drop(c, axis=1)
709
+ if c == 'Position': # pragma: no cover
710
+ data = data.drop(c, axis=1)
711
+ if c == 'Extensions': # pragma: no cover
712
+ data = data.drop(c, axis=1)
713
+
714
+ if gzip: # pragma: no cover
715
+ return data.to_csv(writefile+'.gz', index_label='index',
716
+ compression='gzip')
717
+ else:
718
+ return data.to_csv(writefile, index_label='index')