tcxread 0.1.2 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/README.md +83 -0
- data/lib/tcxread.rb +67 -53
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b17178126aaf26d70869aa047a6054ca4128ac454c75b3fbd0903ce935222dc
|
4
|
+
data.tar.gz: f27e41c68c87672a7acd2791b4a147c8a0bf09a2da1707a8722091a965508b89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9281010f7900f5bf0a579c648690329e0f5680ecb9cc91e56733a2718ce15ac75cdfa15eabb4fc4f97b99d6cd26dfbbb53c627124775261fa1083797f9c1d4c1
|
7
|
+
data.tar.gz: c61eee523a2b34730e40cddce7cd2eb58193bcb9585b84c0d08252f66cd8f4e90f49b6bf77dcbebdf13d8e96336fd5365787a3e670fda68b26cab214a540cd26
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2022-2024 Iztok Fister Jr.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
<h1 align="center">
|
2
|
+
tcxread -- A parser for TCX files written in Ruby
|
3
|
+
</h1>
|
4
|
+
|
5
|
+
<p align="center">
|
6
|
+
<a href="https://badge.fury.io/rb/tcxread">
|
7
|
+
<img alt="Gem Version" src="https://badge.fury.io/rb/tcxread.svg">
|
8
|
+
</a>
|
9
|
+
<a href="https://github.com/firefly-cpp/tcxread/blob/master/LICENSE">
|
10
|
+
<img alt="License" src="https://img.shields.io/github/license/firefly-cpp/tcxread.svg">
|
11
|
+
</a>
|
12
|
+
<a href=https://repology.org/project/ruby:tcxread/versions>
|
13
|
+
<img alt="Packaging status" src="https://repology.org/badge/tiny-repos/ruby:tcxread.svg">
|
14
|
+
</a>
|
15
|
+
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/w/firefly-cpp/tcxread.svg">
|
16
|
+
<img alt="GitHub repo size" src="https://img.shields.io/github/repo-size/firefly-cpp/tcxread">
|
17
|
+
</p>
|
18
|
+
|
19
|
+
<p align="center">
|
20
|
+
<a href="#-installation">📦 Installation</a> •
|
21
|
+
<a href="#-basic-run-example">🚀 Basic run example</a> •
|
22
|
+
<a href="#-datasets">💾 Datasets</a> •
|
23
|
+
<a href="#-further-read">📖 Further read</a> •
|
24
|
+
<a href="#-related-packagesframeworks">🔗 Related packages/frameworks</a> •
|
25
|
+
<a href="#-license">🔑 License</a>
|
26
|
+
</p>
|
27
|
+
|
28
|
+
tcxread is a Ruby package designed to simplify the process of reading and processing .tcx files, commonly used by Garmin devices and other GPS-enabled fitness devices to store workout data.
|
29
|
+
|
30
|
+
## 📦 Installation
|
31
|
+
|
32
|
+
```sh
|
33
|
+
$ gem install tcxread
|
34
|
+
```
|
35
|
+
|
36
|
+
## 🚀 Basic run example
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
require 'tcxread'
|
40
|
+
|
41
|
+
data = TCXRead.new('23.tcx')
|
42
|
+
|
43
|
+
puts "Distance meters: #{data.total_distance_meters}, " \
|
44
|
+
"Time seconds: #{data.total_time_seconds}, " \
|
45
|
+
"Calories: #{data.total_calories}, " \
|
46
|
+
"Total ascent: #{data.total_ascent}, " \
|
47
|
+
"Total descent: #{data.total_descent}, " \
|
48
|
+
"Max altitude: #{data.max_altitude}, " \
|
49
|
+
"Average heart rate: #{data.average_heart_rate}, " \
|
50
|
+
"Average watts: #{data.average_watts}, " \
|
51
|
+
"Max watts: #{data.max_watts}, " \
|
52
|
+
"Average speed: #{data.average_speed_all}, " \
|
53
|
+
"Average speed (moving): #{data.average_speed_moving}, " \
|
54
|
+
"Average cadence (moving): #{data.average_cadence_biking}, " \
|
55
|
+
"Average cadence: #{data.average_cadence_all}"
|
56
|
+
|
57
|
+
```
|
58
|
+
|
59
|
+
## 💾 Datasets
|
60
|
+
|
61
|
+
Datasets available and used in the examples on the following links: [DATASET1](http://iztok-jr-fister.eu/static/publications/Sport5.zip), [DATASET2](http://iztok-jr-fister.eu/static/css/datasets/Sport.zip), [DATASET3](https://github.com/firefly-cpp/tcx-test-files).
|
62
|
+
|
63
|
+
## 📖 Further read
|
64
|
+
|
65
|
+
[1] [Awesome Computational Intelligence in Sports](https://github.com/firefly-cpp/awesome-computational-intelligence-in-sports)
|
66
|
+
|
67
|
+
## 🔗 Related packages/frameworks
|
68
|
+
|
69
|
+
[1] [tcxreader: Python reader/parser for Garmin's TCX file format.](https://github.com/alenrajsp/tcxreader)
|
70
|
+
|
71
|
+
[2] [sport-activities-features: A minimalistic toolbox for extracting features from sports activity files written in Python](https://github.com/firefly-cpp/sport-activities-features)
|
72
|
+
|
73
|
+
[3] [TCXReader.jl: Julia package designed for parsing TCX files](https://github.com/firefly-cpp/TCXReader.jl)
|
74
|
+
|
75
|
+
[4] [TCXWriter: A Tiny Library for writing/creating TCX files on Arduino](https://github.com/firefly-cpp/tcxwriter)
|
76
|
+
|
77
|
+
## 🔑 License
|
78
|
+
|
79
|
+
This package is distributed under the MIT License. This license can be found online at <http://www.opensource.org/licenses/MIT>.
|
80
|
+
|
81
|
+
## Disclaimer
|
82
|
+
|
83
|
+
This framework is provided as-is, and there are no guarantees that it fits your purposes or that it is bug-free. Use it at your own risk!
|
data/lib/tcxread.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
require "nokogiri"
|
2
2
|
|
3
|
-
# TCXRead is a class that parses TCX (Training Center XML) files to extract
|
4
|
-
# workout data such as activities, laps, tracks, trackpoints, and integral metrics.
|
5
3
|
class TCXRead
|
6
4
|
attr_reader :total_distance_meters, :total_time_seconds, :total_calories,
|
7
5
|
:total_ascent, :total_descent, :max_altitude, :average_heart_rate,
|
8
|
-
:max_watts, :average_watts, :
|
6
|
+
:max_watts, :average_watts, :average_cadence_all, :average_cadence_biking,
|
7
|
+
:average_speed_all, :average_speed_moving
|
9
8
|
|
10
9
|
def initialize(file_path)
|
11
10
|
@file_path = file_path
|
@@ -19,17 +18,16 @@ class TCXRead
|
|
19
18
|
@total_descent = 0
|
20
19
|
@max_altitude = 0
|
21
20
|
@average_heart_rate = 0
|
22
|
-
# use NA if no watts exist in the TCX file
|
23
21
|
@max_watts = 'NA'
|
24
22
|
@average_watts = 'NA'
|
25
|
-
@
|
23
|
+
@average_cadence_all = 0
|
24
|
+
@average_cadence_biking = 0
|
25
|
+
@average_speed_all = 0
|
26
|
+
@average_speed_moving = 0
|
26
27
|
|
27
28
|
parse
|
28
29
|
end
|
29
30
|
|
30
|
-
# Parses the TCX file and extracts data.
|
31
|
-
#
|
32
|
-
# @return [Hash] a hash containing the parsed activities.
|
33
31
|
def parse
|
34
32
|
activities = parse_activities
|
35
33
|
if activities.any?
|
@@ -39,7 +37,12 @@ class TCXRead
|
|
39
37
|
@total_ascent, @total_descent, @max_altitude = calculate_ascent_descent_and_max_altitude_from_activities(activities)
|
40
38
|
@average_heart_rate = calculate_average_heart_rate_from_activities(activities)
|
41
39
|
@max_watts, @average_watts = calculate_watts_from_activities(activities)
|
42
|
-
|
40
|
+
cadence_results = calculate_average_cadence_from_activities(activities)
|
41
|
+
@average_cadence_all = cadence_results[:average_cadence_all]
|
42
|
+
@average_cadence_biking = cadence_results[:average_cadence_biking]
|
43
|
+
speed_results = calculate_average_speed_from_activities(activities)
|
44
|
+
@average_speed_all = speed_results[:average_speed_all]
|
45
|
+
@average_speed_moving = speed_results[:average_speed_moving]
|
43
46
|
end
|
44
47
|
|
45
48
|
{ activities: activities }
|
@@ -47,9 +50,6 @@ class TCXRead
|
|
47
50
|
|
48
51
|
private
|
49
52
|
|
50
|
-
# Parses the activities from the TCX file.
|
51
|
-
#
|
52
|
-
# @return [Array<Hash>] an array of hashes, each representing an activity.
|
53
53
|
def parse_activities
|
54
54
|
activities = []
|
55
55
|
@doc.xpath('//xmlns:Activities/xmlns:Activity').each do |activity|
|
@@ -76,10 +76,6 @@ class TCXRead
|
|
76
76
|
activities
|
77
77
|
end
|
78
78
|
|
79
|
-
# Parses the laps for a given activity.
|
80
|
-
#
|
81
|
-
# @param activity [Nokogiri::XML::Element] the activity element from the TCX file.
|
82
|
-
# @return [Array<Hash>] an array of hashes, each representing a lap.
|
83
79
|
def parse_laps(activity)
|
84
80
|
laps = []
|
85
81
|
activity.xpath('xmlns:Lap').each do |lap|
|
@@ -100,10 +96,6 @@ class TCXRead
|
|
100
96
|
laps
|
101
97
|
end
|
102
98
|
|
103
|
-
# Parses the tracks for a given lap.
|
104
|
-
#
|
105
|
-
# @param lap [Nokogiri::XML::Element] the lap element from the TCX file.
|
106
|
-
# @return [Array<Array<Hash>>] an array of arrays, each representing a track containing trackpoints.
|
107
99
|
def parse_tracks(lap)
|
108
100
|
tracks = []
|
109
101
|
lap.xpath('xmlns:Track').each do |track|
|
@@ -125,10 +117,6 @@ class TCXRead
|
|
125
117
|
tracks
|
126
118
|
end
|
127
119
|
|
128
|
-
# Parses the position for a given trackpoint.
|
129
|
-
#
|
130
|
-
# @param trackpoint [Nokogiri::XML::Element] the trackpoint element from the TCX file.
|
131
|
-
# @return [Hash, nil] a hash representing the position (latitude and longitude) or nil if no position is available.
|
132
120
|
def parse_position(trackpoint)
|
133
121
|
position = trackpoint.at_xpath('xmlns:Position')
|
134
122
|
return nil unless position
|
@@ -139,10 +127,6 @@ class TCXRead
|
|
139
127
|
}
|
140
128
|
end
|
141
129
|
|
142
|
-
# Calculates the total ascent, total descent, and maximum altitude from the laps.
|
143
|
-
#
|
144
|
-
# @param laps [Array<Hash>] an array of lap hashes.
|
145
|
-
# @return [Array<Float>] an array containing total ascent, total descent, and maximum altitude.
|
146
130
|
def calculate_ascent_descent_and_max_altitude(laps)
|
147
131
|
total_ascent = 0.0
|
148
132
|
total_descent = 0.0
|
@@ -170,10 +154,6 @@ class TCXRead
|
|
170
154
|
[total_ascent, total_descent, max_altitude]
|
171
155
|
end
|
172
156
|
|
173
|
-
# Calculates the total ascent, total descent, and maximum altitude from the activities.
|
174
|
-
#
|
175
|
-
# @param activities [Array<Hash>] an array of activity hashes.
|
176
|
-
# @return [Array<Float>] an array containing total ascent, total descent, and maximum altitude.
|
177
157
|
def calculate_ascent_descent_and_max_altitude_from_activities(activities)
|
178
158
|
total_ascent = 0.0
|
179
159
|
total_descent = 0.0
|
@@ -188,10 +168,6 @@ class TCXRead
|
|
188
168
|
[total_ascent, total_descent, max_altitude]
|
189
169
|
end
|
190
170
|
|
191
|
-
# Calculates the average heart rate from the laps.
|
192
|
-
#
|
193
|
-
# @param laps [Array<Hash>] an array of lap hashes.
|
194
|
-
# @return [Float] the average heart rate.
|
195
171
|
def calculate_average_heart_rate(laps)
|
196
172
|
total_heart_rate = 0
|
197
173
|
heart_rate_count = 0
|
@@ -209,10 +185,6 @@ class TCXRead
|
|
209
185
|
heart_rate_count > 0 ? total_heart_rate.to_f / heart_rate_count : 0.0
|
210
186
|
end
|
211
187
|
|
212
|
-
# Calculates the average heart rate from the activities.
|
213
|
-
#
|
214
|
-
# @param activities [Array<Hash>] an array of activity hashes.
|
215
|
-
# @return [Float] the average heart rate.
|
216
188
|
def calculate_average_heart_rate_from_activities(activities)
|
217
189
|
total_heart_rate = 0
|
218
190
|
heart_rate_count = 0
|
@@ -232,10 +204,6 @@ class TCXRead
|
|
232
204
|
heart_rate_count > 0 ? total_heart_rate.to_f / heart_rate_count : 0.0
|
233
205
|
end
|
234
206
|
|
235
|
-
# Calculates the maximum and average watts from the activities.
|
236
|
-
#
|
237
|
-
# @param activities [Array<Hash>] an array of activity hashes.
|
238
|
-
# @return [Array<Float, Float>] an array containing maximum watts and average watts. Returns 'NA' for both if no watts data is available.
|
239
207
|
def calculate_watts_from_activities(activities)
|
240
208
|
max_watts = 0
|
241
209
|
total_watts = 0
|
@@ -265,26 +233,72 @@ class TCXRead
|
|
265
233
|
[max_watts, average_watts]
|
266
234
|
end
|
267
235
|
|
268
|
-
# Calculates the average cadence from the activities.
|
269
|
-
#
|
270
|
-
# @param activities [Array<Hash>] an array of activity hashes.
|
271
|
-
# @return [Float] the average cadence.
|
272
236
|
def calculate_average_cadence_from_activities(activities)
|
273
|
-
|
274
|
-
|
237
|
+
total_cadence_all = 0
|
238
|
+
total_cadence_biking = 0
|
239
|
+
cadence_count_all = 0
|
240
|
+
cadence_count_biking = 0
|
275
241
|
|
276
242
|
activities.each do |activity|
|
277
243
|
activity[:laps].each do |lap|
|
278
244
|
lap[:tracks].flatten.each do |trackpoint|
|
279
245
|
cadence = trackpoint[:cadence]
|
246
|
+
total_cadence_all += cadence
|
247
|
+
cadence_count_all += 1
|
248
|
+
|
280
249
|
if cadence > 0
|
281
|
-
|
282
|
-
|
250
|
+
total_cadence_biking += cadence
|
251
|
+
cadence_count_biking += 1
|
283
252
|
end
|
284
253
|
end
|
285
254
|
end
|
286
255
|
end
|
287
256
|
|
288
|
-
|
257
|
+
average_cadence_all = cadence_count_all > 0 ? total_cadence_all.to_f / cadence_count_all : 0.0
|
258
|
+
average_cadence_biking = cadence_count_biking > 0 ? total_cadence_biking.to_f / cadence_count_biking : 0.0
|
259
|
+
|
260
|
+
{
|
261
|
+
average_cadence_all: average_cadence_all,
|
262
|
+
average_cadence_biking: average_cadence_biking
|
263
|
+
}
|
264
|
+
end
|
265
|
+
|
266
|
+
# Calculates the average speed from the activities.
|
267
|
+
#
|
268
|
+
# @param activities [Array<Hash>] an array of activity hashes.
|
269
|
+
# @return [Hash] a hash containing average speed including zeros and average speed while moving.
|
270
|
+
def calculate_average_speed_from_activities(activities)
|
271
|
+
total_speed_all = 0
|
272
|
+
total_speed_moving = 0
|
273
|
+
speed_count_all = 0
|
274
|
+
speed_count_moving = 0
|
275
|
+
|
276
|
+
activities.each do |activity|
|
277
|
+
activity[:laps].each do |lap|
|
278
|
+
lap[:tracks].flatten.each do |trackpoint|
|
279
|
+
distance = trackpoint[:distance_meters]
|
280
|
+
time_seconds = lap[:total_time_seconds]
|
281
|
+
speed = distance / time_seconds if time_seconds > 0
|
282
|
+
|
283
|
+
if speed
|
284
|
+
total_speed_all += speed
|
285
|
+
speed_count_all += 1
|
286
|
+
|
287
|
+
if speed > 0
|
288
|
+
total_speed_moving += speed
|
289
|
+
speed_count_moving += 1
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
average_speed_all = speed_count_all > 0 ? total_speed_all.to_f / speed_count_all : 0.0
|
297
|
+
average_speed_moving = speed_count_moving > 0 ? total_speed_moving.to_f / speed_count_moving : 0.0
|
298
|
+
|
299
|
+
{
|
300
|
+
average_speed_all: average_speed_all,
|
301
|
+
average_speed_moving: average_speed_moving
|
302
|
+
}
|
289
303
|
end
|
290
304
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tcxread
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- firefly-cpp
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -31,6 +31,8 @@ executables: []
|
|
31
31
|
extensions: []
|
32
32
|
extra_rdoc_files: []
|
33
33
|
files:
|
34
|
+
- LICENSE
|
35
|
+
- README.md
|
34
36
|
- lib/tcxread.rb
|
35
37
|
homepage: https://github.com/firefly-cpp/tcxread
|
36
38
|
licenses:
|