Skip to content

Commit

Permalink
0.4.0
Browse files Browse the repository at this point in the history
Working version of new improved tcxreader. Warning, breaking changes!
  • Loading branch information
alenrajsp committed Aug 7, 2022
1 parent 6ea4e68 commit 8671d64
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .github/images/data-explanation.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
158 changes: 121 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
**tcxreader** is a reader for Garmin's TCX file format. It also works well with missing data!

# Warning!
This is a DEV branch. For future 0.4.0 version.
Breaking changes between versions **0.3.15** and **0.4.0**.
### New version (0.4.0+ is not compatible with previous versions)
### Breaking changes between versions **0.3.15** and **0.4.0**.
- Due to: new way of reading TPX and LX extensions in TCX files

## Objective

Expand All @@ -22,6 +23,11 @@ GitHub requests appreciated.
[pypi](https://pypi.org/project/tcxreader/)
[github](https://github.com/alenrajsp/tcxreader)

## Features

Allows parsing / reading of TCX files.


## Installation

```
Expand All @@ -36,53 +42,131 @@ An example on how to use the package is shown below.
from tcxreader.tcxreader import TCXReader, TCXTrackPoint

tcx_reader = TCXReader()
file_location = 'example_data/15.tcx'
file_location = 'example_data/cross-country-skiing_activity_1.tcx'

data: TCXTrackPoint = tcx_reader.read(file_location)
""" Example output:
data = {TCXExercise}
activity_type = {str} 'Biking'
ascent = {float} 1404.400026500225
avg_speed = {float} 24.285282782608693
cadence_avg = {NoneType} None
cadence_max = {NoneType} None
calories = {int} 2010
descent = {float} 1422.000026166439
distance = {float} 116366.98
duration = {float} 17250.0
end_time = {datetime} 2015-02-19 14:18:59+00:00
hr_avg = {float} 140.59545804464972
hr_max = {int} 200
hr_min = {int} 94
altitude_max = {float}
altitude_min = {float}
altitude_avg = {float}
max_speed = {float} 18.95800018310547
start_time = {datetime} 2015-02-19 09:31:29+00:00
trackpoints = {list: 7799} [TCXTrackPoint]
activity_type = {str} 'Other'
altitude_avg = {float} 2285.6744874553915
altitude_max = {float} 2337.60009765625
altitude_min = {float} 1257.5999755859375
ascent = {float} 1117.9996337890625
author = {TCXAuthor} [TCXAuthor]
avg_speed = {float} 8.534458975426906
cadence_avg = {NoneType} None
cadence_max = {NoneType} None
calories = {int} 532
descent = {float} 118.19970703125
distance = {float} 5692.01
duration = {float} 2401.0
end_time = {datetime} 2020-12-26 15:54:22
hr_avg = {float} 141.1954732510288
hr_max = {int} 172
hr_min = {int} 83
laps = {list: 2} [TCXLap]
lx_ext = {dict: 0} {}
max_speed = {float} 23.50810546875
start_time = {datetime} 2020-12-26 15:14:21
tpx_ext_stats = {dict: 2} {'Speed': {'min': 0.0, 'max': 6.1579999923706055, 'avg': 2.2930514418784482}, 'RunCadence': {'min': 0, 'max': 95, 'avg': 40.81069958847737}}
trackpoints = {list: 486} [TCXTrackpoint]
{TCXTrackPoint}
TPX_speed = {float} 5.011000156402588
cadence = {float} 80
distance = {float} 514.0499877929688
elevation = {float} 46.79999923706055
hr_value = {int} 134
latitude = {float} 45.5244944896549
longitude = {float} 13.596355207264423
time = {datetime} 2015-02-19 09:34:17+00:00
watts = {float} 123
{TCXTrackPoint}
cadence = {NoneType} None
distance = {float} 7.329999923706055
elevation = {float} 2250.60009765625
hr_value = {int} 87
latitude = {float} 46.49582446552813
longitude = {float} 15.50408081151545
time = {datetime} 2020-12-26 15:14:28
tpx_ext = {dict: 2} {'Speed': 0.7459999918937683, 'RunCadence': 58}
"""
```
## Data classes
## Classes explanation

Below figure explains the classes of **tcxreader** and the data they contain.

## TCXReader()
User initializes the tcxreader by creating a **TCXReader** class instance. To read the data of a **TCX activity** the user must use
**TCXReader.read(*filename*)** method.
The output of **read()** is an instance of **TCXExercise** class.

### TCXExercise
Primary class that holds cumulative data of an exercise. TCXExercise contains **all** the **trackpoints** of an activity
(e.g. from all the laps merged).

### TCXLap
One TCX activity may contain multiple laps. In the TCX file they are visible by the **Lap** tag.
```xml
<Lap StartTime="2020-12-26T15:50:22.000Z">
...
</Lap>
```
TCXLap contains all the trackpoints of a lap.

### TCXTrackpoint
A point in an exercise. Almost always has **latitude, longitude, time**. Can also have *cadence, distance, elevation, hr_value, tpx_ext*.
The tpx_ext refers to individual extensions contained inside the trackpoint. An example of the Trackpoint (pre-parsing)
in the TCX file is shown below.

```xml
<Trackpoint>
<Time>2020-12-26T15:50:21.000Z</Time>
<Position>
<LatitudeDegrees>46.49732105433941</LatitudeDegrees>
<LongitudeDegrees>15.496849408373237</LongitudeDegrees>
</Position>
<AltitudeMeters>2277.39990234375</AltitudeMeters>
<DistanceMeters>5001.52978515625</DistanceMeters>
<HeartRateBpm>
<Value>148</Value>
</HeartRateBpm>
<Extensions>
<ns3:TPX>
<ns3:Speed>3.3589999675750732</ns3:Speed>
<ns3:RunCadence>61</ns3:RunCadence>
</ns3:TPX>
</Extensions>
</Trackpoint>
```

### tpx_ext
The data parsed from the **trackpoint TPX Extensions**. Example of data (pre-parsing) is shown below.
```xml
<Extensions>
<ns3:TPX>
<ns3:Speed>3.3589999675750732</ns3:Speed>
<ns3:RunCadence>61</ns3:RunCadence>
</ns3:TPX>
</Extensions>
```
Can occur **once (1x)** in every **trackpoint**.
### tpx_ext_stats
Contains **minimum**, **maximum** and **average** values of the recorded **tpx_ext** key.

### lx_ext
The data parsed from the **lap LX Extensions**. Example of data (pre-parsing) is shown below.
```xml
<Extensions>
<ns3:LX>
<ns3:AvgSpeed>1.0820000171661377</ns3:AvgSpeed>
<ns3:Steps>65</ns3:Steps>
</ns3:LX>
</Extensions>
```
Can occur **once (1x)** in every **lap**.

The tags which do not contain **Avg, Min, Max** in their name (e.g. steps) are
summed in the **TCXExercise** **lx_ext** dictionary.

All tags are recorded in the **TCXLap** **lx_ext** dictionary


### Schema of the data
<div width="100%" style="background-color: white; width: 100%">
<img width="100%" style="margin-bottom:-8px" src="https://raw.githubusercontent.com/alenrajsp/tcxreader/7c9af6dc88f9d83a8c6751b454f118220ecfd9a1/.github/images/data-explanation.svg">
</div>

## Features

Allows parsing / reading of TCX files.

## Datasets

Datasets available and used in the examples on the following links: [DATASET1](http://iztok-jr-fister.eu/static/publications/Sport5.zip)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setuptools.setup(
name="tcxreader",
version="0.3.15",
version="0.4.0",
author="Alen Rajšp",
author_email="[email protected]",
description="tcxreader is a reader for Garmin’s TCX file format. It also works well with missing data!",
Expand Down
4 changes: 4 additions & 0 deletions tcxreader/tcx_exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def __init__(self, trackpoints: [TCXTrackPoint] = None, activity_type: str = Non
:param altitude_min: minimum altitude during the exercise
:param altitude_max: maxiumum altitude during the exersice
:param author: describes who recorded the data, e.g. which device
:param tpx_ext_stats: contains statistics (min, max, avg) of TPX extenstion data of trackpoints
:param lx_ext: contains sum of LX extension data for each key in LX extension tag
:param laps: contains subset of data for each lap
"""

self.trackpoints = trackpoints
Expand Down
3 changes: 3 additions & 0 deletions tcxreader/tcx_lap.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def __init__(self, trackpoints: [TCXTrackPoint] = None, calories: int = None,
):
"""
Similar to TCXExercise, but is a container class for a lap.
:param tpx_ext_stats: contains statistics (min, max, avg) of TPX extenstion data of trackpoints
:param lx_ext: contains LX extension data
:param trackpoints: List of TCXTrackPoint objects
:param calories: total calories used in an exercise
:param hr_avg: maxiumum heartrate achieved during the exercise
Expand All @@ -29,6 +31,7 @@ def __init__(self, trackpoints: [TCXTrackPoint] = None, calories: int = None,
:param altitude_avg: average altitude in meters
:param altitude_min: minimum altitude during the exercise
:param altitude_max: maxiumum altitude during the exersice
"""

self.trackpoints = trackpoints
Expand Down
32 changes: 24 additions & 8 deletions tcxreader/tcxreader.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ def read(self, fileLocation: str, only_gps: bool = True) -> TCXExercise:

tcx_exercise.trackpoints = trackpoints

tcx_exercise = self.__find_hi_lo_avg(tcx_exercise)
tcx_exercise = self.__find_hi_lo_avg(tcx_exercise, only_gps)
for lap in tcx_exercise.laps:
self.__find_hi_lo_avg(lap)
self.__find_hi_lo_avg(lap, only_gps)
return tcx_exercise

def trackpoint_parser(self, tcx_point:TCXTrackPoint, trackpoint):
Expand Down Expand Up @@ -143,8 +143,21 @@ def trackpoint_parser(self, tcx_point:TCXTrackPoint, trackpoint):
else:
tag_value=int(tag_value)
tcx_point.tpx_ext[tag_name]=tag_value
def __find_hi_lo_avg(self, tcx: TCXExercise) -> TCXExercise:
def __find_hi_lo_avg(self, tcx: TCXExercise, only_gps) -> TCXExercise:
trackpoints = tcx.trackpoints

if only_gps == True:
removalList = []
for index in range(len(trackpoints)):
if trackpoints[index].longitude == None:
removalList.append(index)

for removal in sorted(removalList, reverse=True):
del trackpoints[removal]

tcx.trackpoints = trackpoints


hr = []
altitude = []
cadence = []
Expand Down Expand Up @@ -215,11 +228,14 @@ def __find_hi_lo_avg(self, tcx: TCXExercise) -> TCXExercise:
max_speed = 0.0
for index in range(len(tcx.trackpoints)):
if skip!=True:
time = abs((tcx.trackpoints[index-1].time - tcx.trackpoints[index].time).total_seconds())
distance = abs(tcx.trackpoints[index-1].distance - tcx.trackpoints[index].distance)
speed = distance/time*3.6
if speed>max_speed:
max_speed=speed
try:
time = abs((tcx.trackpoints[index-1].time - tcx.trackpoints[index].time).total_seconds())
distance = abs(tcx.trackpoints[index-1].distance - tcx.trackpoints[index].distance)
speed = distance/time*3.6
if speed>max_speed:
max_speed=speed
except Exception:
a=100
skip=False
tcx.max_speed=max_speed

Expand Down

0 comments on commit 8671d64

Please sign in to comment.