git-fit 0.19.0 → 0.21.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/004_add_elevation_terrain_fields.rb +19 -0
- data/lib/git-fit.rb +1 -0
- data/lib/git_fit/cli/elevation.rb +30 -4
- data/lib/git_fit/elevation/backfill.rb +5 -22
- data/lib/git_fit/elevation/correct.rb +189 -0
- data/lib/git_fit/export/calculator.rb +6 -6
- data/lib/git_fit/export/csv.rb +1 -0
- data/lib/git_fit/export/json.rb +7 -5
- data/lib/git_fit/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e9ac100a25388c8d2e9a82f541b14b369a08f4307cfa70949032c25a8afc7c74
|
|
4
|
+
data.tar.gz: 60eb1e4c683317df12d66260517b21db9841823b9b20de6defa2819bbecd4b65
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a50e41061abe840e12c2a2f5741cef761dd97f5d87867d830bcb04e6ce7da61e8da48ff54e2500252c8c78c3f5a6a033dde69d695d49257a8d08f97c24799a02
|
|
7
|
+
data.tar.gz: 6db8e171e162cb31c9aca678c0db13e8271f1d61d0d1bf2097f12c1189d8130861349a69991907a7f0cc36c69e36b2892db7dd6f4950b984ec3473e15e2fd5b8
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Sequel.migration do
|
|
2
|
+
up do
|
|
3
|
+
alter_table(:activities) do
|
|
4
|
+
add_column :elevation_gain_terrain, Float
|
|
5
|
+
add_column :elevation_loss_terrain, Float
|
|
6
|
+
add_column :elevation_min_terrain, Float
|
|
7
|
+
add_column :elevation_max_terrain, Float
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
down do
|
|
12
|
+
alter_table(:activities) do
|
|
13
|
+
drop_column :elevation_max_terrain
|
|
14
|
+
drop_column :elevation_min_terrain
|
|
15
|
+
drop_column :elevation_loss_terrain
|
|
16
|
+
drop_column :elevation_gain_terrain
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/git-fit.rb
CHANGED
|
@@ -95,6 +95,7 @@ require_relative 'git_fit/cli/geo_cli'
|
|
|
95
95
|
require_relative 'git_fit/strava_web/file_check'
|
|
96
96
|
require_relative 'git_fit/cli/elevation'
|
|
97
97
|
require_relative 'git_fit/elevation/backfill'
|
|
98
|
+
require_relative 'git_fit/elevation/correct'
|
|
98
99
|
require_relative 'git_fit/elevation/terrain_coeff'
|
|
99
100
|
require_relative 'git_fit/cli/strava'
|
|
100
101
|
require_relative 'git_fit/auth/garmin_token'
|
|
@@ -11,7 +11,7 @@ module GitFit
|
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
desc 'backfill [SOURCE]', 'Backfill elevation data (
|
|
14
|
+
desc 'backfill [SOURCE]', 'Backfill raw elevation data (device/API values) from FIT files or L2 data'
|
|
15
15
|
option :force, type: :boolean, default: false, desc: 'Overwrite existing values (default: only fill NULLs)'
|
|
16
16
|
def backfill(source = nil)
|
|
17
17
|
config = git_fit_config
|
|
@@ -28,7 +28,33 @@ module GitFit
|
|
|
28
28
|
say_status :error, "Elevation backfill failed: #{e.message}", :red
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
desc '
|
|
31
|
+
desc 'correct [SOURCE]', 'Apply elevation correction into _terrain columns (adaptive hysteresis; --dem adds DEM)'
|
|
32
|
+
option :force, type: :boolean, default: false, desc: 'Recompute even when _terrain columns are already populated'
|
|
33
|
+
option :'dry-run', type: :boolean, default: false, desc: 'Count records without writing to database'
|
|
34
|
+
option :dem, type: :boolean, default: true,
|
|
35
|
+
desc: 'Enable DEM correction (default: true; --no-dem for hysteresis only)'
|
|
36
|
+
def correct(source = nil)
|
|
37
|
+
config = git_fit_config
|
|
38
|
+
conn = GitFit::DB::Connection.new(config.db_path)
|
|
39
|
+
conn.migrate!
|
|
40
|
+
db = conn.db
|
|
41
|
+
|
|
42
|
+
result = GitFit::Elevation::Correct.new(
|
|
43
|
+
db,
|
|
44
|
+
force: options[:force],
|
|
45
|
+
dry_run: options[:'dry-run'],
|
|
46
|
+
dem: options[:dem],
|
|
47
|
+
).call(source)
|
|
48
|
+
|
|
49
|
+
msg = "Elevation correct: #{result[:updated]} updated, " \
|
|
50
|
+
"#{result[:skipped]} skipped, #{result[:failed]} failed, " \
|
|
51
|
+
"#{result[:no_model]} no_model"
|
|
52
|
+
say_status :done, msg, :green
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
say_status :error, "Elevation correct failed: #{e.message}", :red
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
desc 'train', 'Train DEM terrain coefficient model from activity data', hide: true
|
|
32
58
|
option :sources, type: :string, default: nil, desc: 'Comma-separated source adapters (default: all)'
|
|
33
59
|
option :output, type: :string, default: nil, desc: 'Output path for model file'
|
|
34
60
|
def train
|
|
@@ -51,7 +77,7 @@ module GitFit
|
|
|
51
77
|
exit 1
|
|
52
78
|
end
|
|
53
79
|
|
|
54
|
-
desc 'infer GAIN_PER_KM RANGE_PER_KM SEG_DENSITY', 'Infer optimal h threshold for terrain'
|
|
80
|
+
desc 'infer GAIN_PER_KM RANGE_PER_KM SEG_DENSITY', 'Infer optimal h threshold for terrain', hide: true
|
|
55
81
|
def infer(gain_per_km, range_per_km, seg_density)
|
|
56
82
|
g = gain_per_km.to_f
|
|
57
83
|
r = range_per_km.to_f
|
|
@@ -77,7 +103,7 @@ module GitFit
|
|
|
77
103
|
exit 1
|
|
78
104
|
end
|
|
79
105
|
|
|
80
|
-
desc 'model-info', 'Show information about the loaded DEM model'
|
|
106
|
+
desc 'model-info', 'Show information about the loaded DEM model', hide: true
|
|
81
107
|
def model_info
|
|
82
108
|
model = GitFit::Elevation::TerrainCoeff.model
|
|
83
109
|
path = GitFit::Elevation::TerrainCoeff.model_path
|
|
@@ -5,8 +5,6 @@ module GitFit
|
|
|
5
5
|
class Backfill
|
|
6
6
|
SOURCES = %w[igpsport xoss strava garmin garmin_cn xingzhe].freeze
|
|
7
7
|
BATCH_SIZE = 100
|
|
8
|
-
# Fallback threshold when detail.json elevation is absent (issue #83: baro 2m / GPS 3m).
|
|
9
|
-
ELEV_THRESHOLD = 2.0
|
|
10
8
|
|
|
11
9
|
def initialize(db, force: false)
|
|
12
10
|
@db = db
|
|
@@ -88,8 +86,8 @@ module GitFit
|
|
|
88
86
|
nil
|
|
89
87
|
end
|
|
90
88
|
|
|
91
|
-
gain = session&.dig('totalAscent').is_a?(Numeric) ? session['totalAscent'] :
|
|
92
|
-
loss = session&.dig('totalDescent').is_a?(Numeric) ? session['totalDescent'] :
|
|
89
|
+
gain = session&.dig('totalAscent').is_a?(Numeric) ? session['totalAscent'] : nil
|
|
90
|
+
loss = session&.dig('totalDescent').is_a?(Numeric) ? session['totalDescent'] : nil
|
|
93
91
|
|
|
94
92
|
{
|
|
95
93
|
elevation_gain: gain&.round(1),
|
|
@@ -102,22 +100,7 @@ module GitFit
|
|
|
102
100
|
nil
|
|
103
101
|
end
|
|
104
102
|
|
|
105
|
-
def compute_from_l2(
|
|
106
|
-
l2_path = File.join('data', 'std', source, "#{natural_id}.json")
|
|
107
|
-
return nil unless File.exist?(l2_path)
|
|
108
|
-
|
|
109
|
-
l2 = JSON.parse(File.read(l2_path))
|
|
110
|
-
alts = l2.filter_map { |pt| pt['altitude'] || pt[:altitude] }
|
|
111
|
-
return nil if alts.size < 2
|
|
112
|
-
|
|
113
|
-
{
|
|
114
|
-
elevation_gain: elevation_gain_from_alts(alts)&.round(1),
|
|
115
|
-
elevation_loss: elevation_loss_from_alts(alts)&.round(1),
|
|
116
|
-
elevation_min: alts.min&.round(1),
|
|
117
|
-
elevation_max: alts.max&.round(1),
|
|
118
|
-
}
|
|
119
|
-
rescue StandardError => e
|
|
120
|
-
warn "Elevation backfill [#{source}][#{natural_id}]: #{e.message}"
|
|
103
|
+
def compute_from_l2(_source, _natural_id)
|
|
121
104
|
nil
|
|
122
105
|
end
|
|
123
106
|
|
|
@@ -141,8 +124,8 @@ module GitFit
|
|
|
141
124
|
dl = detail && detail['elevation_loss']
|
|
142
125
|
dm = detail && detail['max_altitude']
|
|
143
126
|
|
|
144
|
-
gain = detail_gain?(dg) ? dg :
|
|
145
|
-
loss = detail_gain?(dl) ? dl :
|
|
127
|
+
gain = detail_gain?(dg) ? dg : nil
|
|
128
|
+
loss = detail_gain?(dl) ? dl : nil
|
|
146
129
|
|
|
147
130
|
# max: detail max_altitude is reliable (matches stream max in samples); else raw max.
|
|
148
131
|
# min: stream is per-track baro-offset, so only drop low spikes, keep the bulk.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GitFit
|
|
4
|
+
module Elevation
|
|
5
|
+
# Strategy-corrected elevation: L2 std → (optional DEM) → adaptive hysteresis
|
|
6
|
+
# threshold → activities.elevation_*_terrain. Raw device/API values stay in
|
|
7
|
+
# elevation_* (written by Backfill); this class only fills the _terrain columns.
|
|
8
|
+
class Correct
|
|
9
|
+
SOURCES = %w[garmin garmin_cn strava keep igpsport xoss xingzhe 2bulu apple_health].freeze
|
|
10
|
+
BARO_SOURCES = %w[xingzhe 2bulu apple_health].freeze
|
|
11
|
+
BATCH_SIZE = 100
|
|
12
|
+
STATIC_HYST_BARO = 2.0
|
|
13
|
+
STATIC_HYST_GPS = 3.0
|
|
14
|
+
|
|
15
|
+
def initialize(db, force: false, dry_run: false, dem: true)
|
|
16
|
+
@db = db
|
|
17
|
+
@force = force
|
|
18
|
+
@dry_run = dry_run
|
|
19
|
+
@dem = dem
|
|
20
|
+
@updated = 0
|
|
21
|
+
@skipped = 0
|
|
22
|
+
@failed = 0
|
|
23
|
+
@no_model = 0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def call(source = nil)
|
|
27
|
+
sources = source ? [source] : SOURCES
|
|
28
|
+
sources.each do |src|
|
|
29
|
+
correct_source(src)
|
|
30
|
+
end
|
|
31
|
+
{ updated: @updated, skipped: @skipped, failed: @failed, no_model: @no_model }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def correct_source(source)
|
|
37
|
+
offset = 0
|
|
38
|
+
loop do
|
|
39
|
+
rows = fetch_rows(source, offset)
|
|
40
|
+
break if rows.empty?
|
|
41
|
+
|
|
42
|
+
@db.transaction do
|
|
43
|
+
rows.each { |row| process_row(row, source) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
offset += BATCH_SIZE
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def fetch_rows(source, offset)
|
|
51
|
+
scope = @db[:activities].where(source: source)
|
|
52
|
+
unless @force
|
|
53
|
+
scope = scope.where(Sequel.|(
|
|
54
|
+
{ elevation_gain_terrain: nil },
|
|
55
|
+
{ elevation_loss_terrain: nil },
|
|
56
|
+
{ elevation_min_terrain: nil },
|
|
57
|
+
{ elevation_max_terrain: nil },
|
|
58
|
+
))
|
|
59
|
+
end
|
|
60
|
+
scope.order(:id).limit(BATCH_SIZE, offset).all
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def process_row(row, source)
|
|
64
|
+
run_id = row[:run_id]
|
|
65
|
+
natural_id = run_id.sub("#{source}_", '')
|
|
66
|
+
|
|
67
|
+
elev = compute_from_l2(source, natural_id)
|
|
68
|
+
return skip_row(run_id) unless elev
|
|
69
|
+
|
|
70
|
+
update_row(row, elev) unless @dry_run
|
|
71
|
+
@updated += 1
|
|
72
|
+
rescue StandardError => e
|
|
73
|
+
warn "Elevation correct [#{source}][#{natural_id}]: #{e.message}"
|
|
74
|
+
@failed += 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def compute_from_l2(source, natural_id)
|
|
78
|
+
l2_path = File.join('data', 'std', source, "#{natural_id}.json")
|
|
79
|
+
return nil unless File.exist?(l2_path)
|
|
80
|
+
|
|
81
|
+
l2 = JSON.parse(File.read(l2_path))
|
|
82
|
+
return nil unless l2.is_a?(Array)
|
|
83
|
+
|
|
84
|
+
pts = l2.filter_map do |pt|
|
|
85
|
+
next unless pt.is_a?(Hash)
|
|
86
|
+
next unless pt['latitude'] && pt['longitude'] && pt['altitude']
|
|
87
|
+
|
|
88
|
+
pt
|
|
89
|
+
end
|
|
90
|
+
return nil if pts.size < 2
|
|
91
|
+
|
|
92
|
+
locations = pts.map { |pt| [pt['latitude'].to_f, pt['longitude'].to_f] }
|
|
93
|
+
alts_raw = pts.map { |pt| pt['altitude'].to_f }
|
|
94
|
+
|
|
95
|
+
dem_alts = dem_corrected_altitudes(locations, alts_raw)
|
|
96
|
+
h = hysteresis_threshold(source, locations, alts_raw)
|
|
97
|
+
|
|
98
|
+
gain = elevation_gain_from_alts(dem_alts, threshold: h)
|
|
99
|
+
loss = elevation_loss_from_alts(dem_alts, threshold: h)
|
|
100
|
+
filtered = Backfill.filter_low_outliers(dem_alts)
|
|
101
|
+
|
|
102
|
+
{
|
|
103
|
+
elevation_gain_terrain: gain.round(1),
|
|
104
|
+
elevation_loss_terrain: loss.round(1),
|
|
105
|
+
elevation_min_terrain: (filtered.min || dem_alts.min)&.round(1),
|
|
106
|
+
elevation_max_terrain: dem_alts.max&.round(1),
|
|
107
|
+
}
|
|
108
|
+
rescue StandardError => e
|
|
109
|
+
warn "Elevation correct [#{source}][#{natural_id}]: #{e.message}"
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def dem_corrected_altitudes(locations, alts_raw)
|
|
114
|
+
return alts_raw unless @dem
|
|
115
|
+
|
|
116
|
+
elevs = TerrainCoeff.elevations_for(locations)
|
|
117
|
+
# Partial DEM batch failures leave nil holes; mixing them with raw
|
|
118
|
+
# altitudes would corrupt gain/loss — fall back wholesale instead.
|
|
119
|
+
return alts_raw if elevs.nil? || elevs.any?(&:nil?)
|
|
120
|
+
|
|
121
|
+
elevs
|
|
122
|
+
rescue StandardError => e
|
|
123
|
+
warn "DEM correction failed: #{e.message}; using raw altitudes"
|
|
124
|
+
@no_model += 1
|
|
125
|
+
alts_raw
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def hysteresis_threshold(source, locations, alts_raw)
|
|
129
|
+
gain_per_km, range_per_km, seg_density = terrain_features(locations, alts_raw)
|
|
130
|
+
m = TerrainCoeff.terrain_m(
|
|
131
|
+
gain_per_km: gain_per_km,
|
|
132
|
+
range_per_km: range_per_km,
|
|
133
|
+
seg_density: seg_density,
|
|
134
|
+
)
|
|
135
|
+
TerrainCoeff.hysteresis_threshold(m)
|
|
136
|
+
rescue StandardError => e
|
|
137
|
+
warn "adaptive threshold failed: #{e.message}; using static threshold"
|
|
138
|
+
static_threshold(source)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def terrain_features(locations, alts)
|
|
142
|
+
distances = Sync::Base.cumulative_distances(locations)
|
|
143
|
+
total_dist_km = (distances.last || 0) / 1000.0
|
|
144
|
+
if total_dist_km.positive?
|
|
145
|
+
gain_per_km = elevation_gain_from_alts(alts, threshold: 0.0) / total_dist_km
|
|
146
|
+
range_per_km = (alts.max - alts.min) / total_dist_km
|
|
147
|
+
seg_density = alts.size / total_dist_km
|
|
148
|
+
end
|
|
149
|
+
[gain_per_km || 0.0, range_per_km || 0.0, seg_density || 0.0]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def static_threshold(source)
|
|
153
|
+
BARO_SOURCES.include?(source) ? STATIC_HYST_BARO : STATIC_HYST_GPS
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def elevation_gain_from_alts(alts, threshold: 0.0)
|
|
157
|
+
return 0.0 if alts.size < 2
|
|
158
|
+
|
|
159
|
+
(1...alts.size).sum do |i|
|
|
160
|
+
d = alts[i] - alts[i - 1]
|
|
161
|
+
d > threshold ? d : 0.0
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def elevation_loss_from_alts(alts, threshold: 0.0)
|
|
166
|
+
return 0.0 if alts.size < 2
|
|
167
|
+
|
|
168
|
+
(1...alts.size).sum do |i|
|
|
169
|
+
d = alts[i - 1] - alts[i]
|
|
170
|
+
d > threshold ? d : 0.0
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def update_row(row, elev)
|
|
175
|
+
@db[:activities].where(id: row[:id]).update(
|
|
176
|
+
elevation_gain_terrain: elev[:elevation_gain_terrain],
|
|
177
|
+
elevation_loss_terrain: elev[:elevation_loss_terrain],
|
|
178
|
+
elevation_min_terrain: elev[:elevation_min_terrain],
|
|
179
|
+
elevation_max_terrain: elev[:elevation_max_terrain],
|
|
180
|
+
updated_at: Time.now.utc,
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def skip_row(_run_id)
|
|
185
|
+
@skipped += 1
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -44,8 +44,8 @@ module GitFit
|
|
|
44
44
|
cat = r[:sport_category] || 'other'
|
|
45
45
|
d = r[:distance] || 0
|
|
46
46
|
t = r[:moving_time] || 0
|
|
47
|
-
e = r[:elevation_gain] || 0
|
|
48
|
-
l = r[:elevation_loss] || 0
|
|
47
|
+
e = r[:elevation_gain_terrain] || r[:elevation_gain] || 0
|
|
48
|
+
l = r[:elevation_loss_terrain] || r[:elevation_loss] || 0
|
|
49
49
|
s = r[:steps] || 0
|
|
50
50
|
|
|
51
51
|
total_dist += d
|
|
@@ -113,8 +113,8 @@ module GitFit
|
|
|
113
113
|
acc[:count] += 1
|
|
114
114
|
acc[:distance_km] += (row[:distance] || 0) / 1000.0
|
|
115
115
|
acc[:moving_time_h] += (row[:moving_time] || 0) / 3600.0
|
|
116
|
-
acc[:total_elev] += row[:elevation_gain] || 0
|
|
117
|
-
acc[:total_loss] += row[:elevation_loss] || 0
|
|
116
|
+
acc[:total_elev] += row[:elevation_gain_terrain] || row[:elevation_gain] || 0
|
|
117
|
+
acc[:total_loss] += row[:elevation_loss_terrain] || row[:elevation_loss] || 0
|
|
118
118
|
acc[:total_steps] += row[:steps] || 0
|
|
119
119
|
add_if_positive(acc, :total_hr, :hr_count, row[:average_heartrate])
|
|
120
120
|
add_if_positive(acc, :total_cadence, :cadence_count, row[:average_cadence])
|
|
@@ -167,8 +167,8 @@ module GitFit
|
|
|
167
167
|
by_year.sort.map do |year, acts|
|
|
168
168
|
total_dist = acts.sum { |r| r[:distance] || 0 } / 1000.0
|
|
169
169
|
total_time = acts.sum { |r| r[:moving_time] || 0 }
|
|
170
|
-
total_elev = acts.sum { |r| r[:elevation_gain] || 0 }
|
|
171
|
-
total_loss = acts.sum { |r| r[:elevation_loss] || 0 }
|
|
170
|
+
total_elev = acts.sum { |r| r[:elevation_gain_terrain] || r[:elevation_gain] || 0 }
|
|
171
|
+
total_loss = acts.sum { |r| r[:elevation_loss_terrain] || r[:elevation_loss] || 0 }
|
|
172
172
|
total_steps = acts.sum { |r| r[:steps] || 0 }
|
|
173
173
|
longest = acts.map { |r| r[:distance] || 0 }.max / 1000.0
|
|
174
174
|
|
data/lib/git_fit/export/csv.rb
CHANGED
|
@@ -12,6 +12,7 @@ module GitFit
|
|
|
12
12
|
average_heartrate max_heartrate average_cadence max_cadence
|
|
13
13
|
average_power max_power calories average_temperature
|
|
14
14
|
average_speed elevation_gain elevation_loss elevation_min elevation_max
|
|
15
|
+
elevation_gain_terrain elevation_loss_terrain elevation_min_terrain elevation_max_terrain
|
|
15
16
|
steps source dedup_group_id
|
|
16
17
|
].freeze
|
|
17
18
|
|
data/lib/git_fit/export/json.rb
CHANGED
|
@@ -79,10 +79,10 @@ module GitFit
|
|
|
79
79
|
calories: a[:calories],
|
|
80
80
|
average_temperature: a[:average_temperature],
|
|
81
81
|
average_speed: a[:average_speed],
|
|
82
|
-
elevation_gain: a[:elevation_gain],
|
|
83
|
-
elevation_loss: a[:elevation_loss],
|
|
84
|
-
elevation_min: a[:elevation_min],
|
|
85
|
-
elevation_max: a[:elevation_max],
|
|
82
|
+
elevation_gain: a[:elevation_gain_terrain] || a[:elevation_gain],
|
|
83
|
+
elevation_loss: a[:elevation_loss_terrain] || a[:elevation_loss],
|
|
84
|
+
elevation_min: a[:elevation_min_terrain] || a[:elevation_min],
|
|
85
|
+
elevation_max: a[:elevation_max_terrain] || a[:elevation_max],
|
|
86
86
|
steps: a[:steps],
|
|
87
87
|
source: a[:source],
|
|
88
88
|
}
|
|
@@ -109,7 +109,9 @@ module GitFit
|
|
|
109
109
|
composite = members.first&.slice(:run_id, :distance, :moving_time, :elapsed_time,
|
|
110
110
|
:average_heartrate, :max_heartrate, :elevation_gain, :elevation_loss,
|
|
111
111
|
:elevation_min, :elevation_max, :steps,
|
|
112
|
-
:average_speed, :average_cadence, :max_cadence
|
|
112
|
+
:average_speed, :average_cadence, :max_cadence,
|
|
113
|
+
:elevation_gain_terrain, :elevation_loss_terrain,
|
|
114
|
+
:elevation_min_terrain, :elevation_max_terrain) || {}
|
|
113
115
|
composite[:summary_polyline] = pick_best_polyline(group_activities)
|
|
114
116
|
composite
|
|
115
117
|
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.21.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lax
|
|
@@ -259,6 +259,7 @@ files:
|
|
|
259
259
|
- db/migrations/001_full_schema.rb
|
|
260
260
|
- db/migrations/002_iso8601_time_format.rb
|
|
261
261
|
- db/migrations/003_add_elevation_fields.rb
|
|
262
|
+
- db/migrations/004_add_elevation_terrain_fields.rb
|
|
262
263
|
- exe/git-fit
|
|
263
264
|
- lib/git-fit.rb
|
|
264
265
|
- lib/git_fit/auth/garmin.rb
|
|
@@ -293,6 +294,7 @@ files:
|
|
|
293
294
|
- lib/git_fit/dedup/service.rb
|
|
294
295
|
- lib/git_fit/dedup/trajectory.rb
|
|
295
296
|
- lib/git_fit/elevation/backfill.rb
|
|
297
|
+
- lib/git_fit/elevation/correct.rb
|
|
296
298
|
- lib/git_fit/elevation/data/dem_coeff.json
|
|
297
299
|
- lib/git_fit/elevation/data/dem_lookup.json
|
|
298
300
|
- lib/git_fit/elevation/dem_cache.rb
|