git-fit 0.7.8 → 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 +33 -29
- 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 -7
- 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 +15 -12
- data/lib/git_fit/sync/strava.rb +61 -52
- data/lib/git_fit/sync/xingzhe.rb +52 -49
- 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,32 +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
|
|
26
|
+
config_key 'xingzhe'
|
|
24
27
|
register_config :email, :password
|
|
25
28
|
|
|
26
29
|
def initialize(...)
|
|
27
30
|
super
|
|
28
|
-
@email = @config[
|
|
29
|
-
@password = @config[
|
|
31
|
+
@email = @config['email']
|
|
32
|
+
@password = @config['password']
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
def before_call
|
|
@@ -47,9 +50,9 @@ module GitFit
|
|
|
47
50
|
acts = fetch_page(offset, limit)
|
|
48
51
|
break if acts.nil? || acts.empty?
|
|
49
52
|
acts.each do |act|
|
|
50
|
-
aid = act[
|
|
53
|
+
aid = act['id'].to_s
|
|
51
54
|
next if existing.include?(aid) && raw_file_exists?(aid)
|
|
52
|
-
type = map_sport(act[
|
|
55
|
+
type = map_sport(act['sport'])
|
|
53
56
|
next if skip_type?(type)
|
|
54
57
|
ids << { id: aid, type: type, summary: act }
|
|
55
58
|
end
|
|
@@ -87,17 +90,17 @@ module GitFit
|
|
|
87
90
|
encrypted = rsa_encrypt(@password)
|
|
88
91
|
resp = http_request(:post, LOGIN_PATH,
|
|
89
92
|
body: JSON.generate({ account: @email, password: encrypted }),
|
|
90
|
-
headers: {
|
|
93
|
+
headers: { 'Content-Type' => 'application/json' }
|
|
91
94
|
)
|
|
92
95
|
return false unless resp.code.to_i == 200
|
|
93
96
|
|
|
94
97
|
data = JSON.parse(resp.body)
|
|
95
|
-
return false unless data[
|
|
98
|
+
return false unless data['code'] == 0
|
|
96
99
|
|
|
97
100
|
cookie = extract_cookie(resp)
|
|
98
101
|
return false unless cookie
|
|
99
102
|
|
|
100
|
-
@headers = {
|
|
103
|
+
@headers = { 'Cookie' => cookie, 'Accept' => 'application/json' }
|
|
101
104
|
true
|
|
102
105
|
end
|
|
103
106
|
|
|
@@ -108,7 +111,7 @@ module GitFit
|
|
|
108
111
|
end
|
|
109
112
|
|
|
110
113
|
def extract_cookie(resp)
|
|
111
|
-
set_cookie = resp[
|
|
114
|
+
set_cookie = resp['set-cookie']
|
|
112
115
|
return nil unless set_cookie
|
|
113
116
|
m = set_cookie.match(/sessionid=([^;]+)/)
|
|
114
117
|
m ? "sessionid=#{m[1]}" : nil
|
|
@@ -125,7 +128,7 @@ module GitFit
|
|
|
125
128
|
)
|
|
126
129
|
return nil unless resp.code.to_i == 200
|
|
127
130
|
|
|
128
|
-
JSON.parse(resp.body).dig(
|
|
131
|
+
JSON.parse(resp.body).dig('data', 'data')
|
|
129
132
|
end
|
|
130
133
|
|
|
131
134
|
def tz_resolver
|
|
@@ -141,48 +144,48 @@ module GitFit
|
|
|
141
144
|
tz_string = tz_resolver.resolve(lat, lng)
|
|
142
145
|
tz = TZInfo::Timezone.get(tz_string)
|
|
143
146
|
tz.utc_to_local(start_utc).iso8601
|
|
144
|
-
rescue => e
|
|
147
|
+
rescue StandardError => e
|
|
145
148
|
warn "XingZhe: timezone resolution failed: #{e.message}"
|
|
146
149
|
start_utc.iso8601
|
|
147
150
|
end
|
|
148
151
|
|
|
149
152
|
def process_activity(act, type)
|
|
150
|
-
aid = act[
|
|
151
|
-
start_t = Time.at(act[
|
|
153
|
+
aid = act['id'].to_s
|
|
154
|
+
start_t = Time.at(act['start_time'] / 1000.0).utc rescue nil
|
|
152
155
|
return false unless start_t
|
|
153
156
|
|
|
154
157
|
attrs = {
|
|
155
158
|
run_id: "xingzhe_#{aid}",
|
|
156
|
-
name: act[
|
|
157
|
-
distance: act[
|
|
158
|
-
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,
|
|
159
162
|
elapsed_time: t > 0 ? t : nil,
|
|
160
163
|
sport_category: type,
|
|
161
|
-
sport_type: act[
|
|
164
|
+
sport_type: act['sport'].to_s,
|
|
162
165
|
start_date: start_t.iso8601,
|
|
163
166
|
start_date_local: start_t.iso8601,
|
|
164
|
-
average_speed: (act[
|
|
165
|
-
elevation_gain: act[
|
|
166
|
-
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',
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
pts = nil
|
|
170
173
|
stream = fetch_stream(aid)
|
|
171
174
|
if stream
|
|
172
175
|
write_source_archive({
|
|
173
|
-
source:
|
|
174
|
-
fetchedAt: Time.now.utc.strftime(
|
|
175
|
-
stream: stream
|
|
176
|
+
source: 'xingzhe',
|
|
177
|
+
fetchedAt: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
178
|
+
stream: stream,
|
|
176
179
|
}, aid)
|
|
177
|
-
if (locs = stream[
|
|
180
|
+
if (locs = stream['location']) && locs.size >= 2
|
|
178
181
|
pts = locs.map { |lng, lat| [lat, lng] }
|
|
179
182
|
write_std(pts, stream, aid)
|
|
180
183
|
attrs[:summary_polyline] = stream_to_polyline(aid)
|
|
181
184
|
end
|
|
182
185
|
end
|
|
183
186
|
|
|
184
|
-
if attrs[:summary_polyline].nil? && (detail = fetch_activity_detail(aid)) && (segments = detail[
|
|
185
|
-
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']] }
|
|
186
189
|
attrs[:summary_polyline] = Geo::Polyline.encode(pts) if pts.size >= 2
|
|
187
190
|
end
|
|
188
191
|
|
|
@@ -200,7 +203,7 @@ module GitFit
|
|
|
200
203
|
resp = http_request(:get, "#{STREAM_PATH}#{id}/stream/", headers: @headers || {})
|
|
201
204
|
return nil unless resp.code.to_i == 200
|
|
202
205
|
|
|
203
|
-
JSON.parse(resp.body).dig(
|
|
206
|
+
JSON.parse(resp.body).dig('data')
|
|
204
207
|
rescue JSON::ParserError
|
|
205
208
|
nil
|
|
206
209
|
end
|
|
@@ -208,17 +211,17 @@ module GitFit
|
|
|
208
211
|
def write_std(pts, stream, id)
|
|
209
212
|
dir = std_dir
|
|
210
213
|
FileUtils.mkdir_p(dir)
|
|
211
|
-
ts_arr = stream[
|
|
212
|
-
alt_arr = stream[
|
|
213
|
-
spd_arr = stream[
|
|
214
|
-
dst_arr = stream[
|
|
214
|
+
ts_arr = stream['timestamp'] || []
|
|
215
|
+
alt_arr = stream['altitude'] || []
|
|
216
|
+
spd_arr = stream['speed'] || []
|
|
217
|
+
dst_arr = stream['distance'] || []
|
|
215
218
|
|
|
216
219
|
extended = pts.each_with_index.map do |(lat, lng), i|
|
|
217
220
|
ts_val = ts_arr[i]
|
|
218
221
|
{
|
|
219
222
|
latitude: lat, longitude: lng,
|
|
220
223
|
altitude: alt_arr[i],
|
|
221
|
-
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,
|
|
222
225
|
speed: spd_arr[i],
|
|
223
226
|
distance: dst_arr[i]
|
|
224
227
|
}
|
|
@@ -232,7 +235,7 @@ module GitFit
|
|
|
232
235
|
|
|
233
236
|
def stream_to_polyline(id)
|
|
234
237
|
raw = JSON.parse(File.read(raw_path(id)))
|
|
235
|
-
locs = raw.dig(
|
|
238
|
+
locs = raw.dig('stream', 'location') || []
|
|
236
239
|
return nil if locs.size < 2
|
|
237
240
|
|
|
238
241
|
pts = locs.map { |lng, lat| [lat, lng] }
|
|
@@ -246,16 +249,16 @@ module GitFit
|
|
|
246
249
|
return nil unless resp.code.to_i == 200
|
|
247
250
|
|
|
248
251
|
data = JSON.parse(resp.body)
|
|
249
|
-
data.dig(
|
|
252
|
+
data.dig('data', 'workout')
|
|
250
253
|
end
|
|
251
254
|
|
|
252
255
|
def map_sport(sport)
|
|
253
|
-
SPORT_MAP[sport] ||
|
|
256
|
+
SPORT_MAP[sport] || 'other'
|
|
254
257
|
end
|
|
255
258
|
|
|
256
259
|
def existing_run_ids
|
|
257
|
-
@db[:activities].where(source:
|
|
258
|
-
.map { |id| id.sub(
|
|
260
|
+
@db[:activities].where(source: 'xingzhe').select_map(:run_id)
|
|
261
|
+
.map { |id| id.sub('xingzhe_', '') }
|
|
259
262
|
end
|
|
260
263
|
end
|
|
261
264
|
end
|