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/cli.rb
CHANGED
|
@@ -1,36 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
2
4
|
|
|
3
5
|
module GitFit
|
|
4
6
|
class CLI < Thor
|
|
5
|
-
package_name
|
|
7
|
+
package_name 'git-fit'
|
|
6
8
|
include Cli::Sync
|
|
7
9
|
|
|
8
|
-
class_option :config, type: :string, desc:
|
|
10
|
+
class_option :config, type: :string, desc: 'Config file path', aliases: '-c'
|
|
9
11
|
|
|
10
12
|
def self.exit_on_failure?
|
|
11
13
|
true
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
desc
|
|
15
|
-
subcommand
|
|
16
|
+
desc 'db SUBCOMMAND', 'Database operations'
|
|
17
|
+
subcommand 'db', DB::CLI
|
|
16
18
|
|
|
17
|
-
desc
|
|
18
|
-
subcommand
|
|
19
|
+
desc 'import SUBCOMMAND', 'Import activities from local files'
|
|
20
|
+
subcommand 'import', ImportCLI
|
|
19
21
|
|
|
20
|
-
desc
|
|
21
|
-
subcommand
|
|
22
|
+
desc 'export SUBCOMMAND', 'Export activities to various formats'
|
|
23
|
+
subcommand 'export', ExportCLI
|
|
22
24
|
|
|
23
|
-
desc
|
|
25
|
+
desc 'version', 'Show version'
|
|
24
26
|
def version
|
|
25
27
|
puts "git-fit v#{GitFit::VERSION}"
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
desc
|
|
29
|
-
option :save, type: :boolean, desc:
|
|
30
|
+
desc 'parse FILE', 'Parse a GPX or TCX file'
|
|
31
|
+
option :save, type: :boolean, desc: 'Save parsed activities to database', aliases: '-s'
|
|
30
32
|
def parse(file)
|
|
31
33
|
format = file.match?(/\.gpx$/i) ? :gpx : :tcx
|
|
32
34
|
content = File.read(file)
|
|
33
|
-
parser = format == :gpx ? GitFit::Parser::GPX.new(source:
|
|
35
|
+
parser = format == :gpx ? GitFit::Parser::GPX.new(source: 'file') : GitFit::Parser::TCX.new(source: 'file')
|
|
34
36
|
activities = parser.call(content)
|
|
35
37
|
|
|
36
38
|
if activities.empty?
|
|
@@ -39,7 +41,7 @@ module GitFit
|
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
activities.each do |act|
|
|
42
|
-
say_status :activity, act[:name] ||
|
|
44
|
+
say_status :activity, act[:name] || 'unnamed', :green
|
|
43
45
|
say " #{act[:sport_category]}#{act[:sport_type] ? " (#{act[:sport_type]})" : ""}"
|
|
44
46
|
say " #{act[:start_date]} | #{act[:distance]&.round(2)}m" if act[:distance]
|
|
45
47
|
say " #{act[:moving_time]}s" if act[:moving_time]
|
|
@@ -58,30 +60,30 @@ module GitFit
|
|
|
58
60
|
end
|
|
59
61
|
say_status :done, "Saved #{activities.size} activities", :green
|
|
60
62
|
end
|
|
61
|
-
rescue => e
|
|
63
|
+
rescue StandardError => e
|
|
62
64
|
say_status :error, "Parse failed: #{e.message}", :red
|
|
63
65
|
end
|
|
64
66
|
|
|
65
|
-
desc
|
|
66
|
-
option :source, type: :string, desc:
|
|
67
|
-
option :activity, type: :array, desc:
|
|
67
|
+
desc 'sync', 'Sync workout data from a source'
|
|
68
|
+
option :source, type: :string, desc: 'Source name (e.g. lorem)'
|
|
69
|
+
option :activity, type: :array, desc: 'Filter by sport type', aliases: '--act'
|
|
68
70
|
def sync
|
|
69
71
|
super
|
|
70
72
|
end
|
|
71
73
|
|
|
72
|
-
desc
|
|
73
|
-
subcommand
|
|
74
|
+
desc 'gh SUBCOMMAND', 'Manage GitHub secrets and variables'
|
|
75
|
+
subcommand 'gh', GhCLI
|
|
74
76
|
|
|
75
|
-
desc
|
|
76
|
-
subcommand
|
|
77
|
+
desc 'geo SUBCOMMAND', 'Detect geographical administrative regions'
|
|
78
|
+
subcommand 'geo', GeoCLI
|
|
77
79
|
|
|
78
|
-
desc
|
|
79
|
-
subcommand
|
|
80
|
+
desc 'install SUBCOMMAND', 'Install project assets (actions)'
|
|
81
|
+
subcommand 'install', InstallCLI
|
|
80
82
|
|
|
81
|
-
desc
|
|
82
|
-
option :output, type: :string, desc:
|
|
83
|
+
desc 'init', 'Generate config.yml'
|
|
84
|
+
option :output, type: :string, desc: 'Output path', aliases: '-o'
|
|
83
85
|
def init
|
|
84
|
-
path = options[:output] ||
|
|
86
|
+
path = options[:output] || 'config/config.yml'
|
|
85
87
|
FileUtils.mkdir_p(File.dirname(path))
|
|
86
88
|
|
|
87
89
|
if File.exist?(path)
|
data/lib/git_fit/config.rb
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
2
4
|
|
|
3
5
|
module GitFit
|
|
4
6
|
# 三优先级: env var > YAML > DEFAULTS
|
|
5
7
|
class Config
|
|
6
8
|
DEFAULTS = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
'database' => { 'path' => 'db/fit.sqlite3' },
|
|
10
|
+
'sync' => {
|
|
11
|
+
'workers' => 3,
|
|
12
|
+
'time_budget' => nil,
|
|
13
|
+
'total_timeout' => nil,
|
|
14
|
+
},
|
|
15
|
+
'export' => {
|
|
16
|
+
'json' => { 'path' => 'site/activities.json' },
|
|
17
|
+
'svg' => { 'dir' => 'site/svg' },
|
|
18
|
+
'csv' => { 'path' => 'site/workouts.csv' },
|
|
19
|
+
'gpx' => { 'path' => 'site/gpx_out' },
|
|
12
20
|
},
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"svg" => { "dir" => "site/svg" },
|
|
16
|
-
"csv" => { "path" => "site/workouts.csv" },
|
|
17
|
-
"gpx" => { "path" => "site/gpx_out" }
|
|
21
|
+
'privacy' => {
|
|
22
|
+
'start_end_range' => 200,
|
|
18
23
|
},
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
'system' => {
|
|
25
|
+
'units' => 'metric',
|
|
26
|
+
'log_level' => 'info',
|
|
27
|
+
'timezone' => 'Asia/Shanghai',
|
|
21
28
|
},
|
|
22
|
-
"system" => {
|
|
23
|
-
"units" => "metric",
|
|
24
|
-
"log_level" => "info",
|
|
25
|
-
"timezone" => "Asia/Shanghai"
|
|
26
|
-
}
|
|
27
29
|
}.freeze
|
|
28
30
|
|
|
29
|
-
DEFAULT_PATH =
|
|
31
|
+
DEFAULT_PATH = 'config/config.yml'
|
|
30
32
|
KNOWN_TOP_KEYS = %w[database sync export privacy system].freeze
|
|
31
33
|
|
|
32
34
|
def initialize(path = nil)
|
|
@@ -46,29 +48,41 @@ module GitFit
|
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def sync_config(source)
|
|
49
|
-
val = @data.dig(
|
|
51
|
+
val = @data.dig('sync', source)
|
|
50
52
|
val.is_a?(Hash) ? val : {}
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
def privacy_config
|
|
54
|
-
@data[
|
|
56
|
+
@data['privacy'] || {}
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
def export_config
|
|
58
|
-
@data[
|
|
60
|
+
@data['export'] || {}
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
def db_path
|
|
62
|
-
@data.dig(
|
|
64
|
+
@data.dig('database', 'path') || DEFAULTS['database']['path']
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
private
|
|
66
68
|
|
|
67
69
|
def load_env
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
if ENV['GIT_FIT_DATABASE_PATH'] && !ENV['GIT_FIT_DATABASE_PATH'].empty?
|
|
71
|
+
@data['database']['path'] =
|
|
72
|
+
ENV['GIT_FIT_DATABASE_PATH']
|
|
73
|
+
end
|
|
74
|
+
if ENV['GIT_FIT_SYNC_WORKERS'] && !ENV['GIT_FIT_SYNC_WORKERS'].empty?
|
|
75
|
+
@data['sync']['workers'] =
|
|
76
|
+
ENV['GIT_FIT_SYNC_WORKERS'].to_i
|
|
77
|
+
end
|
|
78
|
+
if ENV['GIT_FIT_SYNC_TIME_BUDGET'] && !ENV['GIT_FIT_SYNC_TIME_BUDGET'].empty?
|
|
79
|
+
@data['sync']['time_budget'] =
|
|
80
|
+
ENV['GIT_FIT_SYNC_TIME_BUDGET'].to_f
|
|
81
|
+
end
|
|
82
|
+
if ENV['GIT_FIT_SYNC_TOTAL_TIMEOUT'] && !ENV['GIT_FIT_SYNC_TOTAL_TIMEOUT'].empty?
|
|
83
|
+
@data['sync']['total_timeout'] =
|
|
84
|
+
ENV['GIT_FIT_SYNC_TOTAL_TIMEOUT'].to_f
|
|
85
|
+
end
|
|
72
86
|
|
|
73
87
|
GitFit.registered_configs.each do |reg|
|
|
74
88
|
reg[:keys].each do |key|
|
|
@@ -89,7 +103,7 @@ module GitFit
|
|
|
89
103
|
def load_file(path)
|
|
90
104
|
yaml = YAML.safe_load(File.read(path)) || {}
|
|
91
105
|
deep_merge!(@data, yaml)
|
|
92
|
-
rescue => e
|
|
106
|
+
rescue StandardError => e
|
|
93
107
|
warn "Config: failed to load #{path}: #{e.message}"
|
|
94
108
|
end
|
|
95
109
|
|
data/lib/git_fit/db/activity.rb
CHANGED
data/lib/git_fit/db/cli.rb
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
2
4
|
|
|
3
5
|
module GitFit
|
|
4
6
|
module DB
|
|
5
7
|
class CLI < Thor
|
|
6
|
-
desc
|
|
8
|
+
desc 'migrate', 'Run database migrations'
|
|
7
9
|
def migrate
|
|
8
10
|
config = GitFit::Config.new
|
|
9
11
|
conn = GitFit::DB::Connection.new(config.db_path)
|
|
10
12
|
conn.migrate!
|
|
11
|
-
say_status :done,
|
|
12
|
-
rescue => e
|
|
13
|
+
say_status :done, 'Migrations up to date', :green
|
|
14
|
+
rescue StandardError => e
|
|
13
15
|
say_status :error, "Migration failed: #{e.message}", :red
|
|
14
16
|
end
|
|
15
17
|
|
|
16
|
-
desc
|
|
17
|
-
option :hash, type: :boolean, desc:
|
|
18
|
+
desc 'schema', 'Print DB schema DDL derived from migrations'
|
|
19
|
+
option :hash, type: :boolean, desc: 'Print sha256 hexdigest instead of DDL'
|
|
18
20
|
def schema
|
|
19
|
-
require
|
|
21
|
+
require 'digest'
|
|
20
22
|
db = Sequel.sqlite
|
|
21
23
|
Sequel.extension :migration
|
|
22
|
-
dir = File.expand_path(
|
|
24
|
+
dir = File.expand_path('../../../db/migrations', __dir__)
|
|
23
25
|
Sequel::Migrator.run(db, dir)
|
|
24
|
-
ddl = db.fetch(
|
|
26
|
+
ddl = db.fetch('SELECT sql FROM sqlite_master WHERE sql IS NOT NULL ORDER BY name')
|
|
25
27
|
.map(:sql).join("\n") + "\n"
|
|
26
28
|
if options[:hash]
|
|
27
29
|
puts Digest::SHA256.hexdigest(ddl)
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'sequel'
|
|
3
5
|
|
|
4
6
|
module GitFit
|
|
5
7
|
module DB
|
|
6
8
|
class Connection
|
|
7
9
|
def initialize(db_path)
|
|
8
|
-
FileUtils.mkdir_p(File.dirname(db_path)) if db_path.include?(
|
|
10
|
+
FileUtils.mkdir_p(File.dirname(db_path)) if db_path.include?('/')
|
|
9
11
|
@db = Sequel.sqlite(db_path)
|
|
10
|
-
@db.run(
|
|
11
|
-
@db.run(
|
|
12
|
-
@migrations_path = File.expand_path(
|
|
12
|
+
@db.run('PRAGMA journal_mode=WAL')
|
|
13
|
+
@db.run('PRAGMA busy_timeout=5000')
|
|
14
|
+
@migrations_path = File.expand_path('../../../db/migrations', __dir__)
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def db
|
data/lib/git_fit/db/rebuild.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
2
4
|
|
|
3
5
|
module GitFit
|
|
4
6
|
module DB
|
|
@@ -18,7 +20,7 @@ module GitFit
|
|
|
18
20
|
data = {}
|
|
19
21
|
(tables - migration_tables).each { |t| data[t] = export_table(db, t) }
|
|
20
22
|
|
|
21
|
-
db.run(
|
|
23
|
+
db.run('PRAGMA foreign_keys = OFF')
|
|
22
24
|
tables.each { |t| db.drop_table(t) }
|
|
23
25
|
|
|
24
26
|
Sequel.extension :migration
|
|
@@ -31,7 +33,7 @@ module GitFit
|
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
warn "Rebuilt #{db_path}: #{data.values.sum(&:size)} rows from #{data.size} tables"
|
|
34
|
-
rescue => e
|
|
36
|
+
rescue StandardError => e
|
|
35
37
|
if backup && File.exist?(backup)
|
|
36
38
|
FileUtils.cp(backup, db_path)
|
|
37
39
|
%w[-wal -shm].each do |ext|
|
|
@@ -1,28 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module GitFit
|
|
2
4
|
module Export
|
|
3
5
|
module Defaults
|
|
4
6
|
SVG = {
|
|
5
|
-
background:
|
|
6
|
-
track2:
|
|
7
|
-
empty_cell:
|
|
7
|
+
background: '#222222', text: '#FFFFFF', track: '#4DD2FF',
|
|
8
|
+
track2: '#4DD2FF', special: '#FFFF00', special2: '#FFFF00',
|
|
9
|
+
empty_cell: '#444444', dim_past: '#555555', font: 'Arial'
|
|
8
10
|
}.freeze
|
|
9
11
|
|
|
10
12
|
DASHBOARD = {
|
|
11
|
-
background:
|
|
12
|
-
active_row:
|
|
13
|
-
text_secondary:
|
|
14
|
-
accent:
|
|
13
|
+
background: '#1a1a2e', surface: '#16213e', hover: '#0f3460',
|
|
14
|
+
active_row: '#1a3a6a', border: '#2a2a4a', text: '#e0e0e0',
|
|
15
|
+
text_secondary: '#888', text_dim: '#666', text_insight: '#aaa',
|
|
16
|
+
accent: '#4DD2FF', font: '-apple-system, BlinkMacSystemFont, sans-serif'
|
|
15
17
|
}.freeze
|
|
16
18
|
|
|
17
19
|
CATEGORIES = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
'run' => '#4DD2FF', 'walk' => '#4ECDC4',
|
|
21
|
+
'ride' => '#FF6B6B', 'hike' => '#FFD93D'
|
|
20
22
|
}.freeze
|
|
21
23
|
|
|
22
24
|
MAP = {
|
|
23
|
-
tiles_dark:
|
|
24
|
-
tiles_light:
|
|
25
|
-
echarts_theme:
|
|
25
|
+
tiles_dark: 'https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png',
|
|
26
|
+
tiles_light: 'https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png',
|
|
27
|
+
echarts_theme: 'dark',
|
|
26
28
|
}.freeze
|
|
27
29
|
end
|
|
28
30
|
end
|
data/lib/git_fit/export/json.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
2
4
|
|
|
3
5
|
module GitFit
|
|
4
6
|
module Export
|
|
5
7
|
class JSON
|
|
6
|
-
PLACEHOLDER_POLYLINE =
|
|
8
|
+
PLACEHOLDER_POLYLINE = 'gqqrFurkeU??'
|
|
7
9
|
|
|
8
10
|
def initialize(db:, output:, activity_filter: nil)
|
|
9
11
|
@db = db
|
|
@@ -49,7 +51,7 @@ module GitFit
|
|
|
49
51
|
start_date: a[:start_date],
|
|
50
52
|
start_date_local: a[:start_date_local],
|
|
51
53
|
location_country: a[:location_country],
|
|
52
|
-
summary_polyline: a[:source] ==
|
|
54
|
+
summary_polyline: a[:source] == 'keep' && a[:summary_polyline] == PLACEHOLDER_POLYLINE ?
|
|
53
55
|
nil : a[:summary_polyline],
|
|
54
56
|
average_heartrate: a[:average_heartrate] && a[:average_heartrate] != 0 ? a[:average_heartrate] : nil,
|
|
55
57
|
max_heartrate: a[:max_heartrate],
|
data/lib/git_fit/export.rb
CHANGED
data/lib/git_fit/fit/decoder.rb
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mini_racer'
|
|
4
|
+
require 'json'
|
|
3
5
|
|
|
4
6
|
module GitFit
|
|
5
7
|
module FIT
|
|
6
8
|
class Decoder
|
|
7
|
-
JS_CODE = File.read(File.join(__dir__,
|
|
9
|
+
JS_CODE = File.read(File.join(__dir__, 'decoder.js')).freeze
|
|
8
10
|
CTX = MiniRacer::Context.new.tap { |c| c.eval(JS_CODE) }
|
|
9
11
|
|
|
10
12
|
def self.decode(path)
|
|
11
|
-
bytes = File.read(path, mode:
|
|
13
|
+
bytes = File.read(path, mode: 'rb').bytes
|
|
12
14
|
json = CTX.eval("FitDecoder.decodeFit(#{bytes.inspect})")
|
|
13
15
|
records = JSON.parse(json)
|
|
14
16
|
records.each do |r|
|
|
15
|
-
r[
|
|
16
|
-
r[
|
|
17
|
+
r['positionLat'] *= 180 if r['positionLat']
|
|
18
|
+
r['positionLong'] *= 180 if r['positionLong']
|
|
17
19
|
end
|
|
18
20
|
records
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def self.sport(path)
|
|
22
|
-
bytes = File.read(path, mode:
|
|
24
|
+
bytes = File.read(path, mode: 'rb').bytes
|
|
23
25
|
CTX.eval("FitDecoder.fitSport(#{bytes.inspect})")
|
|
24
26
|
end
|
|
25
27
|
end
|
data/lib/git_fit/fit.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
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 AMapClient
|
|
8
|
-
BASE_URL =
|
|
10
|
+
BASE_URL = 'https://restapi.amap.com/v3/geocode/regeo'
|
|
9
11
|
|
|
10
12
|
def initialize(api_key:)
|
|
11
13
|
@api_key = api_key
|
|
@@ -20,13 +22,13 @@ module GitFit
|
|
|
20
22
|
uri.query = URI.encode_www_form(
|
|
21
23
|
key: @api_key,
|
|
22
24
|
location: "#{lng},#{lat}",
|
|
23
|
-
output:
|
|
25
|
+
output: 'JSON',
|
|
24
26
|
radius: 1000,
|
|
25
|
-
extensions:
|
|
27
|
+
extensions: 'all',
|
|
26
28
|
)
|
|
27
29
|
resp = Net::HTTP.get_response(uri)
|
|
28
30
|
body = JSON.parse(resp.body)
|
|
29
|
-
if body[
|
|
31
|
+
if body['status'] != '1'
|
|
30
32
|
raise "AMap API error: #{body["info"]}"
|
|
31
33
|
end
|
|
32
34
|
parse_response(body)
|
|
@@ -35,12 +37,12 @@ module GitFit
|
|
|
35
37
|
private
|
|
36
38
|
|
|
37
39
|
def parse_response(body)
|
|
38
|
-
comp = body.dig(
|
|
39
|
-
province = comp[
|
|
40
|
-
city = comp[
|
|
41
|
-
district = comp[
|
|
42
|
-
township = comp[
|
|
43
|
-
{ province: province, city: city, district: district, township: township, source:
|
|
40
|
+
comp = body.dig('regeocode', 'addressComponent') || {}
|
|
41
|
+
province = comp['province'].to_s.empty? ? nil : comp['province']
|
|
42
|
+
city = comp['city'].to_s.empty? || comp['city'] == comp['province'] ? nil : comp['city']
|
|
43
|
+
district = comp['district'].to_s.empty? ? nil : comp['district']
|
|
44
|
+
township = comp['township'].to_s.empty? ? nil : comp['township']
|
|
45
|
+
{ province: province, city: city, district: district, township: township, source: 'amap' }
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
def rate_limit
|