git-fit 0.13.0 → 0.14.0
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/db/migrations/003_add_elevation_fields.rb +19 -0
- data/lib/git-fit.rb +1 -0
- data/lib/git_fit/auth/garmin.rb +1 -1
- data/lib/git_fit/auth/strava.rb +2 -2
- data/lib/git_fit/cli/strava.rb +2 -2
- data/lib/git_fit/cli/sync.rb +1 -1
- data/lib/git_fit/cli.rb +1 -1
- data/lib/git_fit/config.rb +8 -2
- data/lib/git_fit/dedup/field_elector.rb +4 -0
- data/lib/git_fit/export/csv.rb +2 -1
- data/lib/git_fit/export/json.rb +6 -1
- data/lib/git_fit/fit/decoder.js +1 -1
- data/lib/git_fit/fit/decoder.rb +6 -0
- data/lib/git_fit/import/apple_health.rb +36 -7
- data/lib/git_fit/import/tbulu.rb +2 -2
- data/lib/git_fit/parser/base.rb +26 -0
- data/lib/git_fit/parser/fit.rb +15 -2
- data/lib/git_fit/parser/gpx.rb +3 -0
- data/lib/git_fit/parser/tbulu.rb +8 -2
- data/lib/git_fit/parser/tcx.rb +6 -1
- data/lib/git_fit/sync/garmin_base.rb +6 -2
- data/lib/git_fit/sync/igpsport.rb +28 -2
- data/lib/git_fit/sync/keep.rb +24 -0
- data/lib/git_fit/sync/lorem.rb +12 -4
- data/lib/git_fit/sync/strava.rb +15 -2
- data/lib/git_fit/sync/xingzhe.rb +1 -0
- data/lib/git_fit/sync/xoss.rb +23 -0
- data/lib/git_fit/version.rb +1 -1
- metadata +2 -1
data/lib/git_fit/fit/decoder.rb
CHANGED
|
@@ -24,6 +24,12 @@ module GitFit
|
|
|
24
24
|
bytes = File.read(path, mode: 'rb').bytes
|
|
25
25
|
CTX.eval("FitDecoder.fitSport(#{bytes.inspect})")
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
def self.session(path)
|
|
29
|
+
bytes = File.read(path, mode: 'rb').bytes
|
|
30
|
+
json = CTX.eval("FitDecoder.fitSession(#{bytes.inspect})")
|
|
31
|
+
json ? JSON.parse(json) : nil
|
|
32
|
+
end
|
|
27
33
|
end
|
|
28
34
|
end
|
|
29
35
|
end
|
|
@@ -147,7 +147,7 @@ module GitFit
|
|
|
147
147
|
|
|
148
148
|
l2_points = nil
|
|
149
149
|
polyline = nil
|
|
150
|
-
|
|
150
|
+
elevation = {}
|
|
151
151
|
|
|
152
152
|
if route_paths&.any?
|
|
153
153
|
gpx_l1_path = File.join(raw_dir, "#{run_id}.gpx")
|
|
@@ -189,14 +189,14 @@ module GitFit
|
|
|
189
189
|
|
|
190
190
|
coords = l2_points.map { |pt| [pt[:lat], pt[:lng]] }
|
|
191
191
|
polyline = GitFit::Geo::Polyline.encode(coords)
|
|
192
|
-
|
|
192
|
+
elevation = elevation_profile(l2_points)
|
|
193
193
|
end
|
|
194
194
|
end
|
|
195
195
|
|
|
196
196
|
title = build_title(workout_type, start_date)
|
|
197
|
-
attrs = build_attrs(source_name, run_id, stats, meta, polyline,
|
|
197
|
+
attrs = build_attrs(source_name, run_id, stats, meta, polyline,
|
|
198
198
|
workout_type, start_date, end_date, duration, duration_unit,
|
|
199
|
-
hr_avg, hr_max)
|
|
199
|
+
hr_avg: hr_avg, hr_max: hr_max, elevation: elevation)
|
|
200
200
|
attrs[:title] = title
|
|
201
201
|
|
|
202
202
|
if attrs[:start_date_local].nil? && l2_points&.any?
|
|
@@ -433,9 +433,9 @@ module GitFit
|
|
|
433
433
|
nil
|
|
434
434
|
end
|
|
435
435
|
|
|
436
|
-
def build_attrs(_source_name, run_id, stats, meta, polyline,
|
|
436
|
+
def build_attrs(_source_name, run_id, stats, meta, polyline,
|
|
437
437
|
workout_type, start_date, end_date, duration, duration_unit,
|
|
438
|
-
hr_avg
|
|
438
|
+
hr_avg: nil, hr_max: nil, elevation: {})
|
|
439
439
|
distance = nil
|
|
440
440
|
calories = nil
|
|
441
441
|
|
|
@@ -470,7 +470,11 @@ module GitFit
|
|
|
470
470
|
max_heartrate: hr_max,
|
|
471
471
|
average_speed: avg_speed,
|
|
472
472
|
calories: calories,
|
|
473
|
-
elevation_gain:
|
|
473
|
+
elevation_gain: elevation[:gain]&.round(1),
|
|
474
|
+
elevation_loss: elevation[:loss]&.round(1),
|
|
475
|
+
elevation_min: elevation[:min]&.round(1),
|
|
476
|
+
elevation_max: elevation[:max]&.round(1),
|
|
477
|
+
steps: steps_from_stats(stats),
|
|
474
478
|
average_temperature: parse_temperature(meta['HKWeatherTemperature']),
|
|
475
479
|
source: 'apple_health',
|
|
476
480
|
external_id: meta['HKExternalUUID'],
|
|
@@ -569,6 +573,31 @@ module GitFit
|
|
|
569
573
|
total
|
|
570
574
|
end
|
|
571
575
|
|
|
576
|
+
def steps_from_stats(stats)
|
|
577
|
+
sum = stats.dig('HKQuantityTypeIdentifierStepCount', 'sum')
|
|
578
|
+
sum&.to_f&.round&.nonzero?
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
def elevation_profile(points)
|
|
582
|
+
{
|
|
583
|
+
gain: elevation_gain_from_points(points),
|
|
584
|
+
loss: elevation_loss_from_points(points),
|
|
585
|
+
min: points.map { |pt| pt[:ele] }.compact.min,
|
|
586
|
+
max: points.map { |pt| pt[:ele] }.compact.max,
|
|
587
|
+
}
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def elevation_loss_from_points(points)
|
|
591
|
+
total = 0.0
|
|
592
|
+
(1...points.length).each do |i|
|
|
593
|
+
elev1 = points[i - 1][:ele] || 0.0
|
|
594
|
+
elev2 = points[i][:ele] || 0.0
|
|
595
|
+
diff = elev1 - elev2
|
|
596
|
+
total += diff if diff > 0
|
|
597
|
+
end
|
|
598
|
+
total
|
|
599
|
+
end
|
|
600
|
+
|
|
572
601
|
def apple_utc_to_local(utc_iso8601, tz_name)
|
|
573
602
|
return utc_iso8601 unless utc_iso8601 && tz_name
|
|
574
603
|
t = Time.parse(utc_iso8601)
|
data/lib/git_fit/import/tbulu.rb
CHANGED
|
@@ -110,8 +110,8 @@ module GitFit
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
def upsert_activity(activity)
|
|
113
|
-
#
|
|
114
|
-
persistable = activity.reject { |k, _| %i[
|
|
113
|
+
# total_up/total_down are hash-only signals, not DB columns (mapped to elevation_gain/loss by the parser)
|
|
114
|
+
persistable = activity.reject { |k, _| %i[total_up total_down].include?(k) }
|
|
115
115
|
super(persistable)
|
|
116
116
|
end
|
|
117
117
|
|
data/lib/git_fit/parser/base.rb
CHANGED
|
@@ -46,6 +46,28 @@ module GitFit
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
def elevation_loss(points)
|
|
50
|
+
return 0.0 if points.length < 2
|
|
51
|
+
(1...points.length).sum do |i|
|
|
52
|
+
elev1 = points[i - 1][2] || 0.0
|
|
53
|
+
elev2 = points[i][2] || 0.0
|
|
54
|
+
loss = elev1 - elev2
|
|
55
|
+
loss > 0 ? loss : 0.0
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def elevation_min(points)
|
|
60
|
+
elevations(points).min
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def elevation_max(points)
|
|
64
|
+
elevations(points).max
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def elevations(points)
|
|
68
|
+
points.map { |p| p[2] }.compact
|
|
69
|
+
end
|
|
70
|
+
|
|
49
71
|
def moving_time_from_points(points, threshold_s: 30)
|
|
50
72
|
return 0 if points.length < 2
|
|
51
73
|
total = 0
|
|
@@ -112,6 +134,10 @@ module GitFit
|
|
|
112
134
|
average_heartrate: data[:average_heartrate]&.round(1),
|
|
113
135
|
average_speed: data[:distance] && data[:moving_time].to_f.positive? ? (data[:distance].to_f / data[:moving_time]).round(2) : data[:average_speed],
|
|
114
136
|
elevation_gain: data[:elevation_gain]&.round(1),
|
|
137
|
+
elevation_loss: data[:elevation_loss]&.round(1),
|
|
138
|
+
elevation_min: data[:elevation_min]&.round(1),
|
|
139
|
+
elevation_max: data[:elevation_max]&.round(1),
|
|
140
|
+
steps: data[:steps]&.round,
|
|
115
141
|
source: @source_name || data[:source],
|
|
116
142
|
}
|
|
117
143
|
end
|
data/lib/git_fit/parser/fit.rb
CHANGED
|
@@ -25,6 +25,15 @@ module GitFit
|
|
|
25
25
|
|
|
26
26
|
sport_type = GitFit::FIT::Decoder.sport(path)
|
|
27
27
|
|
|
28
|
+
session = GitFit::FIT::Decoder.session(path)
|
|
29
|
+
if session && (session['totalAscent'].is_a?(Numeric) || session['totalDescent'].is_a?(Numeric))
|
|
30
|
+
elevation_gain = session['totalAscent'] || 0.0
|
|
31
|
+
elevation_loss = session['totalDescent'] || 0.0
|
|
32
|
+
else
|
|
33
|
+
elevation_gain = elevation_gain(points)
|
|
34
|
+
elevation_loss = elevation_loss(points)
|
|
35
|
+
end
|
|
36
|
+
|
|
28
37
|
[build_activity_attrs({
|
|
29
38
|
run_id: build_run_id('fit', SecureRandom.hex(8)),
|
|
30
39
|
name: nil,
|
|
@@ -38,7 +47,10 @@ module GitFit
|
|
|
38
47
|
location_country: nil,
|
|
39
48
|
summary_polyline: simplify_polyline(points.map { |p| [p[0], p[1]] }),
|
|
40
49
|
average_heartrate: average_heartrate(points),
|
|
41
|
-
elevation_gain: elevation_gain
|
|
50
|
+
elevation_gain: elevation_gain,
|
|
51
|
+
elevation_loss: elevation_loss,
|
|
52
|
+
elevation_min: elevation_min(points),
|
|
53
|
+
elevation_max: elevation_max(points),
|
|
42
54
|
source: 'fit',
|
|
43
55
|
})]
|
|
44
56
|
end
|
|
@@ -51,7 +63,8 @@ module GitFit
|
|
|
51
63
|
return nil unless lat && lon
|
|
52
64
|
ts = rec['timestamp']
|
|
53
65
|
time = ts.is_a?(Numeric) ? FIT_EPOCH + ts : parse_time(ts)
|
|
54
|
-
|
|
66
|
+
altitude = rec['enhancedAltitude'] || rec['altitude']
|
|
67
|
+
[lat, lon, altitude, time, rec['heartRate']]
|
|
55
68
|
end
|
|
56
69
|
|
|
57
70
|
def average_heartrate(points)
|
data/lib/git_fit/parser/gpx.rb
CHANGED
|
@@ -41,6 +41,9 @@ module GitFit
|
|
|
41
41
|
summary_polyline: simplify_polyline(points.map { |p| [p[0], p[1]] }),
|
|
42
42
|
average_heartrate: average_heartrate(points),
|
|
43
43
|
elevation_gain: elevation_gain(points),
|
|
44
|
+
elevation_loss: elevation_loss(points),
|
|
45
|
+
elevation_min: elevation_min(points),
|
|
46
|
+
elevation_max: elevation_max(points),
|
|
44
47
|
source: 'gpx',
|
|
45
48
|
}
|
|
46
49
|
build_activity_attrs(data)
|
data/lib/git_fit/parser/tbulu.rb
CHANGED
|
@@ -82,7 +82,10 @@ module GitFit
|
|
|
82
82
|
location_country: nil,
|
|
83
83
|
summary_polyline: nil,
|
|
84
84
|
average_heartrate: nil,
|
|
85
|
-
elevation_gain: elevation_gain(points),
|
|
85
|
+
elevation_gain: ti['totalup']&.to_f || elevation_gain(points),
|
|
86
|
+
elevation_loss: ti['totaldown']&.to_f || elevation_loss(points),
|
|
87
|
+
elevation_min: elevation_min(points),
|
|
88
|
+
elevation_max: elevation_max(points),
|
|
86
89
|
source: '2bulu',
|
|
87
90
|
})
|
|
88
91
|
attach_sensor_stats(attrs, ti)
|
|
@@ -204,7 +207,10 @@ module GitFit
|
|
|
204
207
|
location_country: nil,
|
|
205
208
|
summary_polyline: nil,
|
|
206
209
|
average_heartrate: nil,
|
|
207
|
-
elevation_gain: elevation_gain(points),
|
|
210
|
+
elevation_gain: ext['ElevationGain']&.to_f || elevation_gain(points),
|
|
211
|
+
elevation_loss: ext['ElevationLoss']&.to_f || elevation_loss(points),
|
|
212
|
+
elevation_min: elevation_min(points),
|
|
213
|
+
elevation_max: elevation_max(points),
|
|
208
214
|
source: '2bulu',
|
|
209
215
|
})
|
|
210
216
|
attach_kml_stats(attrs, ext)
|
data/lib/git_fit/parser/tcx.rb
CHANGED
|
@@ -27,6 +27,7 @@ module GitFit
|
|
|
27
27
|
total_moving_time = lap_data.sum { |ld| ld[:moving_time] || 0 }
|
|
28
28
|
total_elapsed_time = lap_data.sum { |ld| ld[:elapsed_time] || 0 }
|
|
29
29
|
elevation_gain = lap_data.sum { |ld| ld[:elevation_gain] || 0.0 }
|
|
30
|
+
elevation_loss = lap_data.sum { |ld| ld[:elevation_loss] || 0.0 }
|
|
30
31
|
avg_hr = lap_data.map { |ld| ld[:avg_hr] }.compact
|
|
31
32
|
all_points = lap_data.flat_map { |ld| ld[:points] }
|
|
32
33
|
times = all_points.map { |p| p[3] }.compact
|
|
@@ -45,6 +46,9 @@ module GitFit
|
|
|
45
46
|
summary_polyline: simplify_polyline(all_points.map { |p| [p[0], p[1]] }),
|
|
46
47
|
average_heartrate: avg_hr.empty? ? nil : (avg_hr.sum / avg_hr.size).round(1),
|
|
47
48
|
elevation_gain: elevation_gain,
|
|
49
|
+
elevation_loss: elevation_loss,
|
|
50
|
+
elevation_min: elevation_min(all_points),
|
|
51
|
+
elevation_max: elevation_max(all_points),
|
|
48
52
|
source: 'tcx',
|
|
49
53
|
}
|
|
50
54
|
build_activity_attrs(data)
|
|
@@ -58,10 +62,11 @@ module GitFit
|
|
|
58
62
|
avg_hr_node = lap.at_xpath('tcx:AverageHeartRateBpm/tcx:Value', NS)
|
|
59
63
|
avg_hr = avg_hr_node&.text&.to_f
|
|
60
64
|
elev_gain = points.length >= 2 ? elevation_gain(points) : 0.0
|
|
65
|
+
elev_loss = points.length >= 2 ? elevation_loss(points) : 0.0
|
|
61
66
|
moving_time = moving_time_from_points(points, threshold_s: 30)
|
|
62
67
|
{ distance: distance, elapsed_time: total_time&.round,
|
|
63
68
|
moving_time: moving_time > 0 ? moving_time : (total_time&.round || 0),
|
|
64
|
-
avg_hr: avg_hr, elevation_gain: elev_gain, points: points }
|
|
69
|
+
avg_hr: avg_hr, elevation_gain: elev_gain, elevation_loss: elev_loss, points: points }
|
|
65
70
|
end
|
|
66
71
|
|
|
67
72
|
def parse_track(lap)
|
|
@@ -199,7 +199,7 @@ module GitFit
|
|
|
199
199
|
end
|
|
200
200
|
|
|
201
201
|
def save_secret_to_config
|
|
202
|
-
path = @config['config_path'] ||
|
|
202
|
+
path = @config['config_path'] || GitFit::Config.default_path
|
|
203
203
|
return unless File.exist?(path)
|
|
204
204
|
|
|
205
205
|
cfg = YAML.safe_load(File.read(path)) || {}
|
|
@@ -477,7 +477,7 @@ module GitFit
|
|
|
477
477
|
end
|
|
478
478
|
|
|
479
479
|
def reauth_hint
|
|
480
|
-
"Re-auth: set sync.#{source_prefix}.secret in
|
|
480
|
+
"Re-auth: set sync.#{source_prefix}.secret in #{GitFit::Config.default_path}"
|
|
481
481
|
end
|
|
482
482
|
|
|
483
483
|
def build_attrs(detail, activity_id, type, polyline = nil)
|
|
@@ -509,6 +509,10 @@ module GitFit
|
|
|
509
509
|
average_temperature: summary['avgTemperature']&.to_f,
|
|
510
510
|
average_speed: summary['averageSpeed']&.to_f,
|
|
511
511
|
elevation_gain: summary['elevationGain']&.to_f,
|
|
512
|
+
elevation_loss: summary['elevationLoss']&.to_f,
|
|
513
|
+
elevation_min: summary['minElevation']&.to_f,
|
|
514
|
+
elevation_max: summary['maxElevation']&.to_f,
|
|
515
|
+
steps: summary['steps']&.to_i,
|
|
512
516
|
source: source_prefix,
|
|
513
517
|
}
|
|
514
518
|
end
|
|
@@ -103,8 +103,9 @@ module GitFit
|
|
|
103
103
|
write_standardized_json(wgs_points, ride_id)
|
|
104
104
|
polyline = Geo::Polyline.encode(wgs_points.map { |pt| [pt['positionLat'], pt['positionLong']] })
|
|
105
105
|
sensor_summary = compute_sensor_summary(wgs_points)
|
|
106
|
+
elevation_stats = elevation_stats_from_records(wgs_points, fit_path)
|
|
106
107
|
|
|
107
|
-
attrs = build_attrs(act, ride_id, polyline, sensor_summary)
|
|
108
|
+
attrs = build_attrs(act, ride_id, polyline, sensor_summary, elevation_stats)
|
|
108
109
|
return false unless attrs
|
|
109
110
|
|
|
110
111
|
upsert_activity(attrs)
|
|
@@ -153,7 +154,7 @@ module GitFit
|
|
|
153
154
|
JSON.parse(resp.body).dig('data', 'rows')
|
|
154
155
|
end
|
|
155
156
|
|
|
156
|
-
def build_attrs(act, ride_id, polyline = nil, sensor = {})
|
|
157
|
+
def build_attrs(act, ride_id, polyline = nil, sensor = {}, elevation_stats = {})
|
|
157
158
|
type = map_sport(act['exerciseType'])
|
|
158
159
|
start_t = @fit_start_utc || parse_date(act['startTime'])
|
|
159
160
|
start_t_local = @fit_start_local || start_t
|
|
@@ -182,6 +183,9 @@ module GitFit
|
|
|
182
183
|
average_temperature: sensor[:average_temperature],
|
|
183
184
|
average_speed: avg_speed > 0 ? avg_speed : nil,
|
|
184
185
|
elevation_gain: act['totalAscent'].to_f.nonzero?,
|
|
186
|
+
elevation_loss: elevation_stats[:elevation_loss],
|
|
187
|
+
elevation_min: elevation_stats[:elevation_min],
|
|
188
|
+
elevation_max: elevation_stats[:elevation_max],
|
|
185
189
|
summary_polyline: polyline,
|
|
186
190
|
source: 'igpsport',
|
|
187
191
|
}
|
|
@@ -204,6 +208,28 @@ module GitFit
|
|
|
204
208
|
}
|
|
205
209
|
end
|
|
206
210
|
|
|
211
|
+
def elevation_stats_from_records(records, fit_path)
|
|
212
|
+
alts = records.map { |r| r['enhancedAltitude'] || r['altitude'] }.compact
|
|
213
|
+
points_loss = 0.0
|
|
214
|
+
(1...alts.size).each do |i|
|
|
215
|
+
d = alts[i - 1] - alts[i]
|
|
216
|
+
points_loss += d if d > 0
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
session_descent = begin
|
|
220
|
+
session = FIT::Decoder.session(fit_path)
|
|
221
|
+
session && session['totalDescent'].is_a?(Numeric) ? session['totalDescent'] : nil
|
|
222
|
+
rescue StandardError
|
|
223
|
+
nil
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
{
|
|
227
|
+
elevation_loss: (session_descent || (alts.size >= 2 ? points_loss : nil))&.round(1),
|
|
228
|
+
elevation_min: alts.min&.round(1),
|
|
229
|
+
elevation_max: alts.max&.round(1),
|
|
230
|
+
}
|
|
231
|
+
end
|
|
232
|
+
|
|
207
233
|
def map_sport(exercise_type)
|
|
208
234
|
EXERCISE_MAP[exercise_type] || 'other'
|
|
209
235
|
end
|
data/lib/git_fit/sync/keep.rb
CHANGED
|
@@ -306,12 +306,14 @@ module GitFit
|
|
|
306
306
|
|
|
307
307
|
polyline_str = nil
|
|
308
308
|
total_elevation = run_data['elevation']
|
|
309
|
+
total_elevation_loss = nil
|
|
309
310
|
computed_distance = run_data['distance']
|
|
310
311
|
|
|
311
312
|
if wgs_points.any?
|
|
312
313
|
if wgs_points.any? { |pt| pt[3] }
|
|
313
314
|
computed_distance ||= total_distance_haversine(wgs_points)
|
|
314
315
|
total_elevation ||= elevation_gain_from_points(wgs_points)
|
|
316
|
+
total_elevation_loss = elevation_loss_from_points(wgs_points)
|
|
315
317
|
end
|
|
316
318
|
|
|
317
319
|
polyline_str = Geo::Polyline.encode(wgs_points.map { |pt| [pt[0], pt[1]] })
|
|
@@ -336,6 +338,9 @@ module GitFit
|
|
|
336
338
|
average_heartrate: avg_hr&.round(1),
|
|
337
339
|
average_speed: computed_distance && moving_time&.positive? ? (computed_distance.to_f / moving_time).round(2) : nil,
|
|
338
340
|
elevation_gain: total_elevation&.round(1),
|
|
341
|
+
elevation_loss: total_elevation_loss&.round(1),
|
|
342
|
+
elevation_min: elevation_min_from_points(wgs_points),
|
|
343
|
+
elevation_max: elevation_max_from_points(wgs_points),
|
|
339
344
|
source: 'keep',
|
|
340
345
|
}
|
|
341
346
|
end
|
|
@@ -424,6 +429,25 @@ module GitFit
|
|
|
424
429
|
total
|
|
425
430
|
end
|
|
426
431
|
|
|
432
|
+
def elevation_loss_from_points(points)
|
|
433
|
+
total = 0.0
|
|
434
|
+
(1...points.length).each do |i|
|
|
435
|
+
elev1 = points[i - 1][2] || 0.0
|
|
436
|
+
elev2 = points[i][2] || 0.0
|
|
437
|
+
diff = elev1 - elev2
|
|
438
|
+
total += diff if diff > 0
|
|
439
|
+
end
|
|
440
|
+
total
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def elevation_min_from_points(points)
|
|
444
|
+
points.map { |p| p[2] }.compact.min
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def elevation_max_from_points(points)
|
|
448
|
+
points.map { |p| p[2] }.compact.max
|
|
449
|
+
end
|
|
450
|
+
|
|
427
451
|
def spider_sleep
|
|
428
452
|
sleep 0.1
|
|
429
453
|
end
|
data/lib/git_fit/sync/lorem.rb
CHANGED
|
@@ -17,28 +17,32 @@ module GitFit
|
|
|
17
17
|
location_country: 'CN', average_heartrate: 148.0, max_heartrate: 165.0,
|
|
18
18
|
average_cadence: 172.0, max_cadence: 185.0, average_power: nil, max_power: nil,
|
|
19
19
|
average_temperature: 22.0, calories: 380,
|
|
20
|
-
average_speed: 5200.0 / 1800, elevation_gain: 125.0,
|
|
20
|
+
average_speed: 5200.0 / 1800, elevation_gain: 125.0, elevation_loss: 110.0,
|
|
21
|
+
elevation_min: 35.0, elevation_max: 160.0, steps: 8200, external_id: 'lorem_001' },
|
|
21
22
|
{ id: 'beta_002', name: 'Ipsum Cycling Session', sport_category: 'ride',
|
|
22
23
|
distance: 35000.0, moving_time: 5400, elapsed_time: 6000,
|
|
23
24
|
start_date: '2025-01-14T08:00:00Z', start_date_local: '2025-01-14T16:00:00+08:00',
|
|
24
25
|
location_country: 'CN', average_heartrate: 138.0, max_heartrate: 155.0,
|
|
25
26
|
average_cadence: 85.0, max_cadence: 105.0, average_power: 185.0, max_power: 420.0,
|
|
26
27
|
average_temperature: 18.0, calories: 850,
|
|
27
|
-
average_speed: 35000.0 / 5400, elevation_gain: 450.0,
|
|
28
|
+
average_speed: 35000.0 / 5400, elevation_gain: 450.0, elevation_loss: 430.0,
|
|
29
|
+
elevation_min: 12.0, elevation_max: 460.0, external_id: 'lorem_002' },
|
|
28
30
|
{ id: 'gamma_003', name: 'Dolor Hike', sport_category: 'hike',
|
|
29
31
|
distance: 12000.0, moving_time: 7200, elapsed_time: 8100,
|
|
30
32
|
start_date: '2025-01-13T09:00:00Z', start_date_local: '2025-01-13T17:00:00+08:00',
|
|
31
33
|
location_country: 'CN', average_heartrate: 122.0, max_heartrate: 142.0,
|
|
32
34
|
average_cadence: 110.0, max_cadence: 135.0, average_power: nil, max_power: nil,
|
|
33
35
|
average_temperature: 15.0, calories: 620,
|
|
34
|
-
average_speed: 12000.0 / 7200, elevation_gain: 820.0,
|
|
36
|
+
average_speed: 12000.0 / 7200, elevation_gain: 820.0, elevation_loss: 790.0,
|
|
37
|
+
elevation_min: 300.0, elevation_max: 900.0, steps: 15_200, external_id: 'lorem_003' },
|
|
35
38
|
{ id: 'delta_004', name: 'Sit Amet Walk', sport_category: 'walk',
|
|
36
39
|
distance: 5000.0, moving_time: 3600, elapsed_time: 3900,
|
|
37
40
|
start_date: '2025-01-12T18:00:00Z', start_date_local: '2025-01-12T18:00:00+08:00',
|
|
38
41
|
location_country: 'CN', average_heartrate: 108.0, max_heartrate: 125.0,
|
|
39
42
|
average_cadence: 105.0, max_cadence: 118.0, average_power: nil, max_power: nil,
|
|
40
43
|
average_temperature: 20.0, calories: 280,
|
|
41
|
-
average_speed: 5000.0 / 3600, elevation_gain: 35.0,
|
|
44
|
+
average_speed: 5000.0 / 3600, elevation_gain: 35.0, elevation_loss: 40.0,
|
|
45
|
+
steps: 6400, external_id: 'lorem_004' },
|
|
42
46
|
{ id: 'epsilon_005', name: 'Consectetur Swim', sport_category: 'swim',
|
|
43
47
|
distance: 2000.0, moving_time: 2400, elapsed_time: 2700,
|
|
44
48
|
start_date: '2025-01-11T07:00:00Z', start_date_local: '2025-01-11T15:00:00+08:00',
|
|
@@ -100,6 +104,10 @@ module GitFit
|
|
|
100
104
|
calories: act[:calories],
|
|
101
105
|
average_speed: act[:average_speed],
|
|
102
106
|
elevation_gain: act[:elevation_gain],
|
|
107
|
+
elevation_loss: act[:elevation_loss],
|
|
108
|
+
elevation_min: act[:elevation_min],
|
|
109
|
+
elevation_max: act[:elevation_max],
|
|
110
|
+
steps: act[:steps],
|
|
103
111
|
source: 'lorem',
|
|
104
112
|
external_id: act[:external_id],
|
|
105
113
|
}
|
data/lib/git_fit/sync/strava.rb
CHANGED
|
@@ -86,7 +86,8 @@ module GitFit
|
|
|
86
86
|
act.dig('map', 'summary_polyline')
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
attrs = build_attrs(act, type, polyline, external_id: external_id
|
|
89
|
+
attrs = build_attrs(act, type, polyline, external_id: external_id,
|
|
90
|
+
elevation_loss: l2 ? elevation_loss_from_stream(l2) : nil)
|
|
90
91
|
upsert_activity(attrs)
|
|
91
92
|
attrs
|
|
92
93
|
end
|
|
@@ -170,7 +171,7 @@ module GitFit
|
|
|
170
171
|
data && i < data.size ? data[i] : nil
|
|
171
172
|
end
|
|
172
173
|
|
|
173
|
-
def build_attrs(act, category, polyline = nil, external_id: nil)
|
|
174
|
+
def build_attrs(act, category, polyline = nil, external_id: nil, elevation_loss: nil)
|
|
174
175
|
start_t = Time.parse(act['start_date']) rescue nil
|
|
175
176
|
local_t = Time.parse(act['start_date_local']) rescue nil
|
|
176
177
|
|
|
@@ -196,11 +197,23 @@ module GitFit
|
|
|
196
197
|
average_temperature: act['average_temp'],
|
|
197
198
|
average_speed: act['average_speed'],
|
|
198
199
|
elevation_gain: act['total_elevation_gain'],
|
|
200
|
+
elevation_loss: elevation_loss&.round(1),
|
|
201
|
+
elevation_min: act['elev_low'],
|
|
202
|
+
elevation_max: act['elev_high'],
|
|
199
203
|
source: 'strava',
|
|
200
204
|
external_id: external_id,
|
|
201
205
|
}
|
|
202
206
|
end
|
|
203
207
|
|
|
208
|
+
def elevation_loss_from_stream(l2)
|
|
209
|
+
alts = l2.filter_map { |pt| pt[:altitude] }
|
|
210
|
+
return nil if alts.size < 2
|
|
211
|
+
(1...alts.size).sum do |i|
|
|
212
|
+
diff = alts[i - 1] - alts[i]
|
|
213
|
+
diff > 0 ? diff : 0.0
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
204
217
|
def map_sport_type(type, sport_type)
|
|
205
218
|
SportMapper.canonicalize(sport_type || type)
|
|
206
219
|
end
|
data/lib/git_fit/sync/xingzhe.rb
CHANGED
|
@@ -251,6 +251,7 @@ module GitFit
|
|
|
251
251
|
start_date_local: start_t.iso8601,
|
|
252
252
|
average_speed: (act['avg_speed'].to_f / 3.6).round(2).nonzero?,
|
|
253
253
|
elevation_gain: act['elevation_gain'].to_f.nonzero?,
|
|
254
|
+
elevation_loss: act['elevation_loss'].to_f.nonzero?,
|
|
254
255
|
source: 'xingzhe',
|
|
255
256
|
}
|
|
256
257
|
|
data/lib/git_fit/sync/xoss.rb
CHANGED
|
@@ -316,6 +316,7 @@ module GitFit
|
|
|
316
316
|
pts = wgs_points.map { |pt| [pt['positionLat'], pt['positionLong']] }
|
|
317
317
|
attrs[:summary_polyline] = Geo::Polyline.encode(pts)
|
|
318
318
|
attrs.merge!(compute_sensor_summary(wgs_points))
|
|
319
|
+
attrs.merge!(elevation_stats_from_records(wgs_points, raw_path(act_id)))
|
|
319
320
|
end
|
|
320
321
|
end
|
|
321
322
|
end
|
|
@@ -371,6 +372,28 @@ module GitFit
|
|
|
371
372
|
}
|
|
372
373
|
end
|
|
373
374
|
|
|
375
|
+
def elevation_stats_from_records(records, fit_path)
|
|
376
|
+
alts = records.map { |r| r['enhancedAltitude'] || r['altitude'] }.compact
|
|
377
|
+
points_loss = 0.0
|
|
378
|
+
(1...alts.size).each do |i|
|
|
379
|
+
d = alts[i - 1] - alts[i]
|
|
380
|
+
points_loss += d if d > 0
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
session_descent = begin
|
|
384
|
+
session = FIT::Decoder.session(fit_path)
|
|
385
|
+
session && session['totalDescent'].is_a?(Numeric) ? session['totalDescent'] : nil
|
|
386
|
+
rescue StandardError
|
|
387
|
+
nil
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
{
|
|
391
|
+
elevation_loss: (session_descent || (alts.size >= 2 ? points_loss : nil))&.round(1),
|
|
392
|
+
elevation_min: alts.min&.round(1),
|
|
393
|
+
elevation_max: alts.max&.round(1),
|
|
394
|
+
}
|
|
395
|
+
end
|
|
396
|
+
|
|
374
397
|
def map_sport(sport)
|
|
375
398
|
SPORT_MAP[sport] || 'other'
|
|
376
399
|
end
|
data/lib/git_fit/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: git-fit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lax
|
|
@@ -244,6 +244,7 @@ extra_rdoc_files: []
|
|
|
244
244
|
files:
|
|
245
245
|
- db/migrations/001_full_schema.rb
|
|
246
246
|
- db/migrations/002_iso8601_time_format.rb
|
|
247
|
+
- db/migrations/003_add_elevation_fields.rb
|
|
247
248
|
- exe/git-fit
|
|
248
249
|
- lib/git-fit.rb
|
|
249
250
|
- lib/git_fit/auth/garmin.rb
|