git-fit 0.17.8 → 0.17.9
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/lib/git_fit/elevation/backfill.rb +64 -9
- data/lib/git_fit/sync/xingzhe.rb +3 -2
- data/lib/git_fit/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 49d811e8b7771ebd21053d0d274c9ac26b96f8d453bdc627623e5571689c5ccf
|
|
4
|
+
data.tar.gz: ececedeedda8e83e8689e9a2f63ba227bed19dc430600a385328586a01728b66
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 29d188b5016c849a7770768de9ccd7c82df9755cbcc93e0f0ad98f9461acd85285dc269d3b42018064e9ed030dc309584c53c72161b7b62605f4d5ae864391f7
|
|
7
|
+
data.tar.gz: bce6161efcb1322a59def53da6636abe4877917b66c4ae420f110d6b7464774537881e64bcade700ae3c47240c1f13c93a07cc4a41660703298d8eee3d583254
|
|
@@ -5,6 +5,8 @@ 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
|
|
8
10
|
|
|
9
11
|
def initialize(db, force: false)
|
|
10
12
|
@db = db
|
|
@@ -129,20 +131,47 @@ module GitFit
|
|
|
129
131
|
end
|
|
130
132
|
|
|
131
133
|
raw = JSON.parse(File.read(raw_path))
|
|
132
|
-
alts = raw.dig('stream', 'altitude') || []
|
|
134
|
+
alts = (raw.dig('stream', 'altitude') || raw.dig('data', 'altitude') || []).map(&:to_f)
|
|
133
135
|
return nil if alts.size < 2
|
|
134
136
|
|
|
137
|
+
# detail.json stores the inner API payload ({workout:...}); legacy wraps it in
|
|
138
|
+
# {code,data:{workout:...},msg}. Prefer xingzhe's own processed elevation.
|
|
139
|
+
detail = xingzhe_detail_workout(natural_id)
|
|
140
|
+
dg = detail && detail['elevation_gain']
|
|
141
|
+
dl = detail && detail['elevation_loss']
|
|
142
|
+
dm = detail && detail['max_altitude']
|
|
143
|
+
|
|
144
|
+
gain = detail_gain?(dg) ? dg : elevation_gain_from_alts(alts, threshold: ELEV_THRESHOLD)
|
|
145
|
+
loss = detail_gain?(dl) ? dl : elevation_loss_from_alts(alts, threshold: ELEV_THRESHOLD)
|
|
146
|
+
|
|
147
|
+
# max: detail max_altitude is reliable (matches stream max in samples); else raw max.
|
|
148
|
+
# min: stream is per-track baro-offset, so only drop low spikes, keep the bulk.
|
|
149
|
+
filtered = self.class.filter_low_outliers(alts)
|
|
135
150
|
{
|
|
136
|
-
elevation_gain:
|
|
137
|
-
elevation_loss:
|
|
138
|
-
elevation_min: alts.min&.round(1),
|
|
139
|
-
elevation_max: alts.max&.round(1),
|
|
151
|
+
elevation_gain: gain&.to_f&.round(1),
|
|
152
|
+
elevation_loss: loss&.to_f&.round(1),
|
|
153
|
+
elevation_min: (filtered.min || alts.min)&.round(1),
|
|
154
|
+
elevation_max: (dm.is_a?(Numeric) && dm > 0 ? dm : alts.max)&.round(1),
|
|
140
155
|
}
|
|
141
156
|
rescue StandardError => e
|
|
142
157
|
warn "Elevation backfill [xingzhe][#{natural_id}]: #{e.message}"
|
|
143
158
|
nil
|
|
144
159
|
end
|
|
145
160
|
|
|
161
|
+
def xingzhe_detail_workout(natural_id)
|
|
162
|
+
path = File.join('data', 'raw', 'xingzhe', natural_id, 'detail.json')
|
|
163
|
+
return nil unless File.exist?(path)
|
|
164
|
+
|
|
165
|
+
d = JSON.parse(File.read(path))
|
|
166
|
+
d['workout'] || d.dig('data', 'workout')
|
|
167
|
+
rescue StandardError
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def detail_gain?(value)
|
|
172
|
+
value.is_a?(Numeric) && !value.zero?
|
|
173
|
+
end
|
|
174
|
+
|
|
146
175
|
def fit_path_for(source, natural_id)
|
|
147
176
|
case source
|
|
148
177
|
when 'garmin', 'garmin_cn'
|
|
@@ -152,19 +181,19 @@ module GitFit
|
|
|
152
181
|
end
|
|
153
182
|
end
|
|
154
183
|
|
|
155
|
-
def elevation_gain_from_alts(alts)
|
|
184
|
+
def elevation_gain_from_alts(alts, threshold: 0.0)
|
|
156
185
|
return 0.0 if alts.size < 2
|
|
157
186
|
(1...alts.size).sum do |i|
|
|
158
187
|
d = alts[i] - alts[i - 1]
|
|
159
|
-
d >
|
|
188
|
+
d > threshold ? d : 0.0
|
|
160
189
|
end
|
|
161
190
|
end
|
|
162
191
|
|
|
163
|
-
def elevation_loss_from_alts(alts)
|
|
192
|
+
def elevation_loss_from_alts(alts, threshold: 0.0)
|
|
164
193
|
return 0.0 if alts.size < 2
|
|
165
194
|
(1...alts.size).sum do |i|
|
|
166
195
|
d = alts[i - 1] - alts[i]
|
|
167
|
-
d >
|
|
196
|
+
d > threshold ? d : 0.0
|
|
168
197
|
end
|
|
169
198
|
end
|
|
170
199
|
|
|
@@ -182,6 +211,32 @@ module GitFit
|
|
|
182
211
|
def skip_row(_run_id)
|
|
183
212
|
@skipped += 1
|
|
184
213
|
end
|
|
214
|
+
|
|
215
|
+
class << self
|
|
216
|
+
# Drop only implausibly-low altitude spikes (baro dropouts), leaving the
|
|
217
|
+
# genuinely shifted bulk intact. Lower fence = median - factor*1.4826*MAD.
|
|
218
|
+
def filter_low_outliers(altitudes, factor = 2.0)
|
|
219
|
+
return altitudes if altitudes.size < 4
|
|
220
|
+
|
|
221
|
+
med = median(altitudes)
|
|
222
|
+
m = mad(altitudes)
|
|
223
|
+
return altitudes if m.zero?
|
|
224
|
+
|
|
225
|
+
fence = med - factor * 1.4826 * m
|
|
226
|
+
altitudes.reject { |x| x < fence }
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def median(values)
|
|
230
|
+
s = values.sort
|
|
231
|
+
n = s.size
|
|
232
|
+
n.odd? ? s[n / 2] : (s[n / 2 - 1] + s[n / 2]) / 2.0
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def mad(values)
|
|
236
|
+
med = median(values)
|
|
237
|
+
median(values.map { |x| (x - med).abs })
|
|
238
|
+
end
|
|
239
|
+
end
|
|
185
240
|
end
|
|
186
241
|
end
|
|
187
242
|
end
|
data/lib/git_fit/sync/xingzhe.rb
CHANGED
|
@@ -352,8 +352,9 @@ module GitFit
|
|
|
352
352
|
end
|
|
353
353
|
|
|
354
354
|
def derive_elevation_min(stream, detail_workout)
|
|
355
|
-
if stream && (alt = stream['altitude']) && alt.is_a?(Array) && alt.
|
|
356
|
-
|
|
355
|
+
if stream && (alt = stream['altitude']) && alt.is_a?(Array) && alt.size >= 2
|
|
356
|
+
alts = alt.compact.map(&:to_f)
|
|
357
|
+
v = GitFit::Elevation::Backfill.filter_low_outliers(alts).min
|
|
357
358
|
return v.to_f if v
|
|
358
359
|
end
|
|
359
360
|
if detail_workout && (segs = detail_workout['segments_km']) && segs.is_a?(Array) && segs.any?
|
data/lib/git_fit/version.rb
CHANGED