calcpace 1.9.2 → 1.9.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03d52df60dcc776004ee27c76c98010904d0bc6ecfc839e81e4d8aa19e299b8d
4
- data.tar.gz: d25c779a355310c08574b8b5c16ed9d946515fbe55c70c766569f13a57774d5c
3
+ metadata.gz: 9ee49b6b99023d43bef8396cb0c1ab9ba4ef99e6c96285b4c4405eb3d163807d
4
+ data.tar.gz: 442169e5414c141390261da86e5cc312eda98c685bbc64530ca7692d39773119
5
5
  SHA512:
6
- metadata.gz: 1c4b764c13b5231c2a19731003395933e059e64c30c7a8e1930ab7fa6c119b120cecfb815d78e30a32a653cdfa01b084187783170c44a762178b392f8c988a19
7
- data.tar.gz: 7e395492e42da2b4f9b7d0abcf715ae6c5a253853316ec026b95116edd516bad87a77f17edd27e2d855fc1fb19ed77c46ee276751fc5ad7be567794dc66bb10c
6
+ metadata.gz: ae31a9047296eb8848030d08d35af80bd9af22f5ae5ac65de727e99b7448155918198caa2467edb5a3371c92d6f3f4b4c54adef1d0d8da51bc19b4b9cafb680d
7
+ data.tar.gz: 57b06d49de20b1415ec230c1d2bfd3ac495dc4c8c3ed25e684fcdf3072fef142728394942706aa39892b69eea3ce9b1b1e9f8c2fdb3efea7fbd3bd7e90016fa3
data/README.md CHANGED
@@ -1,404 +1,193 @@
1
- # Calcpace [![Gem Version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=rb&r=r&ts=1683906897&type=6e&v=1.9.0&x2=0)](https://badge.fury.io/rb/calcpace)
1
+ # Calcpace [![Gem Version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=rb&r=r&ts=1683906897&type=6e&v=1.9.3&x2=0)](https://badge.fury.io/rb/calcpace)
2
2
 
3
- Calcpace is a Ruby gem designed for calculations and conversions related to distance and time. It can calculate velocity, pace, total time, and distance, accepting time in various formats, including HH:MM:SS. The gem supports conversion to 42 different units, including kilometers, miles, meters, and feet. It also provides methods to validate input.
3
+ A Ruby gem for running and cycling calculations: pace, time, distance, unit conversions, race predictions, GPS track analysis, and VO2max estimation.
4
4
 
5
5
  ## Installation
6
6
 
7
- ### Add to your Gemfile
8
-
9
7
  ```ruby
10
8
  gem 'calcpace', '~> 1.9'
11
9
  ```
12
10
 
13
- Then run:
14
-
15
- ```bash
16
- bundle install
17
- ```
18
-
19
- ### Install the gem manually
20
-
21
- ```bash
22
- gem install calcpace
23
- ```
24
-
25
11
  ## Usage
26
12
 
27
- Before performing any calculations or conversions, create a new instance of Calcpace:
28
-
29
13
  ```ruby
30
14
  require 'calcpace'
31
-
32
- calculate = Calcpace.new
15
+ calc = Calcpace.new
33
16
  ```
34
17
 
35
- ### Calculate using Integers or Floats
18
+ ---
36
19
 
37
- Calcpace provides methods to calculate velocity, pace, total time, and distance. The methods are unit-agnostic, and the return value is a float. Here are some examples:
20
+ ### Basic Calculations
38
21
 
39
22
  ```ruby
40
- calculate.velocity(3625, 12275) # => 3.386206896551724
41
- calculate.pace(3665, 12) # => 305.4166666666667
42
- calculate.time(210, 12) # => 2520.0
43
- calculate.distance(9660, 120) # => 80.5
44
- ```
45
-
46
- Tip: Use the `round` method to round a float. For example:
23
+ calc.velocity(3625, 12275) # => 3.386 (distance / time)
24
+ calc.pace(3665, 12) # => 305.4 (time / distance)
25
+ calc.time(210, 12) # => 2520.0 (pace × distance)
26
+ calc.distance(9660, 120) # => 80.5 (velocity × time)
47
27
 
48
- ```ruby
49
- calculate.velocity(3625, 12275).round(3) # => 3.386
28
+ # Clocktime input/output (HH:MM:SS or MM:SS)
29
+ calc.clock_pace('01:00:00', 10) # => "00:06:00"
30
+ calc.clock_time('00:05:31', 12.6) # => "01:09:30"
31
+ calc.checked_distance('01:21:32', '00:06:27') # => 12.64
50
32
  ```
51
33
 
52
- Remember:
53
-
54
- - Velocity is the distance divided by the time (e.g., m/s or km/h).
55
- - Pace is the time divided by the distance (e.g., minutes/km or minutes/miles).
56
- - Total time is the distance divided by the velocity.
57
- - Distance is the velocity multiplied by the time.
34
+ ---
58
35
 
59
- ### Calculate using Clocktime
36
+ ### Unit Conversions
60
37
 
61
- Calcpace also provides methods to calculate using clocktime (HH:MM:SS or MM:SS format string). The return value will be in seconds or clocktime, depending on the method called, except for `checked_distance`. Here are some examples:
38
+ 30+ units supported. String or symbol format:
62
39
 
63
40
  ```ruby
64
- # The return will be in the unit you input/seconds or seconds/unit you input
65
- calculate.checked_velocity('01:00:00', 12275) # => 3.4097222222222223
66
- calculate.checked_pace('01:21:32', 10) # => 489.2
67
- calculate.checked_time('00:05:31', 12.6) # => 4170.599999999999
68
-
69
- calculate.checked_distance('01:21:32', '00:06:27') # => 12.640826873385013
41
+ calc.convert(10, :km_to_mi) # => 6.21371
42
+ calc.convert(10, 'mi to km') # => 16.0934
43
+ calc.convert(1, :m_s_to_km_h) # => 3.6
70
44
 
71
- # The return will be in clocktime
72
- calculate.clock_pace('01:21:32', 10) # => "00:08:09"
73
- calculate.clock_velocity('01:00:00', 10317) # => "00:00:02"
74
- calculate.clock_time('00:05:31', 12.6) # => "01:09:30"
75
- ```
76
-
77
- Note: Using the `clock` methods may be less precise than using other methods due to conversions.
78
-
79
- You can also use BigDecimal for more precise calculations. For example:
80
-
81
- ```ruby
82
- require 'bigdecimal'
83
- calculate.checked_velocity('10:00:00', 10317).to_d # => #<BigDecimal:7f9f1b8b1d08,'0.2865833333 333333E1',27(36)>
45
+ # Chain conversions
46
+ calc.convert_chain(1, [:km_to_mi, :mi_to_feet]) # => 3280.84
84
47
  ```
85
48
 
86
- To learn more about BigDecimal, check the [documentation](https://ruby-doc.org/stdlib-2.7.1/libdoc/bigdecimal/rdoc/BigDecimal.html).
87
-
88
- ### Convert Distances and Velocities
89
-
90
- Use the `convert` method to convert a distance or velocity. The first parameter is the value to be converted, and the second parameter is the unit to which the value will be converted. The unit can be a string (e.g. 'km to meters') or a symbol (e.g. :km_to_meters). The gem supports 42 different units, including kilometers, miles, meters, knots, and feet.
91
-
92
- Here are some examples:
93
-
94
- ```ruby
95
- converter.convert(10, :km_to_meters) # => 1000
96
- converter.convert(10, 'mi to km') # => 16.0934
97
- converter.convert(1, :nautical_mi_to_km) # => 1.852
98
- converter.convert(1, 'km h to m s') # => 0.277778
99
- converter.convert(1, :m_s_to_mi_h) # => 2.23694
100
- ```
101
-
102
- | Conversion Unit | Description |
103
- |----------------------|-----------------------------|
104
- | :km_to_mi | Kilometers to Miles |
105
- | :mi_to_km | Miles to Kilometers |
106
- | :nautical_mi_to_km | Nautical Miles to Kilometers |
107
- | :km_to_nautical_mi | Kilometers to Nautical Miles |
108
- | :meters_to_km | Meters to Kilometers |
109
- | :km_to_meters | Kilometers to Meters |
110
- | :meters_to_mi | Meters to Miles |
111
- | :mi_to_meters | Miles to Meters |
112
- | :m_s_to_km_h | Meters per Second to Kilometers per Hour |
113
- | :km_h_to_m_s | Kilometers per Hour to Meters per Second |
114
- | :m_s_to_mi_h | Meters per Second to Miles per Hour |
115
- | :mi_h_to_m_s | Miles per Hour to Meters per Second |
116
- | :m_s_to_feet_s | Meters per Second to Feet per Second |
117
- | :feet_s_to_m_s | Feet per Second to Meters per Second |
118
- | :km_h_to_mi_h | Kilometers per Hour to Miles per Hour |
119
- | :mi_h_to_km_h | Miles per Hour to Kilometers per Hour |
120
-
121
- You can list all the available units [here](/lib/calcpace/converter.rb), or using `list` methods. The return will be a hash with the unit and a description of the unit.
122
-
123
- ```ruby
124
- converter.list_all
125
- # => {:km_to_mi=>"KM to MI", :mi_to_km=>"MI to KM", ...}
126
-
127
- converter.list_distance
128
- # => {:km_to_mi=>"KM to MI", :mi_to_km=>"MI to KM", ...}
129
-
130
- converter.list_speed
131
- # => {:m_s_to_km_h=>"M S to KM H", :km_h_to_m_s=>"KM H to M S", ...}
132
- ```
133
-
134
- ### Chain Conversions
135
-
136
- Perform multiple conversions in sequence with the converter chain feature:
137
-
138
- ```ruby
139
- calc = Calcpace.new
140
-
141
- # Convert kilometers to miles to feet in one call
142
- calc.convert_chain(1, [:km_to_mi, :mi_to_feet])
143
- # => 3280.84 (1 km = 0.621 mi = 3280.84 feet)
144
-
145
- # Convert with description for debugging
146
- calc.convert_chain_with_description(100, [:meters_to_km, :km_to_mi])
147
- # => { result: 0.0621371, description: "100 → meters_to_km → km_to_mi → 0.0621" }
148
-
149
- # Speed conversions
150
- calc.convert_chain(10, [:m_s_to_km_h, :km_h_to_mi_h])
151
- # => 22.3694 (10 m/s = 36 km/h = 22.37 mi/h)
152
- ```
49
+ See all units: `calc.list_all`, `calc.list_distance`, `calc.list_speed`.
153
50
 
154
- ### Race Pace Calculator
51
+ ---
155
52
 
156
- Calcpace includes a race pace calculator for standard race distances (5K, 10K, half-marathon, and marathon):
53
+ ### Pace Conversions
157
54
 
158
55
  ```ruby
159
- calc = Calcpace.new
160
-
161
- # Calculate finish time for a race given a pace
162
- calc.race_time(300, '5k') # => 1500.0 (5:00/km pace for 5K = 25:00)
163
- calc.race_time_clock('05:00', 'marathon') # => '03:30:58' (5:00/km pace for marathon)
164
-
165
- # Calculate required pace for a target finish time
166
- calc.race_pace('00:30:00', '5k') # => 360.0 (need 6:00/km to finish 5K in 30:00)
167
- calc.race_pace_clock('04:00:00', 'marathon') # => '00:05:41' (need 5:41/km for 4-hour marathon)
168
-
169
- # List available race distances
170
- calc.list_races
171
- # => { '5k' => 5.0, '10k' => 10.0, 'half_marathon' => 21.0975, 'marathon' => 42.195 }
56
+ calc.pace_km_to_mi('05:00') # => "00:08:02"
57
+ calc.pace_mi_to_km('08:00') # => "00:04:58"
172
58
  ```
173
59
 
174
- Supported race distances:
175
- - `5k` - 5 kilometers
176
- - `10k` - 10 kilometers
177
- - `half_marathon` - 21.0975 kilometers
178
- - `marathon` - 42.195 kilometers
179
- - `1mile` - 1.60934 kilometers
180
- - `5mile` - 8.04672 kilometers
181
- - `10mile` - 16.0934 kilometers
60
+ ---
182
61
 
183
- ### Pace Conversions
184
-
185
- Convert running pace between kilometers and miles:
62
+ ### Race Pace & Time
186
63
 
187
64
  ```ruby
188
- calc = Calcpace.new
189
-
190
- # Convert pace from km to miles
191
- calc.pace_km_to_mi('05:00') # => '00:08:02' (5:00/km = 8:02/mi)
192
- calc.convert_pace('05:00', :km_to_mi) # => '00:08:02' (same as above)
193
-
194
- # Convert pace from miles to km
195
- calc.pace_mi_to_km('08:00') # => '00:04:58' (8:00/mi ≈ 4:58/km)
196
- calc.convert_pace('08:00', :mi_to_km) # => '00:04:58' (same as above)
197
-
198
- # Works with numeric input (seconds) as well
199
- calc.pace_km_to_mi(300) # => '00:08:02' (300 seconds/km)
200
- calc.pace_mi_to_km(480) # => '00:04:58' (480 seconds/mi)
201
-
202
- # String format also supported
203
- calc.convert_pace('05:00', 'km to mi') # => '00:08:02'
65
+ calc.race_time_clock('05:00', 'marathon') # => "03:30:58"
66
+ calc.race_pace_clock('04:00:00', 'marathon') # => "00:05:41"
67
+ calc.list_races # => { '5k' => 5.0, '10k' => 10.0, 'half_marathon' => 21.0975, ... }
204
68
  ```
205
69
 
206
- The pace conversions are particularly useful when:
207
- - Planning races in different countries (metric vs imperial)
208
- - Comparing training paces with international runners
209
- - Converting workout plans between pace formats
70
+ ---
210
71
 
211
72
  ### Race Splits
212
73
 
213
- Calculate split times for races to help pace your race strategy:
214
-
215
74
  ```ruby
216
- calc = Calcpace.new
217
-
218
- # Even pace splits for half marathon (every 5k)
75
+ # Even pace — default
219
76
  calc.race_splits('half_marathon', target_time: '01:30:00', split_distance: '5k')
220
77
  # => ["00:21:20", "00:42:40", "01:03:59", "01:25:19", "01:30:00"]
221
78
 
222
- # Kilometer splits for 10K
223
- calc.race_splits('10k', target_time: '00:40:00', split_distance: '1k')
224
- # => ["00:04:00", "00:08:00", "00:12:00", ..., "00:40:00"]
225
-
226
- # Mile splits for marathon
227
- calc.race_splits('marathon', target_time: '03:30:00', split_distance: '1mile')
228
- # => ["00:08:02", "00:16:04", ..., "03:30:00"]
229
-
230
- # Negative splits strategy (second half faster)
79
+ # Strategies: :even (default), :negative (second half faster), :positive (first half faster)
231
80
  calc.race_splits('10k', target_time: '00:40:00', split_distance: '5k', strategy: :negative)
232
- # => ["00:20:48", "00:40:00"] # First 5k slower, second 5k faster
233
-
234
- # Positive splits strategy (first half faster)
235
- calc.race_splits('10k', target_time: '00:40:00', split_distance: '5k', strategy: :positive)
236
- # => ["00:19:12", "00:40:00"] # First 5k faster, second 5k slower
81
+ # => ["00:20:48", "00:40:00"]
237
82
  ```
238
83
 
239
- Supported strategies:
240
- - `:even` - Constant pace throughout (default)
241
- - `:negative` - Second half ~4% faster (progressive race)
242
- - `:positive` - First half ~4% faster (conservative start)
243
-
244
- Split distances can be:
245
- - Standard race distances: `'5k'`, `'10k'`, `'1mile'`, etc.
246
- - Custom distances: `2.5` (in kilometers), `'3k'`, etc.
84
+ ---
247
85
 
248
86
  ### Race Time Predictions
249
87
 
250
- Predict your race times at different distances based on a recent performance using the **Riegel formula**:
88
+ **Riegel formula** (`T2 = T1 × (D2/D1)^1.06`):
251
89
 
252
90
  ```ruby
253
- calc = Calcpace.new
254
-
255
- # Predict marathon time from a 5K result
256
- calc.predict_time_clock('5k', '00:20:00', 'marathon')
257
- # => "03:11:49" (predicts 3:11:49 marathon from 20:00 5K)
258
-
259
- # Predict 10K time from half marathon
260
- calc.predict_time_clock('half_marathon', '01:30:00', '10k')
261
- # => "00:40:47"
262
-
263
- # Get predicted pace for target race
264
- calc.predict_pace_clock('5k', '00:20:00', 'marathon')
265
- # => "00:04:32" (4:32/km pace for predicted marathon)
266
-
267
- # Get complete equivalent performance info
91
+ calc.predict_time_clock('5k', '00:20:00', 'marathon') # => "03:11:49"
92
+ calc.predict_pace_clock('5k', '00:20:00', 'marathon') # => "00:04:32"
268
93
  calc.equivalent_performance('10k', '00:42:00', '5k')
269
- # => {
270
- # time: 1209.0,
271
- # time_clock: "00:20:09",
272
- # pace: 241.8,
273
- # pace_clock: "00:04:02"
274
- # }
94
+ # => { time: 1209.0, time_clock: "00:20:09", pace: 241.8, pace_clock: "00:04:02" }
275
95
  ```
276
96
 
277
- #### How the Riegel Formula Works
278
-
279
- The Riegel formula is a mathematical model that predicts race performance across different distances:
97
+ **Cameron formula** (exponential correction tends to be more conservative from short distances):
280
98
 
281
- **Formula:** `T2 = T1 × (D2/D1)^1.06`
282
-
283
- Where:
284
- - **T1** = your known time at distance D1
285
- - **T2** = predicted time at distance D2
286
- - **D1** = known race distance (in km)
287
- - **D2** = target race distance (in km)
288
- - **1.06** = fatigue/endurance factor
99
+ ```ruby
100
+ calc.predict_time_cameron_clock('10k', '00:42:00', 'marathon') # => "02:57:46"
101
+ calc.predict_pace_cameron_clock('10k', '00:42:00', 'marathon') # => "00:04:13"
102
+ ```
289
103
 
290
- **The 1.06 exponent** represents how pace slows as distance increases. If endurance was perfect (1.0), doubling distance would simply double time. But in reality, you slow down slightly - the 1.06 factor accounts for this accumulated fatigue.
104
+ ---
291
105
 
292
- **Example:** From a 5K in 20:00, predict marathon time:
293
- - T1 = 1200 seconds (20:00)
294
- - D1 = 5 km
295
- - D2 = 42.195 km (marathon)
296
- - T2 = 1200 × (42.195/5)^1.06
297
- - T2 = 1200 × 9.591 = 11,509 seconds
298
- - T2 = **3:11:49**
106
+ ### GPS Track Analysis
299
107
 
300
- The formula works best for:
301
- - Distances from 5K to marathon
302
- - Recent race results (within 6-8 weeks)
303
- - Similar terrain and weather conditions
304
- - Well-trained runners with consistent pacing
108
+ Accepts an array of hashes with `:lat`, `:lon`, and optionally `:ele` (metres) and `:time` (`Time`):
305
109
 
306
- **Important notes:**
307
- - Predictions assume equal training and effort across distances
308
- - Results are estimates - actual performance varies by individual fitness, training focus, and race conditions
309
- - The formula is most accurate when predicting between similar distance ranges (e.g., 10K to half marathon)
110
+ ```ruby
111
+ points = [
112
+ { lat: -23.5505, lon: -46.6333, ele: 760.0, time: Time.parse('2024-01-01 07:00:00') },
113
+ { lat: -23.5510, lon: -46.6400, ele: 765.0, time: Time.parse('2024-01-01 07:05:00') },
114
+ { lat: -23.5520, lon: -46.6480, ele: 758.0, time: Time.parse('2024-01-01 07:10:00') },
115
+ ]
310
116
 
311
- ### Race Time Predictions Cameron Formula
117
+ calc.haversine_distance(-23.5505, -46.6333, -23.5510, -46.6340) # => 0.089 km
118
+ calc.track_distance(points) # => 0.87 km
119
+ calc.elevation_gain(points) # => { gain: 5.0, loss: 7.0 }
120
+ calc.track_splits(points, 1.0) # => [{ km: 1, elapsed: 312, pace: "05:12" }, ...]
121
+ ```
312
122
 
313
- An alternative predictor using the **Cameron formula**, which applies an exponential correction based on the known distance. Unlike Riegel's fixed power-law exponent, Cameron's correction is larger when predicting from shorter races (where anaerobic capacity plays a bigger role) and diminishes as the known distance increases.
123
+ **Haversine formula** great-circle distance on a sphere (R = 6,371 km). Accuracy: ~0.3% of GPS/WGS84. Best for running and cycling distances; not for geodetic surveying.
314
124
 
315
- ```ruby
316
- calc = Calcpace.new
125
+ ---
317
126
 
318
- # Predict marathon time from 10K
319
- calc.predict_time_cameron_clock('10k', '00:42:00', 'marathon')
320
- # => "02:57:46"
127
+ ### VO2max Estimation
321
128
 
322
- # Predict 10K from 5K
323
- calc.predict_time_cameron_clock('5k', '00:20:00', '10k')
324
- # => "00:42:24"
129
+ Estimate aerobic fitness from a race result using the **Daniels & Gilbert formula** (1979):
325
130
 
326
- # Get predicted pace per km
327
- calc.predict_pace_cameron_clock('10k', '00:42:00', 'marathon')
328
- # => "00:04:13"
131
+ ```ruby
132
+ calc.estimate_vo2max(10.0, '00:40:00') # => 51.9 ml/kg/min
133
+ calc.estimate_vo2max(42.195, '03:30:00') # => 44.8
134
+ calc.estimate_vo2max(5.0, 2400) # also accepts total seconds
329
135
 
330
- # Raw seconds (useful for further calculations)
331
- calc.predict_time_cameron('5k', '00:20:00', 'half_marathon')
332
- # => 5382.7 (approximately 1:29:42)
136
+ calc.vo2max_label(51.9) # => "Very Good"
333
137
  ```
334
138
 
335
- #### How the Cameron Formula Works
139
+ | VO2max (ml/kg/min) | Level |
140
+ |--------------------|-----------|
141
+ | ≥ 70 | Elite |
142
+ | 60–69 | Excellent |
143
+ | 50–59 | Very Good |
144
+ | 40–49 | Good |
145
+ | 30–39 | Fair |
146
+ | < 30 | Beginner |
336
147
 
337
- **Formula:** `T2 = T1 × (D2/D1) × [(a + b × e^(-D1/c)) / (a + b × e^(-D2/c))]`
338
-
339
- Where:
340
- - **T1** = known time, **D1** = known distance (km)
341
- - **T2** = predicted time, **D2** = target distance (km)
342
- - **a = 0.000495**, **b = 0.000985**, **c = 1.4485** (empirical constants)
343
-
344
- The exponential correction factor `f(d) = a + b × e^(-d/c)` decreases as distance grows. When predicting from a short race to a long one, `f(D1) > f(D2)`, making `T2` grow slightly faster than a pure linear extrapolation — accounting for the greater fatigue at longer distances.
148
+ **Formula:**
149
+ ```
150
+ velocity (m/min) = distance_m / time_min
151
+ VO2 = −4.60 + 0.182258·v + 0.000104·v²
152
+ %VO2max = 0.8 + 0.1894393·e^(−0.012778·t) + 0.2989558·e^(−0.1932605·t)
153
+ VO2max = VO2 / %VO2max
154
+ ```
345
155
 
346
- **Compared to Riegel:**
347
- - Predicting from short distances (5K): Cameron tends to be more conservative (slower prediction) — acknowledges that 5K speed has a larger anaerobic component
348
- - Predicting from moderate distances (10K): Cameron tends to be slightly more optimistic — 10K is already a strong predictor of marathon aerobic capacity
349
- - Both formulas are estimates; real performance depends on training specificity, conditions, and individual physiology
156
+ Accuracy: ±3–5 ml/kg/min vs. laboratory testing. Best with efforts between **5 and 60 minutes** at near-maximal pace.
350
157
 
351
- ### Other Useful Methods
158
+ ---
352
159
 
353
- Calcpace also provides other useful methods:
160
+ ### Other Utilities
354
161
 
355
162
  ```ruby
356
- converter = Calcpace.new
357
- converter.convert_to_seconds('01:00:00') # => 3600
358
- converter.convert_to_clocktime(3600) # => '01:00:00'
359
- converter.convert_to_clocktime(100000) # => '1 03:46:40'
360
- converter.check_time('01:00:00') # => nil
163
+ calc.convert_to_seconds('01:00:00') # => 3600
164
+ calc.convert_to_clocktime(3600) # => "01:00:00"
165
+ calc.check_time('01:00:00') # => nil (valid)
361
166
  ```
362
167
 
363
- ### Errors
168
+ ---
364
169
 
365
- The gem now raises specific custom error classes for invalid inputs, allowing for more precise error handling. These errors inherit from `Calcpace::Error`.
170
+ ### Errors
366
171
 
367
- - `Calcpace::NonPositiveInputError`: Raised when a numeric input is not positive.
368
- - `Calcpace::InvalidTimeFormatError`: Raised when a time string is not in the expected `HH:MM:SS` or `MM:SS` format.
172
+ All errors inherit from `Calcpace::Error`:
369
173
 
370
- For example:
174
+ - `Calcpace::NonPositiveInputError` — numeric input is zero or negative
175
+ - `Calcpace::InvalidTimeFormatError` — time string not in `HH:MM:SS` or `MM:SS` format
371
176
 
372
- ```ruby
373
- begin
374
- calculate.pace(945, -1)
375
- rescue Calcpace::NonPositiveInputError => e
376
- puts e.message # => "Distance must be a positive number"
377
- end
378
-
379
- begin
380
- calculate.checked_time('string', 10)
381
- rescue Calcpace::InvalidTimeFormatError => e
382
- puts e.message # => "It must be a valid time in the XX:XX:XX or XX:XX format"
383
- end
384
- ```
177
+ ---
385
178
 
386
179
  ### Testing
387
180
 
388
- To run the tests, clone the repository and run:
389
-
390
181
  ```bash
391
182
  bundle exec rake
392
183
  ```
393
184
 
394
- ### Supported Ruby Versions
395
-
396
- The tests are run using Ruby versions from 2.7.8 to 3.4.2, as specified in the `.github/workflows/test.yml` file.
185
+ Requires Ruby >= 3.2.0.
397
186
 
398
187
  ## Contributing
399
188
 
400
- We welcome contributions to Calcpace! To contribute, clone this repository and submit a pull request. Please ensure that your code adheres to our style and includes tests where appropriate.
189
+ Clone the repo and submit a pull request. Please include tests.
401
190
 
402
191
  ## License
403
192
 
404
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
193
+ [MIT License](https://opensource.org/licenses/MIT)
data/calcpace.gemspec CHANGED
@@ -8,8 +8,11 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ['João Gilberto Saraiva']
9
9
  spec.email = ['joaogilberto@tuta.io']
10
10
 
11
- spec.summary = 'A Ruby gem for pace, distance, and time calculations.'
12
- spec.description = 'Ruby gem for pace, distance, time, and speed calculations. Supports multiple units and formats.'
11
+ spec.summary = 'Pace, distance, GPS track analysis, and VO2max calculations for runners and cyclists.'
12
+ spec.description = 'Ruby gem for running and cycling calculations: pace, time, distance, ' \
13
+ 'unit conversions (30+ units), race predictions (Riegel & Cameron), ' \
14
+ 'GPS track analysis (Haversine, elevation gain, per-km splits), ' \
15
+ 'and VO2max estimation (Daniels & Gilbert).'
13
16
  spec.homepage = 'https://github.com/0jonjo/calcpace'
14
17
  spec.metadata['source_code_uri'] = spec.homepage
15
18
  spec.license = 'MIT'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Calcpace
4
- VERSION = '1.9.2'
4
+ VERSION = '1.9.3'
5
5
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Module for estimating VO2max from a race performance
4
+ #
5
+ # Uses the Daniels & Gilbert formula (1979), which relates running velocity
6
+ # and exercise duration to oxygen consumption as a percentage of VO2max.
7
+ #
8
+ # Formula:
9
+ # velocity (m/min) = distance_m / time_min
10
+ # VO2 = -4.60 + 0.182258 * v + 0.000104 * v²
11
+ # %VO2max = 0.8 + 0.1894393·e^(-0.012778·t) + 0.2989558·e^(-0.1932605·t)
12
+ # VO2max = VO2 / %VO2max
13
+ #
14
+ # Accuracy: ±3–5 ml/kg/min vs laboratory testing. Best results with efforts
15
+ # between 5 and 60 minutes at race pace (i.e. near-maximal effort).
16
+ module Vo2maxEstimator
17
+ VO2MAX_LABELS = [
18
+ { min: 70, label: 'Elite' },
19
+ { min: 60, label: 'Excellent' },
20
+ { min: 50, label: 'Very Good' },
21
+ { min: 40, label: 'Good' },
22
+ { min: 30, label: 'Fair' },
23
+ { min: 0, label: 'Beginner' }
24
+ ].freeze
25
+
26
+ # Estimates VO2max from a race performance using Daniels & Gilbert formula
27
+ #
28
+ # @param distance_km [Numeric] race distance in kilometres (must be > 0)
29
+ # @param time [String, Integer] finish time as "HH:MM:SS" / "MM:SS", or total seconds (must be > 0)
30
+ # @return [Float] estimated VO2max in ml/kg/min, rounded to one decimal place
31
+ # @raise [Calcpace::NonPositiveInputError] if distance or time are not positive
32
+ # @raise [Calcpace::InvalidTimeFormatError] if time string is not in HH:MM:SS or MM:SS format
33
+ #
34
+ # @example 10 km in 40:00 → ~51.9 ml/kg/min
35
+ # calc = Calcpace.new
36
+ # calc.estimate_vo2max(10.0, '00:40:00') #=> 51.9
37
+ def estimate_vo2max(distance_km, time)
38
+ distance_m = distance_km.to_f * 1000
39
+ time_min = parse_time_minutes(time)
40
+
41
+ check_positive(distance_m, 'Distance')
42
+ check_positive(time_min, 'Time')
43
+
44
+ velocity = distance_m / time_min
45
+ vo2 = vo2_at_velocity(velocity)
46
+ pct_vo2max = percent_vo2max(time_min)
47
+
48
+ (vo2 / pct_vo2max).round(1)
49
+ end
50
+
51
+ # Returns a descriptive label for a given VO2max value
52
+ #
53
+ # @param value [Numeric] VO2max in ml/kg/min
54
+ # @return [String] label: "Beginner", "Fair", "Good", "Very Good", "Excellent", or "Elite"
55
+ # @raise [ArgumentError] if value is not positive
56
+ #
57
+ # @example
58
+ # calc.vo2max_label(51.9) #=> "Very Good"
59
+ def vo2max_label(value)
60
+ check_positive(value.to_f, 'VO2max value')
61
+
62
+ VO2MAX_LABELS.find { |entry| value.to_f >= entry[:min] }[:label]
63
+ end
64
+
65
+ private
66
+
67
+ def vo2_at_velocity(velocity)
68
+ -4.60 + (0.182258 * velocity) + (0.000104 * (velocity**2))
69
+ end
70
+
71
+ def percent_vo2max(time_min)
72
+ 0.8 +
73
+ (0.1894393 * Math.exp(-0.012778 * time_min)) +
74
+ (0.2989558 * Math.exp(-0.1932605 * time_min))
75
+ end
76
+
77
+ def parse_time_minutes(time)
78
+ return time.to_f / 60.0 if time.is_a?(Numeric)
79
+
80
+ check_time(time.to_s)
81
+ convert_to_seconds(time.to_s) / 60.0
82
+ end
83
+ end
data/lib/calcpace.rb CHANGED
@@ -11,6 +11,7 @@ require_relative 'calcpace/pace_converter'
11
11
  require_relative 'calcpace/race_predictor'
12
12
  require_relative 'calcpace/race_splits'
13
13
  require_relative 'calcpace/track_calculator'
14
+ require_relative 'calcpace/vo2max_estimator'
14
15
 
15
16
  # Calcpace - A Ruby gem for pace, distance, and time calculations
16
17
  #
@@ -44,6 +45,7 @@ class Calcpace
44
45
  include RacePredictor
45
46
  include RaceSplits
46
47
  include TrackCalculator
48
+ include Vo2maxEstimator
47
49
 
48
50
  # Creates a new Calcpace instance
49
51
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: calcpace
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.2
4
+ version: 1.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - João Gilberto Saraiva
@@ -9,8 +9,9 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: Ruby gem for pace, distance, time, and speed calculations. Supports multiple
13
- units and formats.
12
+ description: 'Ruby gem for running and cycling calculations: pace, time, distance,
13
+ unit conversions (30+ units), race predictions (Riegel & Cameron), GPS track analysis
14
+ (Haversine, elevation gain, per-km splits), and VO2max estimation (Daniels & Gilbert).'
14
15
  email:
15
16
  - joaogilberto@tuta.io
16
17
  executables: []
@@ -42,6 +43,7 @@ files:
42
43
  - lib/calcpace/race_splits.rb
43
44
  - lib/calcpace/track_calculator.rb
44
45
  - lib/calcpace/version.rb
46
+ - lib/calcpace/vo2max_estimator.rb
45
47
  homepage: https://github.com/0jonjo/calcpace
46
48
  licenses:
47
49
  - MIT
@@ -65,5 +67,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
67
  requirements: []
66
68
  rubygems_version: 4.0.6
67
69
  specification_version: 4
68
- summary: A Ruby gem for pace, distance, and time calculations.
70
+ summary: Pace, distance, GPS track analysis, and VO2max calculations for runners and
71
+ cyclists.
69
72
  test_files: []