git-fit 0.7.9 → 0.8.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/lib/git-fit.rb +44 -42
- data/lib/git_fit/cli/export.rb +8 -6
- data/lib/git_fit/cli/geo_cli.rb +10 -8
- data/lib/git_fit/cli/gh_cli.rb +24 -22
- data/lib/git_fit/cli/import_cli.rb +40 -38
- data/lib/git_fit/cli/install_cli.rb +10 -8
- data/lib/git_fit/cli/sync.rb +9 -7
- data/lib/git_fit/cli.rb +29 -27
- data/lib/git_fit/config.rb +42 -28
- data/lib/git_fit/config_template.rb +2 -0
- data/lib/git_fit/db/activity.rb +2 -0
- data/lib/git_fit/db/cli.rb +11 -9
- data/lib/git_fit/db/connection.rb +8 -6
- data/lib/git_fit/db/rebuild.rb +5 -3
- data/lib/git_fit/export/defaults.rb +14 -12
- data/lib/git_fit/export/json.rb +5 -3
- data/lib/git_fit/export.rb +4 -2
- data/lib/git_fit/fit/decoder.rb +9 -7
- data/lib/git_fit/fit.rb +3 -1
- data/lib/git_fit/geo/amap_client.rb +15 -13
- data/lib/git_fit/geo/coord_transform.rb +2 -0
- data/lib/git_fit/geo/division_detector.rb +31 -26
- data/lib/git_fit/geo/nominatim_client.rb +14 -12
- data/lib/git_fit/geo/polyline.rb +4 -2
- data/lib/git_fit/geo/reverse_geocode.rb +11 -9
- data/lib/git_fit/geo.rb +8 -6
- data/lib/git_fit/import/apple_health.rb +84 -82
- data/lib/git_fit/import/local_file.rb +45 -42
- data/lib/git_fit/import.rb +4 -2
- data/lib/git_fit/install/actions.rb +15 -13
- data/lib/git_fit/parser/base.rb +12 -10
- data/lib/git_fit/parser/fit.rb +11 -9
- data/lib/git_fit/parser/gpx.rb +17 -15
- data/lib/git_fit/parser/tcx.rb +22 -20
- data/lib/git_fit/parser.rb +6 -4
- data/lib/git_fit/privacy/polyline_filter.rb +6 -4
- data/lib/git_fit/source/base.rb +25 -23
- data/lib/git_fit/source/tally.rb +9 -7
- data/lib/git_fit/source.rb +4 -2
- data/lib/git_fit/sport_mapper.rb +23 -21
- data/lib/git_fit/sync/base.rb +29 -27
- data/lib/git_fit/sync/garmin.rb +5 -3
- data/lib/git_fit/sync/garmin_base.rb +145 -143
- data/lib/git_fit/sync/garmin_base_di.rb +42 -40
- data/lib/git_fit/sync/garmin_cn.rb +8 -6
- data/lib/git_fit/sync/igpsport.rb +43 -41
- data/lib/git_fit/sync/keep.rb +98 -96
- data/lib/git_fit/sync/lorem.rb +31 -29
- data/lib/git_fit/sync/runner.rb +14 -11
- data/lib/git_fit/sync/strava.rb +61 -52
- data/lib/git_fit/sync/xingzhe.rb +52 -50
- data/lib/git_fit/sync/xoss.rb +68 -66
- data/lib/git_fit/timezone/resolver.rb +5 -3
- data/lib/git_fit/util/tally.rb +10 -8
- data/lib/git_fit/util.rb +2 -0
- data/lib/git_fit/version.rb +3 -1
- metadata +1 -1
data/lib/git_fit/sync/strava.rb
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require 'faraday'
|
|
3
5
|
|
|
4
6
|
module GitFit
|
|
5
7
|
module Sync
|
|
6
8
|
class SyncError < RuntimeError; end unless const_defined?(:SyncError)
|
|
7
9
|
|
|
8
10
|
class Strava < Base
|
|
9
|
-
BASE_URL =
|
|
11
|
+
BASE_URL = 'https://www.strava.com/api/v3'
|
|
10
12
|
|
|
11
13
|
register_adapter
|
|
12
14
|
register_config :client_id, :client_secret, :refresh_token
|
|
13
15
|
|
|
14
16
|
def authenticate
|
|
15
|
-
return false unless @config[
|
|
16
|
-
@config[
|
|
17
|
-
@config[
|
|
17
|
+
return false unless @config['client_id'].to_s != '' &&
|
|
18
|
+
@config['client_secret'].to_s != '' &&
|
|
19
|
+
@config['refresh_token'].to_s != ''
|
|
18
20
|
@access_token = refresh_access_token
|
|
19
21
|
@access_token ? true : false
|
|
20
22
|
end
|
|
@@ -27,9 +29,9 @@ module GitFit
|
|
|
27
29
|
activities = fetch_activities(@access_token, page)
|
|
28
30
|
break if activities.empty?
|
|
29
31
|
activities.each do |act|
|
|
30
|
-
act_id = act[
|
|
32
|
+
act_id = act['id'].to_s
|
|
31
33
|
next if existing.include?(act_id) && raw_file_exists?(act_id)
|
|
32
|
-
type = map_sport_type(act[
|
|
34
|
+
type = map_sport_type(act['type'], act['sport_type'])
|
|
33
35
|
next if skip_type?(type)
|
|
34
36
|
ids << { id: act_id, type: type, summary: act }
|
|
35
37
|
end
|
|
@@ -46,35 +48,41 @@ module GitFit
|
|
|
46
48
|
private
|
|
47
49
|
|
|
48
50
|
def refresh_access_token
|
|
49
|
-
resp = Faraday.post(
|
|
51
|
+
resp = Faraday.post('https://www.strava.com/oauth/token') do |req|
|
|
50
52
|
req.body = {
|
|
51
|
-
client_id: @config[
|
|
52
|
-
client_secret: @config[
|
|
53
|
-
refresh_token: @config[
|
|
54
|
-
grant_type:
|
|
53
|
+
client_id: @config['client_id'],
|
|
54
|
+
client_secret: @config['client_secret'],
|
|
55
|
+
refresh_token: @config['refresh_token'],
|
|
56
|
+
grant_type: 'refresh_token',
|
|
55
57
|
}
|
|
56
58
|
end
|
|
57
59
|
raise SyncError, "Strava token refresh failed: #{resp.status}" unless resp.success?
|
|
58
|
-
JSON.parse(resp.body)[
|
|
60
|
+
JSON.parse(resp.body)['access_token']
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
def sync_activity(act, act_id, token)
|
|
62
|
-
type = map_sport_type(act[
|
|
64
|
+
type = map_sport_type(act['type'], act['sport_type'])
|
|
63
65
|
return false if skip_type?(type)
|
|
64
66
|
|
|
65
67
|
detail = fetch_detail(act_id, token)
|
|
66
68
|
return false unless detail
|
|
67
69
|
|
|
68
|
-
external_id = detail[
|
|
70
|
+
external_id = detail['external_id']
|
|
69
71
|
|
|
70
72
|
streams = fetch_streams(act_id, token)
|
|
71
73
|
|
|
72
|
-
write_source_archive({ source:
|
|
74
|
+
write_source_archive({ source: 'strava', detail: detail, streams: streams }, act_id)
|
|
73
75
|
|
|
74
76
|
l2 = streams_to_l2(detail, streams)
|
|
75
77
|
write_standardized_json(l2, act_id) if l2
|
|
76
78
|
|
|
77
|
-
polyline =
|
|
79
|
+
polyline = if l2
|
|
80
|
+
Geo::Polyline.encode(l2.map do |pt|
|
|
81
|
+
[pt[:positionLat], pt[:positionLong]]
|
|
82
|
+
end)
|
|
83
|
+
else
|
|
84
|
+
act.dig('map', 'summary_polyline')
|
|
85
|
+
end
|
|
78
86
|
|
|
79
87
|
attrs = build_attrs(act, type, polyline, external_id: external_id)
|
|
80
88
|
upsert_activity(attrs)
|
|
@@ -83,7 +91,7 @@ module GitFit
|
|
|
83
91
|
|
|
84
92
|
def fetch_activities(token, page)
|
|
85
93
|
resp = Faraday.get("#{BASE_URL}/athlete/activities") do |req|
|
|
86
|
-
req.headers[
|
|
94
|
+
req.headers['Authorization'] = "Bearer #{token}"
|
|
87
95
|
req.params = { page: page, per_page: 100 }
|
|
88
96
|
end
|
|
89
97
|
return [] unless resp.success?
|
|
@@ -92,7 +100,7 @@ module GitFit
|
|
|
92
100
|
|
|
93
101
|
def fetch_detail(act_id, token)
|
|
94
102
|
resp = Faraday.get("#{BASE_URL}/activities/#{act_id}") do |req|
|
|
95
|
-
req.headers[
|
|
103
|
+
req.headers['Authorization'] = "Bearer #{token}"
|
|
96
104
|
end
|
|
97
105
|
return nil unless resp.success?
|
|
98
106
|
JSON.parse(resp.body)
|
|
@@ -100,8 +108,9 @@ module GitFit
|
|
|
100
108
|
|
|
101
109
|
def fetch_streams(act_id, token)
|
|
102
110
|
resp = Faraday.get("#{BASE_URL}/activities/#{act_id}/streams") do |req|
|
|
103
|
-
req.headers[
|
|
104
|
-
req.params = { keys:
|
|
111
|
+
req.headers['Authorization'] = "Bearer #{token}"
|
|
112
|
+
req.params = { keys: 'lat_lng,altitude,time,heartrate,cadence,watts,temp,velocity_smooth,distance',
|
|
113
|
+
key_by_type: true }
|
|
105
114
|
end
|
|
106
115
|
return {} unless resp.success?
|
|
107
116
|
JSON.parse(resp.body)
|
|
@@ -117,20 +126,20 @@ module GitFit
|
|
|
117
126
|
end
|
|
118
127
|
|
|
119
128
|
def streams_to_l2(detail, streams)
|
|
120
|
-
polyline = detail.dig(
|
|
129
|
+
polyline = detail.dig('map', 'polyline')
|
|
121
130
|
return nil unless polyline
|
|
122
131
|
|
|
123
132
|
coords = Geo::Polyline.decode(polyline)
|
|
124
133
|
return nil if coords.empty?
|
|
125
134
|
|
|
126
|
-
alt_data = streams[
|
|
127
|
-
time_data = streams[
|
|
128
|
-
hr_data = streams[
|
|
129
|
-
cad_data = streams[
|
|
130
|
-
watts_data = streams[
|
|
131
|
-
temp_data = streams[
|
|
132
|
-
speed_data = streams[
|
|
133
|
-
dist_data = streams[
|
|
135
|
+
alt_data = streams['altitude']&.dig('data')
|
|
136
|
+
time_data = streams['time']&.dig('data')
|
|
137
|
+
hr_data = streams['heartrate']&.dig('data')
|
|
138
|
+
cad_data = streams['cadence']&.dig('data')
|
|
139
|
+
watts_data = streams['watts']&.dig('data')
|
|
140
|
+
temp_data = streams['temp']&.dig('data')
|
|
141
|
+
speed_data = streams['velocity_smooth']&.dig('data')
|
|
142
|
+
dist_data = streams['distance']&.dig('data')
|
|
134
143
|
|
|
135
144
|
coords.each_with_index.map do |latlng, i|
|
|
136
145
|
pt = { positionLat: latlng[0], positionLong: latlng[1] }
|
|
@@ -147,33 +156,33 @@ module GitFit
|
|
|
147
156
|
end
|
|
148
157
|
|
|
149
158
|
def build_attrs(act, category, polyline = nil, external_id: nil)
|
|
150
|
-
start_t = Time.parse(act[
|
|
151
|
-
local_t = Time.parse(act[
|
|
159
|
+
start_t = Time.parse(act['start_date']) rescue nil
|
|
160
|
+
local_t = Time.parse(act['start_date_local']) rescue nil
|
|
152
161
|
|
|
153
162
|
{
|
|
154
163
|
run_id: "strava_#{act["id"]}",
|
|
155
|
-
name: act[
|
|
156
|
-
distance: act[
|
|
157
|
-
moving_time: act[
|
|
158
|
-
elapsed_time: act[
|
|
164
|
+
name: act['name'],
|
|
165
|
+
distance: act['distance'],
|
|
166
|
+
moving_time: act['moving_time'],
|
|
167
|
+
elapsed_time: act['elapsed_time'],
|
|
159
168
|
sport_category: category,
|
|
160
|
-
sport_type: (act[
|
|
169
|
+
sport_type: (act['sport_type'] || act['type'])&.downcase,
|
|
161
170
|
start_date: start_t&.iso8601,
|
|
162
171
|
start_date_local: local_t&.iso8601,
|
|
163
|
-
location_country: act[
|
|
172
|
+
location_country: act['location_country'],
|
|
164
173
|
summary_polyline: polyline,
|
|
165
|
-
average_heartrate: act[
|
|
166
|
-
max_heartrate: act[
|
|
167
|
-
average_cadence: act[
|
|
174
|
+
average_heartrate: act['average_heartrate'],
|
|
175
|
+
max_heartrate: act['max_heartrate'],
|
|
176
|
+
average_cadence: act['average_cadence'],
|
|
168
177
|
max_cadence: nil,
|
|
169
|
-
average_power: act[
|
|
170
|
-
max_power: act[
|
|
171
|
-
calories: act[
|
|
172
|
-
average_temperature: act[
|
|
173
|
-
average_speed: act[
|
|
174
|
-
elevation_gain: act[
|
|
175
|
-
source:
|
|
176
|
-
external_id: external_id
|
|
178
|
+
average_power: act['average_watts'],
|
|
179
|
+
max_power: act['max_watts'],
|
|
180
|
+
calories: act['calories'],
|
|
181
|
+
average_temperature: act['average_temp'],
|
|
182
|
+
average_speed: act['average_speed'],
|
|
183
|
+
elevation_gain: act['total_elevation_gain'],
|
|
184
|
+
source: 'strava',
|
|
185
|
+
external_id: external_id,
|
|
177
186
|
}
|
|
178
187
|
end
|
|
179
188
|
|
|
@@ -186,8 +195,8 @@ module GitFit
|
|
|
186
195
|
end
|
|
187
196
|
|
|
188
197
|
def existing_run_ids
|
|
189
|
-
@db[:activities].where(source:
|
|
190
|
-
.map { |id| id.sub(
|
|
198
|
+
@db[:activities].where(source: 'strava').select_map(:run_id)
|
|
199
|
+
.map { |id| id.sub('strava_', '') }
|
|
191
200
|
end
|
|
192
201
|
end
|
|
193
202
|
end
|
data/lib/git_fit/sync/xingzhe.rb
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'openssl'
|
|
6
|
+
require 'tzinfo'
|
|
5
7
|
|
|
6
8
|
module GitFit
|
|
7
9
|
module Sync
|
|
8
10
|
class XingZhe < Base
|
|
9
|
-
BASE_HOST =
|
|
10
|
-
LOGIN_PATH =
|
|
11
|
-
WORKOUTS_PATH =
|
|
12
|
-
STREAM_PATH =
|
|
13
|
-
DETAIL_PATH =
|
|
14
|
-
RSA_PUBKEY =
|
|
11
|
+
BASE_HOST = 'www.imxingzhe.com'
|
|
12
|
+
LOGIN_PATH = '/api/v1/user/login/'
|
|
13
|
+
WORKOUTS_PATH = '/api/v1/pgworkout/'
|
|
14
|
+
STREAM_PATH = '/api/v1/pgworkout/'
|
|
15
|
+
DETAIL_PATH = '/api/v1/pgworkout/'
|
|
16
|
+
RSA_PUBKEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDmuQkBbijudDAJgfffDeeIButqWHZvUwcRuvWdg89393FSdz3IJUHc0rgI/S3WuU8N0VePJLmVAZtCOK4qe4FY/eKmWpJmn7JfXB4HTMWjPVoyRZmSYjW4L8GrWmh51Qj7DwpTADadF3aq04o+s1b8LXJa8r6+TIqqL5WUHtRqmQIDAQAB'
|
|
15
17
|
|
|
16
18
|
SPORT_MAP = {
|
|
17
|
-
3 =>
|
|
18
|
-
5 =>
|
|
19
|
+
3 => 'ride', 1 => 'run', 2 => 'hike',
|
|
20
|
+
5 => 'swim', 6 => 'walk', 11 => 'ski', 13 => 'workout'
|
|
19
21
|
}.freeze
|
|
20
22
|
|
|
21
|
-
RAW_EXT =
|
|
23
|
+
RAW_EXT = 'json'
|
|
22
24
|
|
|
23
25
|
register_adapter
|
|
24
|
-
config_key
|
|
26
|
+
config_key 'xingzhe'
|
|
25
27
|
register_config :email, :password
|
|
26
28
|
|
|
27
29
|
def initialize(...)
|
|
28
30
|
super
|
|
29
|
-
@email = @config[
|
|
30
|
-
@password = @config[
|
|
31
|
+
@email = @config['email']
|
|
32
|
+
@password = @config['password']
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def before_call
|
|
@@ -48,9 +50,9 @@ module GitFit
|
|
|
48
50
|
acts = fetch_page(offset, limit)
|
|
49
51
|
break if acts.nil? || acts.empty?
|
|
50
52
|
acts.each do |act|
|
|
51
|
-
aid = act[
|
|
53
|
+
aid = act['id'].to_s
|
|
52
54
|
next if existing.include?(aid) && raw_file_exists?(aid)
|
|
53
|
-
type = map_sport(act[
|
|
55
|
+
type = map_sport(act['sport'])
|
|
54
56
|
next if skip_type?(type)
|
|
55
57
|
ids << { id: aid, type: type, summary: act }
|
|
56
58
|
end
|
|
@@ -88,17 +90,17 @@ module GitFit
|
|
|
88
90
|
encrypted = rsa_encrypt(@password)
|
|
89
91
|
resp = http_request(:post, LOGIN_PATH,
|
|
90
92
|
body: JSON.generate({ account: @email, password: encrypted }),
|
|
91
|
-
headers: {
|
|
93
|
+
headers: { 'Content-Type' => 'application/json' }
|
|
92
94
|
)
|
|
93
95
|
return false unless resp.code.to_i == 200
|
|
94
96
|
|
|
95
97
|
data = JSON.parse(resp.body)
|
|
96
|
-
return false unless data[
|
|
98
|
+
return false unless data['code'] == 0
|
|
97
99
|
|
|
98
100
|
cookie = extract_cookie(resp)
|
|
99
101
|
return false unless cookie
|
|
100
102
|
|
|
101
|
-
@headers = {
|
|
103
|
+
@headers = { 'Cookie' => cookie, 'Accept' => 'application/json' }
|
|
102
104
|
true
|
|
103
105
|
end
|
|
104
106
|
|
|
@@ -109,7 +111,7 @@ module GitFit
|
|
|
109
111
|
end
|
|
110
112
|
|
|
111
113
|
def extract_cookie(resp)
|
|
112
|
-
set_cookie = resp[
|
|
114
|
+
set_cookie = resp['set-cookie']
|
|
113
115
|
return nil unless set_cookie
|
|
114
116
|
m = set_cookie.match(/sessionid=([^;]+)/)
|
|
115
117
|
m ? "sessionid=#{m[1]}" : nil
|
|
@@ -126,7 +128,7 @@ module GitFit
|
|
|
126
128
|
)
|
|
127
129
|
return nil unless resp.code.to_i == 200
|
|
128
130
|
|
|
129
|
-
JSON.parse(resp.body).dig(
|
|
131
|
+
JSON.parse(resp.body).dig('data', 'data')
|
|
130
132
|
end
|
|
131
133
|
|
|
132
134
|
def tz_resolver
|
|
@@ -142,48 +144,48 @@ module GitFit
|
|
|
142
144
|
tz_string = tz_resolver.resolve(lat, lng)
|
|
143
145
|
tz = TZInfo::Timezone.get(tz_string)
|
|
144
146
|
tz.utc_to_local(start_utc).iso8601
|
|
145
|
-
rescue => e
|
|
147
|
+
rescue StandardError => e
|
|
146
148
|
warn "XingZhe: timezone resolution failed: #{e.message}"
|
|
147
149
|
start_utc.iso8601
|
|
148
150
|
end
|
|
149
151
|
|
|
150
152
|
def process_activity(act, type)
|
|
151
|
-
aid = act[
|
|
152
|
-
start_t = Time.at(act[
|
|
153
|
+
aid = act['id'].to_s
|
|
154
|
+
start_t = Time.at(act['start_time'] / 1000.0).utc rescue nil
|
|
153
155
|
return false unless start_t
|
|
154
156
|
|
|
155
157
|
attrs = {
|
|
156
158
|
run_id: "xingzhe_#{aid}",
|
|
157
|
-
name: act[
|
|
158
|
-
distance: act[
|
|
159
|
-
moving_time: (t = act[
|
|
159
|
+
name: act['title'] || 'XingZhe Activity',
|
|
160
|
+
distance: act['distance'].to_f,
|
|
161
|
+
moving_time: (t = act['duration'].to_i) > 0 ? t : nil,
|
|
160
162
|
elapsed_time: t > 0 ? t : nil,
|
|
161
163
|
sport_category: type,
|
|
162
|
-
sport_type: act[
|
|
164
|
+
sport_type: act['sport'].to_s,
|
|
163
165
|
start_date: start_t.iso8601,
|
|
164
166
|
start_date_local: start_t.iso8601,
|
|
165
|
-
average_speed: (act[
|
|
166
|
-
elevation_gain: act[
|
|
167
|
-
source:
|
|
167
|
+
average_speed: (act['avg_speed'].to_f / 3.6).round(2).nonzero?,
|
|
168
|
+
elevation_gain: act['elevation_gain'].to_f.nonzero?,
|
|
169
|
+
source: 'xingzhe',
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
pts = nil
|
|
171
173
|
stream = fetch_stream(aid)
|
|
172
174
|
if stream
|
|
173
175
|
write_source_archive({
|
|
174
|
-
source:
|
|
175
|
-
fetchedAt: Time.now.utc.strftime(
|
|
176
|
-
stream: stream
|
|
176
|
+
source: 'xingzhe',
|
|
177
|
+
fetchedAt: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
178
|
+
stream: stream,
|
|
177
179
|
}, aid)
|
|
178
|
-
if (locs = stream[
|
|
180
|
+
if (locs = stream['location']) && locs.size >= 2
|
|
179
181
|
pts = locs.map { |lng, lat| [lat, lng] }
|
|
180
182
|
write_std(pts, stream, aid)
|
|
181
183
|
attrs[:summary_polyline] = stream_to_polyline(aid)
|
|
182
184
|
end
|
|
183
185
|
end
|
|
184
186
|
|
|
185
|
-
if attrs[:summary_polyline].nil? && (detail = fetch_activity_detail(aid)) && (segments = detail[
|
|
186
|
-
pts = segments.map { |s| [s[
|
|
187
|
+
if attrs[:summary_polyline].nil? && (detail = fetch_activity_detail(aid)) && (segments = detail['segments_km'])
|
|
188
|
+
pts = segments.map { |s| [s['latitude'], s['longitude']] }
|
|
187
189
|
attrs[:summary_polyline] = Geo::Polyline.encode(pts) if pts.size >= 2
|
|
188
190
|
end
|
|
189
191
|
|
|
@@ -201,7 +203,7 @@ module GitFit
|
|
|
201
203
|
resp = http_request(:get, "#{STREAM_PATH}#{id}/stream/", headers: @headers || {})
|
|
202
204
|
return nil unless resp.code.to_i == 200
|
|
203
205
|
|
|
204
|
-
JSON.parse(resp.body).dig(
|
|
206
|
+
JSON.parse(resp.body).dig('data')
|
|
205
207
|
rescue JSON::ParserError
|
|
206
208
|
nil
|
|
207
209
|
end
|
|
@@ -209,17 +211,17 @@ module GitFit
|
|
|
209
211
|
def write_std(pts, stream, id)
|
|
210
212
|
dir = std_dir
|
|
211
213
|
FileUtils.mkdir_p(dir)
|
|
212
|
-
ts_arr = stream[
|
|
213
|
-
alt_arr = stream[
|
|
214
|
-
spd_arr = stream[
|
|
215
|
-
dst_arr = stream[
|
|
214
|
+
ts_arr = stream['timestamp'] || []
|
|
215
|
+
alt_arr = stream['altitude'] || []
|
|
216
|
+
spd_arr = stream['speed'] || []
|
|
217
|
+
dst_arr = stream['distance'] || []
|
|
216
218
|
|
|
217
219
|
extended = pts.each_with_index.map do |(lat, lng), i|
|
|
218
220
|
ts_val = ts_arr[i]
|
|
219
221
|
{
|
|
220
222
|
latitude: lat, longitude: lng,
|
|
221
223
|
altitude: alt_arr[i],
|
|
222
|
-
timestamp: ts_val ? Time.at(ts_val).utc.strftime(
|
|
224
|
+
timestamp: ts_val ? Time.at(ts_val).utc.strftime('%Y-%m-%dT%H:%M:%SZ') : nil,
|
|
223
225
|
speed: spd_arr[i],
|
|
224
226
|
distance: dst_arr[i]
|
|
225
227
|
}
|
|
@@ -233,7 +235,7 @@ module GitFit
|
|
|
233
235
|
|
|
234
236
|
def stream_to_polyline(id)
|
|
235
237
|
raw = JSON.parse(File.read(raw_path(id)))
|
|
236
|
-
locs = raw.dig(
|
|
238
|
+
locs = raw.dig('stream', 'location') || []
|
|
237
239
|
return nil if locs.size < 2
|
|
238
240
|
|
|
239
241
|
pts = locs.map { |lng, lat| [lat, lng] }
|
|
@@ -247,16 +249,16 @@ module GitFit
|
|
|
247
249
|
return nil unless resp.code.to_i == 200
|
|
248
250
|
|
|
249
251
|
data = JSON.parse(resp.body)
|
|
250
|
-
data.dig(
|
|
252
|
+
data.dig('data', 'workout')
|
|
251
253
|
end
|
|
252
254
|
|
|
253
255
|
def map_sport(sport)
|
|
254
|
-
SPORT_MAP[sport] ||
|
|
256
|
+
SPORT_MAP[sport] || 'other'
|
|
255
257
|
end
|
|
256
258
|
|
|
257
259
|
def existing_run_ids
|
|
258
|
-
@db[:activities].where(source:
|
|
259
|
-
.map { |id| id.sub(
|
|
260
|
+
@db[:activities].where(source: 'xingzhe').select_map(:run_id)
|
|
261
|
+
.map { |id| id.sub('xingzhe_', '') }
|
|
260
262
|
end
|
|
261
263
|
end
|
|
262
264
|
end
|