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
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require_relative 'reverse_geocode'
|
|
4
6
|
|
|
5
7
|
module GitFit
|
|
6
8
|
module Geo
|
|
7
9
|
class DivisionDetector
|
|
8
|
-
KEEP_PLACEHOLDER =
|
|
10
|
+
KEEP_PLACEHOLDER = 'gqqrFurkeU??'
|
|
9
11
|
SAMPLE_INTERVAL_DEG = 0.01
|
|
10
|
-
CACHE_DIR =
|
|
11
|
-
POINT_CACHE_FILE = File.join(CACHE_DIR,
|
|
12
|
+
CACHE_DIR = 'data/cache'
|
|
13
|
+
POINT_CACHE_FILE = File.join(CACHE_DIR, 'geo-point.jsonl')
|
|
12
14
|
|
|
13
|
-
def initialize(db:, amap_key: nil, batch: 20, limit: nil, strategy:
|
|
15
|
+
def initialize(db:, amap_key: nil, batch: 20, limit: nil, strategy: 'proportional', time_budget: nil,
|
|
16
|
+
dry_run: false)
|
|
14
17
|
@db = db
|
|
15
18
|
@geocoder = ReverseGeocode.new(amap_key: amap_key)
|
|
16
19
|
@amap_key = amap_key
|
|
@@ -39,9 +42,9 @@ module GitFit
|
|
|
39
42
|
@total = activities.size
|
|
40
43
|
|
|
41
44
|
if @total > 0 && @amap_key.nil?
|
|
42
|
-
$stdout.puts
|
|
43
|
-
$stdout.puts
|
|
44
|
-
$stdout.puts
|
|
45
|
+
$stdout.puts 'WARNING: AMAP_API_KEY not configured. China coordinates will fail.'
|
|
46
|
+
$stdout.puts 'Set via: ENV["AMAP_API_KEY"] or config.yml sync.amap.api_key'
|
|
47
|
+
$stdout.puts ''
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
$stdout.puts "Geo detection: #{@total} activities to process"
|
|
@@ -57,9 +60,9 @@ module GitFit
|
|
|
57
60
|
|
|
58
61
|
def pending_activities
|
|
59
62
|
dataset = @db[:activities]
|
|
60
|
-
.where(Sequel.lit(
|
|
63
|
+
.where(Sequel.lit('summary_polyline IS NOT NULL'))
|
|
61
64
|
.where(Sequel.lit("summary_polyline != ''"))
|
|
62
|
-
.where(Sequel.lit(
|
|
65
|
+
.where(Sequel.lit('divisions IS NULL'))
|
|
63
66
|
.order(Sequel.desc(:start_date))
|
|
64
67
|
dataset = dataset.limit(@limit) if @limit
|
|
65
68
|
dataset.all
|
|
@@ -121,13 +124,15 @@ module GitFit
|
|
|
121
124
|
update_db(run_id, summary) unless @dry_run
|
|
122
125
|
$stdout.puts " #{prefix} #{run_id} → #{format_divisions(divisions)} (#{samples.size} samples)"
|
|
123
126
|
@processed += 1
|
|
124
|
-
rescue => e
|
|
127
|
+
rescue StandardError => e
|
|
125
128
|
$stdout.puts " #{prefix} #{run_id} → error: #{e.message}"
|
|
126
129
|
@errors += 1
|
|
127
130
|
end
|
|
128
131
|
|
|
129
132
|
def format_divisions(divisions)
|
|
130
|
-
divisions.map
|
|
133
|
+
divisions.map do |d|
|
|
134
|
+
[d[:province], d[:city], d[:district], d[:township]].compact.reject(&:empty?).join(' ')
|
|
135
|
+
end.join(' / ')
|
|
131
136
|
end
|
|
132
137
|
|
|
133
138
|
def decode_points(polyline)
|
|
@@ -165,21 +170,21 @@ module GitFit
|
|
|
165
170
|
key_counts[key] += 1
|
|
166
171
|
end
|
|
167
172
|
key_counts.map do |key, count|
|
|
168
|
-
province, city, district, township = key.split(
|
|
173
|
+
province, city, district, township = key.split('|')
|
|
169
174
|
{ province: province, city: city, district: district, township: township, count: count }
|
|
170
175
|
end.sort_by { |r| -r[:count] }
|
|
171
176
|
end
|
|
172
177
|
|
|
173
178
|
def normalize_entry(raw)
|
|
174
179
|
return nil unless raw
|
|
175
|
-
province = raw[:province] || raw[
|
|
176
|
-
province = nil if province.is_a?(Array) || province.to_s.empty? || province ==
|
|
180
|
+
province = raw[:province] || raw['p'] || raw['province']
|
|
181
|
+
province = nil if province.is_a?(Array) || province.to_s.empty? || province == '中华人民共和国'
|
|
177
182
|
return nil unless province
|
|
178
183
|
{
|
|
179
184
|
province: province,
|
|
180
|
-
city: pick_field(raw, :city,
|
|
181
|
-
district: pick_field(raw, :district,
|
|
182
|
-
township: pick_field(raw, :township,
|
|
185
|
+
city: pick_field(raw, :city, 'city', 'c', 'city') { |v| valid_name?(v) },
|
|
186
|
+
district: pick_field(raw, :district, 'dist', 'd'),
|
|
187
|
+
township: pick_field(raw, :township, 'town', 't'),
|
|
183
188
|
}
|
|
184
189
|
end
|
|
185
190
|
|
|
@@ -196,7 +201,7 @@ module GitFit
|
|
|
196
201
|
end
|
|
197
202
|
|
|
198
203
|
def division_key(result)
|
|
199
|
-
[result[:province], result[:city], result[:district], result[:township]].join(
|
|
204
|
+
[result[:province], result[:city], result[:district], result[:township]].join('|')
|
|
200
205
|
end
|
|
201
206
|
|
|
202
207
|
def build_summary(divisions, total_points)
|
|
@@ -210,15 +215,15 @@ module GitFit
|
|
|
210
215
|
h[:n] = r[:count]
|
|
211
216
|
h
|
|
212
217
|
end
|
|
213
|
-
{ divisions: division_list, total_sampled: total_points, source:
|
|
218
|
+
{ divisions: division_list, total_sampled: total_points, source: 'amap', processed_at: Time.now.utc.iso8601 }
|
|
214
219
|
end
|
|
215
220
|
|
|
216
221
|
def lookup_point_cache(coord_key)
|
|
217
222
|
return nil unless File.exist?(POINT_CACHE_FILE)
|
|
218
|
-
File.open(POINT_CACHE_FILE,
|
|
223
|
+
File.open(POINT_CACHE_FILE, 'r') do |f|
|
|
219
224
|
f.each_line do |line|
|
|
220
225
|
entry = JSON.parse(line) rescue next
|
|
221
|
-
return entry if entry[
|
|
226
|
+
return entry if entry['ck'] == coord_key
|
|
222
227
|
end
|
|
223
228
|
end
|
|
224
229
|
nil
|
|
@@ -229,7 +234,7 @@ module GitFit
|
|
|
229
234
|
entry[:city] = result[:city] if result[:city]
|
|
230
235
|
entry[:dist] = result[:district] if result[:district]
|
|
231
236
|
entry[:town] = result[:township] if result[:township]
|
|
232
|
-
File.open(POINT_CACHE_FILE,
|
|
237
|
+
File.open(POINT_CACHE_FILE, 'a') { |f| f.puts(JSON.generate(entry)) }
|
|
233
238
|
end
|
|
234
239
|
|
|
235
240
|
def update_db(run_id, summary)
|
|
@@ -237,7 +242,7 @@ module GitFit
|
|
|
237
242
|
end
|
|
238
243
|
|
|
239
244
|
def mark_empty_divisions(run_id)
|
|
240
|
-
@db[:activities].where(run_id: run_id).update(divisions:
|
|
245
|
+
@db[:activities].where(run_id: run_id).update(divisions: '[]')
|
|
241
246
|
end
|
|
242
247
|
|
|
243
248
|
def load_caches
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
4
6
|
|
|
5
7
|
module GitFit
|
|
6
8
|
module Geo
|
|
7
9
|
class NominatimClient
|
|
8
|
-
BASE_URL =
|
|
9
|
-
USER_AGENT =
|
|
10
|
+
BASE_URL = 'https://nominatim.openstreetmap.org/reverse'
|
|
11
|
+
USER_AGENT = 'git-fit/1.0 (geo-detector)'
|
|
10
12
|
|
|
11
13
|
def initialize
|
|
12
14
|
@mutex = Mutex.new
|
|
@@ -20,17 +22,17 @@ module GitFit
|
|
|
20
22
|
uri.query = URI.encode_www_form(
|
|
21
23
|
lat: lat,
|
|
22
24
|
lon: lng,
|
|
23
|
-
format:
|
|
25
|
+
format: 'jsonv2',
|
|
24
26
|
addressdetails: 1,
|
|
25
27
|
zoom: 10,
|
|
26
28
|
)
|
|
27
29
|
resp = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
28
30
|
req = Net::HTTP::Get.new(uri)
|
|
29
|
-
req[
|
|
31
|
+
req['User-Agent'] = USER_AGENT
|
|
30
32
|
http.request(req)
|
|
31
33
|
end
|
|
32
34
|
body = JSON.parse(resp.body)
|
|
33
|
-
if body[
|
|
35
|
+
if body['error']
|
|
34
36
|
raise "Nominatim error: #{body["error"]}"
|
|
35
37
|
end
|
|
36
38
|
parse_response(body)
|
|
@@ -39,10 +41,10 @@ module GitFit
|
|
|
39
41
|
private
|
|
40
42
|
|
|
41
43
|
def parse_response(body)
|
|
42
|
-
addr = body[
|
|
43
|
-
country = addr[
|
|
44
|
-
state = addr[
|
|
45
|
-
{ province: country, city: state, district: nil, township: nil, source:
|
|
44
|
+
addr = body['address'] || {}
|
|
45
|
+
country = addr['country']
|
|
46
|
+
state = addr['state']
|
|
47
|
+
{ province: country, city: state, district: nil, township: nil, source: 'nominatim' }
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def rate_limit
|
data/lib/git_fit/geo/polyline.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module GitFit
|
|
2
4
|
module Geo
|
|
3
5
|
module Polyline
|
|
@@ -43,7 +45,7 @@ module GitFit
|
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def encode(points)
|
|
46
|
-
result =
|
|
48
|
+
result = ''
|
|
47
49
|
plat = 0
|
|
48
50
|
plng = 0
|
|
49
51
|
|
|
@@ -63,7 +65,7 @@ module GitFit
|
|
|
63
65
|
|
|
64
66
|
def encode_number(num)
|
|
65
67
|
num = num < 0 ? ~(num << 1) : (num << 1)
|
|
66
|
-
result =
|
|
68
|
+
result = ''
|
|
67
69
|
|
|
68
70
|
while num >= 0x20
|
|
69
71
|
result += ((0x20 | (num & 0x1f)) + 63).chr
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require_relative
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'coord_transform'
|
|
4
|
+
require_relative 'amap_client'
|
|
5
|
+
require_relative 'nominatim_client'
|
|
4
6
|
|
|
5
7
|
module GitFit
|
|
6
8
|
module Geo
|
|
7
9
|
class ReverseGeocode
|
|
8
10
|
CHINA_BBOX = {
|
|
9
11
|
min_lng: 73, max_lng: 135,
|
|
10
|
-
min_lat: 18, max_lat: 54
|
|
12
|
+
min_lat: 18, max_lat: 54
|
|
11
13
|
}.freeze
|
|
12
14
|
|
|
13
15
|
def initialize(amap_key: nil)
|
|
@@ -31,17 +33,17 @@ module GitFit
|
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def lookup_china(lat, lng)
|
|
34
|
-
raise
|
|
36
|
+
raise 'AMap API key not configured' unless @amap
|
|
35
37
|
gcj_lat, gcj_lng = CoordTransform.wgs84_to_gcj02(lat, lng)
|
|
36
38
|
@amap.reverse_geocode(gcj_lat, gcj_lng)
|
|
37
|
-
rescue => e
|
|
38
|
-
{ province: nil, city: nil, district: nil, township: nil, source:
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
{ province: nil, city: nil, district: nil, township: nil, source: 'error', error: e.message }
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def lookup_intl(lat, lng)
|
|
42
44
|
@nominatim.reverse_geocode(lat, lng)
|
|
43
|
-
rescue => e
|
|
44
|
-
{ province: nil, city: nil, district: nil, township: nil, source:
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
{ province: nil, city: nil, district: nil, township: nil, source: 'error', error: e.message }
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
end
|
data/lib/git_fit/geo.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module GitFit
|
|
2
4
|
module Geo
|
|
3
5
|
end
|
|
4
6
|
end
|
|
5
7
|
|
|
6
|
-
require_relative
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
10
|
-
require_relative
|
|
11
|
-
require_relative
|
|
8
|
+
require_relative 'geo/polyline'
|
|
9
|
+
require_relative 'geo/coord_transform'
|
|
10
|
+
require_relative 'geo/amap_client'
|
|
11
|
+
require_relative 'geo/nominatim_client'
|
|
12
|
+
require_relative 'geo/reverse_geocode'
|
|
13
|
+
require_relative 'geo/division_detector'
|