tcxread 0.1.3 → 0.1.5
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.
- checksums.yaml +4 -4
- data/README.md +5 -1
- data/lib/tcxread.rb +67 -54
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0f94a5a07acd01e1c6b0419324bbef97912e0d7e67b3f2b1dc18d50336dd7e2
|
4
|
+
data.tar.gz: 776953d7610ffa6c6903449332eb1bf36ff2a7fb54c5fa10dde8e591ecefd3df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc1240984ccb04288e3790d69529eea52269f16888e1b6d8906017b42252ce98eeef44240b729ed1358444e2f0d3f7ce377f3a49123ea5c85c21493a2438cc77
|
7
|
+
data.tar.gz: 99863f90228c50f2590709105fab6d55395c26462bec7b2ba2b11c429f2e85453867583fdcb964d83599f669bbc1dc991fa6ca8a0c5eff6aba8a3681007d1b48
|
data/README.md
CHANGED
@@ -49,7 +49,11 @@ puts "Distance meters: #{data.total_distance_meters}, " \
|
|
49
49
|
"Average heart rate: #{data.average_heart_rate}, " \
|
50
50
|
"Average watts: #{data.average_watts}, " \
|
51
51
|
"Max watts: #{data.max_watts}, " \
|
52
|
-
"Average
|
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
|
+
|
53
57
|
```
|
54
58
|
|
55
59
|
## 💾 Datasets
|
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|
|
@@ -117,7 +109,8 @@ class TCXRead
|
|
117
109
|
heart_rate: trackpoint.xpath('xmlns:HeartRateBpm/xmlns:Value').text.to_i,
|
118
110
|
cadence: trackpoint.xpath('xmlns:Cadence').text.to_i,
|
119
111
|
sensor_state: trackpoint.xpath('xmlns:SensorState').text,
|
120
|
-
watts: trackpoint.xpath('xmlns:Extensions/ns3:TPX/ns3:Watts').text.to_f
|
112
|
+
watts: trackpoint.xpath('xmlns:Extensions/ns3:TPX/ns3:Watts').text.to_f,
|
113
|
+
speed: trackpoint.xpath('xmlns:Extensions/ns3:TPX/ns3:Speed').text.to_f
|
121
114
|
}
|
122
115
|
end
|
123
116
|
tracks << trackpoints
|
@@ -125,10 +118,6 @@ class TCXRead
|
|
125
118
|
tracks
|
126
119
|
end
|
127
120
|
|
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
121
|
def parse_position(trackpoint)
|
133
122
|
position = trackpoint.at_xpath('xmlns:Position')
|
134
123
|
return nil unless position
|
@@ -139,10 +128,6 @@ class TCXRead
|
|
139
128
|
}
|
140
129
|
end
|
141
130
|
|
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
131
|
def calculate_ascent_descent_and_max_altitude(laps)
|
147
132
|
total_ascent = 0.0
|
148
133
|
total_descent = 0.0
|
@@ -170,10 +155,6 @@ class TCXRead
|
|
170
155
|
[total_ascent, total_descent, max_altitude]
|
171
156
|
end
|
172
157
|
|
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
158
|
def calculate_ascent_descent_and_max_altitude_from_activities(activities)
|
178
159
|
total_ascent = 0.0
|
179
160
|
total_descent = 0.0
|
@@ -188,10 +169,6 @@ class TCXRead
|
|
188
169
|
[total_ascent, total_descent, max_altitude]
|
189
170
|
end
|
190
171
|
|
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
172
|
def calculate_average_heart_rate(laps)
|
196
173
|
total_heart_rate = 0
|
197
174
|
heart_rate_count = 0
|
@@ -209,10 +186,6 @@ class TCXRead
|
|
209
186
|
heart_rate_count > 0 ? total_heart_rate.to_f / heart_rate_count : 0.0
|
210
187
|
end
|
211
188
|
|
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
189
|
def calculate_average_heart_rate_from_activities(activities)
|
217
190
|
total_heart_rate = 0
|
218
191
|
heart_rate_count = 0
|
@@ -232,10 +205,6 @@ class TCXRead
|
|
232
205
|
heart_rate_count > 0 ? total_heart_rate.to_f / heart_rate_count : 0.0
|
233
206
|
end
|
234
207
|
|
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
208
|
def calculate_watts_from_activities(activities)
|
240
209
|
max_watts = 0
|
241
210
|
total_watts = 0
|
@@ -265,26 +234,70 @@ class TCXRead
|
|
265
234
|
[max_watts, average_watts]
|
266
235
|
end
|
267
236
|
|
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
237
|
def calculate_average_cadence_from_activities(activities)
|
273
|
-
|
274
|
-
|
238
|
+
total_cadence_all = 0
|
239
|
+
total_cadence_biking = 0
|
240
|
+
cadence_count_all = 0
|
241
|
+
cadence_count_biking = 0
|
275
242
|
|
276
243
|
activities.each do |activity|
|
277
244
|
activity[:laps].each do |lap|
|
278
245
|
lap[:tracks].flatten.each do |trackpoint|
|
279
246
|
cadence = trackpoint[:cadence]
|
247
|
+
total_cadence_all += cadence
|
248
|
+
cadence_count_all += 1
|
249
|
+
|
280
250
|
if cadence > 0
|
281
|
-
|
282
|
-
|
251
|
+
total_cadence_biking += cadence
|
252
|
+
cadence_count_biking += 1
|
283
253
|
end
|
284
254
|
end
|
285
255
|
end
|
286
256
|
end
|
287
257
|
|
288
|
-
|
258
|
+
average_cadence_all = cadence_count_all > 0 ? total_cadence_all.to_f / cadence_count_all : 0.0
|
259
|
+
average_cadence_biking = cadence_count_biking > 0 ? total_cadence_biking.to_f / cadence_count_biking : 0.0
|
260
|
+
|
261
|
+
{
|
262
|
+
average_cadence_all: average_cadence_all,
|
263
|
+
average_cadence_biking: average_cadence_biking
|
264
|
+
}
|
265
|
+
end
|
266
|
+
|
267
|
+
# Calculates the average speed from the activities.
|
268
|
+
#
|
269
|
+
# @param activities [Array<Hash>] an array of activity hashes.
|
270
|
+
# @return [Hash] a hash containing average speed including zeros and average speed while moving.
|
271
|
+
def calculate_average_speed_from_activities(activities)
|
272
|
+
total_speed_all = 0
|
273
|
+
total_speed_moving = 0
|
274
|
+
speed_count_all = 0
|
275
|
+
speed_count_moving = 0
|
276
|
+
|
277
|
+
activities.each do |activity|
|
278
|
+
activity[:laps].each do |lap|
|
279
|
+
lap[:tracks].flatten.each do |trackpoint|
|
280
|
+
speed = trackpoint[:speed]
|
281
|
+
|
282
|
+
if speed
|
283
|
+
total_speed_all += speed
|
284
|
+
speed_count_all += 1
|
285
|
+
|
286
|
+
if speed > 0
|
287
|
+
total_speed_moving += speed
|
288
|
+
speed_count_moving += 1
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
average_speed_all = speed_count_all > 0 ? total_speed_all.to_f / speed_count_all : 0.0
|
296
|
+
average_speed_moving = speed_count_moving > 0 ? total_speed_moving.to_f / speed_count_moving : 0.0
|
297
|
+
|
298
|
+
{
|
299
|
+
average_speed_all: average_speed_all,
|
300
|
+
average_speed_moving: average_speed_moving
|
301
|
+
}
|
289
302
|
end
|
290
303
|
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.5
|
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-08-
|
11
|
+
date: 2024-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|