git-fit 0.10.4 → 0.10.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.
@@ -7,10 +7,11 @@ module GitFit
7
7
  class JSON
8
8
  PLACEHOLDER_POLYLINE = 'gqqrFurkeU??'
9
9
 
10
- def initialize(db:, output:, activity_filter: nil)
10
+ def initialize(db:, output:, activity_filter: nil, tracks_output: nil)
11
11
  @db = db
12
12
  @output = output
13
13
  @activity_filter = activity_filter
14
+ @tracks_output = tracks_output
14
15
  end
15
16
 
16
17
  def call
@@ -24,23 +25,40 @@ module GitFit
24
25
 
25
26
  group_members_cache = {}
26
27
 
28
+ tracks = @tracks_output ? {} : nil
29
+
27
30
  formatted = activities.map do |a|
28
31
  gid = run_id_to_group[a[:run_id]]
29
32
  if gid
30
33
  group_members_cache[gid] ||= @db[:dedup_group_members].where(group_id: gid).all
31
- composite_activity(a, gid, group_members_cache[gid])
34
+ composite_activity(a, gid, group_members_cache[gid], tracks)
32
35
  else
33
- format_activity(a)
36
+ format_activity(a, tracks)
34
37
  end
35
38
  end
36
39
 
37
40
  File.write(@output, ::JSON.pretty_generate(formatted))
41
+ File.write(@tracks_output, ::JSON.generate(tracks)) if @tracks_output
38
42
  formatted.size
39
43
  end
40
44
 
41
45
  private
42
46
 
43
- def format_activity(a)
47
+ def extract_polyline(a)
48
+ return nil if a[:source] == 'keep' && a[:summary_polyline] == PLACEHOLDER_POLYLINE
49
+
50
+ a[:summary_polyline]
51
+ end
52
+
53
+ def pick_best_polyline(group_activities)
54
+ candidates = group_activities.filter_map { |a| extract_polyline(a) }
55
+ candidates.max_by { |p| p.to_s.length }
56
+ end
57
+
58
+ def format_activity(a, tracks = nil)
59
+ polyline = extract_polyline(a)
60
+ tracks[a[:run_id]] = polyline if tracks && polyline
61
+
44
62
  base = {
45
63
  run_id: a[:run_id],
46
64
  name: a[:name],
@@ -51,8 +69,7 @@ module GitFit
51
69
  start_date: a[:start_date],
52
70
  start_date_local: a[:start_date_local],
53
71
  location_country: a[:location_country],
54
- summary_polyline: a[:source] == 'keep' && a[:summary_polyline] == PLACEHOLDER_POLYLINE ?
55
- nil : a[:summary_polyline],
72
+ summary_polyline: tracks ? nil : polyline,
56
73
  average_heartrate: a[:average_heartrate] && a[:average_heartrate] != 0 ? a[:average_heartrate] : nil,
57
74
  max_heartrate: a[:max_heartrate],
58
75
  average_cadence: a[:average_cadence],
@@ -71,7 +88,7 @@ module GitFit
71
88
  base
72
89
  end
73
90
 
74
- def composite_activity(activity, group_id, members)
91
+ def composite_activity(activity, group_id, members, tracks = nil)
75
92
  run_ids = members.map { |m| m[:run_id] }
76
93
  group_activities = @db[:activities].where(run_id: run_ids).all
77
94
 
@@ -85,11 +102,17 @@ module GitFit
85
102
  end
86
103
  result
87
104
  else
88
- members.first&.slice(:run_id, :distance, :moving_time, :elapsed_time,
105
+ composite = members.first&.slice(:run_id, :distance, :moving_time, :elapsed_time,
89
106
  :average_heartrate, :max_heartrate, :elevation_gain,
90
107
  :average_speed, :average_cadence, :max_cadence) || {}
108
+ composite[:summary_polyline] = pick_best_polyline(group_activities)
109
+ composite
91
110
  end
92
111
 
112
+ polyline = composite[:summary_polyline]
113
+ tracks[activity[:run_id]] = polyline if tracks && polyline
114
+ composite[:summary_polyline] = tracks ? nil : polyline
115
+
93
116
  composite[:run_id] = activity[:run_id]
94
117
  composite[:source] = activity[:source]
95
118
  composite[:sport_type] = activity[:sport_type]
@@ -8,5 +8,6 @@ end
8
8
  require_relative 'export/defaults'
9
9
  require_relative 'export/json'
10
10
  require_relative 'export/csv'
11
+ require_relative 'export/gpx'
11
12
  require_relative 'export/calculator'
12
13
  require_relative 'export/stats'
data/lib/git_fit/fit.rb CHANGED
@@ -1,3 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module GitFit
4
+ module FIT
5
+ FIT_EPOCH = 631_065_600
6
+ end
7
+ end
8
+
3
9
  require_relative 'fit/decoder'
@@ -235,6 +235,17 @@ module GitFit
235
235
  .select_map(:run_id)
236
236
  .map { |rid| rid.sub(/\A#{Regexp.escape(prefix)}/, '') }
237
237
  end
238
+
239
+ def normalize_fit_timestamps(points)
240
+ points.map do |pt|
241
+ ts = pt['timestamp']
242
+ next pt unless ts.is_a?(Numeric)
243
+
244
+ pt = pt.dup
245
+ pt['timestamp'] = Time.at(FIT::FIT_EPOCH + ts).utc.iso8601
246
+ pt
247
+ end
248
+ end
238
249
  end
239
250
  end
240
251
  end
@@ -140,7 +140,7 @@ module GitFit
140
140
  FileUtils.mkdir_p(dir)
141
141
  tmp = File.join(dir, ".#{ride_id}.json.tmp")
142
142
  final = File.join(dir, "#{ride_id}.json")
143
- File.write(tmp, JSON.generate(wgs_points))
143
+ File.write(tmp, JSON.generate(normalize_fit_timestamps(wgs_points)))
144
144
  File.rename(tmp, final)
145
145
  end
146
146
 
@@ -134,27 +134,40 @@ module GitFit
134
134
  coords = Geo::Polyline.decode(polyline)
135
135
  return nil if coords.empty?
136
136
 
137
- alt_data = streams['altitude']&.dig('data')
138
- time_data = streams['time']&.dig('data')
139
- hr_data = streams['heartrate']&.dig('data')
140
- cad_data = streams['cadence']&.dig('data')
141
- watts_data = streams['watts']&.dig('data')
142
- temp_data = streams['temp']&.dig('data')
143
- speed_data = streams['velocity_smooth']&.dig('data')
144
- dist_data = streams['distance']&.dig('data')
137
+ streams_data = %w[altitude time heartrate cadence watts temp velocity_smooth distance].to_h do |k|
138
+ [k, streams[k]&.dig('data')]
139
+ end
140
+
141
+ start_time = begin
142
+ Time.parse(detail['start_date'])
143
+ rescue StandardError
144
+ nil
145
+ end
145
146
 
146
147
  coords.each_with_index.map do |latlng, i|
147
- pt = { positionLat: latlng[0], positionLong: latlng[1] }
148
- pt[:altitude] = alt_data[i] if alt_data && i < alt_data.size
149
- pt[:timestamp] = time_data[i] if time_data && i < time_data.size
150
- pt[:heartRate] = hr_data[i] if hr_data && i < hr_data.size
151
- pt[:cadence] = cad_data[i] if cad_data && i < cad_data.size
152
- pt[:power] = watts_data[i] if watts_data && i < watts_data.size
153
- pt[:temperature] = temp_data[i] if temp_data && i < temp_data.size
154
- pt[:speed] = speed_data[i] if speed_data && i < speed_data.size
155
- pt[:distance] = dist_data[i] if dist_data && i < dist_data.size
156
- pt
148
+ build_l2_point(latlng, i, streams_data, start_time)
149
+ end
150
+ end
151
+
152
+ def build_l2_point(latlng, i, streams_data, start_time)
153
+ pt = { positionLat: latlng[0], positionLong: latlng[1] }
154
+ pt[:altitude] = stream_at(streams_data, 'altitude', i)
155
+ ts = stream_at(streams_data, 'time', i)
156
+ if ts
157
+ pt[:timestamp] = start_time ? (start_time + ts).utc.iso8601 : ts
157
158
  end
159
+ pt[:heartRate] = stream_at(streams_data, 'heartrate', i)
160
+ pt[:cadence] = stream_at(streams_data, 'cadence', i)
161
+ pt[:power] = stream_at(streams_data, 'watts', i)
162
+ pt[:temperature] = stream_at(streams_data, 'temp', i)
163
+ pt[:speed] = stream_at(streams_data, 'velocity_smooth', i)
164
+ pt[:distance] = stream_at(streams_data, 'distance', i)
165
+ pt
166
+ end
167
+
168
+ def stream_at(streams_data, key, i)
169
+ data = streams_data[key]
170
+ data && i < data.size ? data[i] : nil
158
171
  end
159
172
 
160
173
  def build_attrs(act, category, polyline = nil, external_id: nil)
@@ -348,7 +348,7 @@ module GitFit
348
348
  FileUtils.mkdir_p(dir)
349
349
  tmp = File.join(dir, ".#{act_id}.json.tmp")
350
350
  final = File.join(dir, "#{act_id}.json")
351
- File.write(tmp, JSON.generate(points))
351
+ File.write(tmp, JSON.generate(normalize_fit_timestamps(points)))
352
352
  File.rename(tmp, final)
353
353
  end
354
354
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitFit
4
- VERSION = '0.10.4'
4
+ VERSION = '0.10.5'
5
5
  end
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.10.4
4
+ version: 0.10.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lax
@@ -177,6 +177,20 @@ dependencies:
177
177
  - - "~>"
178
178
  - !ruby/object:Gem::Version
179
179
  version: '1.0'
180
+ - !ruby/object:Gem::Dependency
181
+ name: webrick
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: '1.9'
187
+ type: :runtime
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - "~>"
192
+ - !ruby/object:Gem::Version
193
+ version: '1.9'
180
194
  - !ruby/object:Gem::Dependency
181
195
  name: ostruct
182
196
  requirement: !ruby/object:Gem::Requirement
@@ -218,6 +232,8 @@ files:
218
232
  - db/migrations/002_iso8601_time_format.rb
219
233
  - exe/git-fit
220
234
  - lib/git-fit.rb
235
+ - lib/git_fit/auth/garmin_token.rb
236
+ - lib/git_fit/auth/strava.rb
221
237
  - lib/git_fit/cli.rb
222
238
  - lib/git_fit/cli/export.rb
223
239
  - lib/git_fit/cli/geo_cli.rb
@@ -232,10 +248,17 @@ files:
232
248
  - lib/git_fit/db/cli.rb
233
249
  - lib/git_fit/db/connection.rb
234
250
  - lib/git_fit/db/rebuild.rb
251
+ - lib/git_fit/dedup/field_elector.rb
252
+ - lib/git_fit/dedup/group_consolidator.rb
253
+ - lib/git_fit/dedup/log_writer.rb
254
+ - lib/git_fit/dedup/matcher.rb
255
+ - lib/git_fit/dedup/service.rb
256
+ - lib/git_fit/dedup/trajectory.rb
235
257
  - lib/git_fit/export.rb
236
258
  - lib/git_fit/export/calculator.rb
237
259
  - lib/git_fit/export/csv.rb
238
260
  - lib/git_fit/export/defaults.rb
261
+ - lib/git_fit/export/gpx.rb
239
262
  - lib/git_fit/export/json.rb
240
263
  - lib/git_fit/export/stats.rb
241
264
  - lib/git_fit/fit.rb