coordconver 0.1.3 → 1.1.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 +5 -5
- data/.github/workflows/ci.yml +36 -0
- data/CHANGELOG.md +59 -0
- data/README.en.md +177 -0
- data/README.md +270 -67
- data/coordconver.gemspec +33 -17
- data/docs/cc-algorithms.js +293 -0
- data/docs/index.html +283 -0
- data/exe/coordconver +241 -0
- data/lib/coordconver/coordconver.rb +286 -104
- data/lib/coordconver/degree.rb +55 -58
- data/lib/coordconver/errors.rb +23 -0
- data/lib/coordconver/formats.rb +51 -0
- data/lib/coordconver/geo/api.rb +45 -0
- data/lib/coordconver/geo/baidu.rb +90 -0
- data/lib/coordconver/geo/cache.rb +66 -0
- data/lib/coordconver/geo/client.rb +25 -0
- data/lib/coordconver/geo/configuration.rb +112 -0
- data/lib/coordconver/geo/gaode.rb +105 -0
- data/lib/coordconver/geo/key_pool.rb +78 -0
- data/lib/coordconver/geo/tencent.rb +69 -0
- data/lib/coordconver/projection.rb +129 -0
- data/lib/coordconver/utils/http.rb +97 -0
- data/lib/coordconver/utils/request.rb +43 -0
- data/lib/coordconver/version.rb +3 -1
- data/lib/coordconver.rb +27 -2
- metadata +43 -32
- data/.travis.yml +0 -8
- data/bin/coordconver +0 -4
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coordconver
|
|
4
|
+
module Geo
|
|
5
|
+
# 多 key 智能轮询: round-robin 取 key, 请求失败(配额超限/无效)时摘除该 key,
|
|
6
|
+
# 冷却时间过后自动恢复。线程安全。
|
|
7
|
+
class KeyPool
|
|
8
|
+
# 被摘除 key 的冷却时长(秒), 覆盖"每秒/每日配额超限"两类场景
|
|
9
|
+
COOLDOWN = 300
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
@index = {}
|
|
14
|
+
@disabled_at = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# 轮询取下一个可用 key; 全部处于冷却期时抛出 ApiError
|
|
18
|
+
def next_key(provider, keys)
|
|
19
|
+
@mutex.synchronize do
|
|
20
|
+
purge_expired(Time.now)
|
|
21
|
+
enabled = keys.reject { |k| @disabled_at[[provider, k]] }
|
|
22
|
+
if enabled.empty?
|
|
23
|
+
raise ApiError, "所有 #{provider} key 均不可用(配额超限或无效), 请更换或补充 key"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@index[provider] = (@index[provider] ||= -1) + 1
|
|
27
|
+
enabled[@index[provider] % enabled.size]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def mark_bad(provider, key)
|
|
32
|
+
@mutex.synchronize { @disabled_at[[provider, key]] = Time.now }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def disabled?(provider, key)
|
|
36
|
+
@mutex.synchronize do
|
|
37
|
+
purge_expired(Time.now)
|
|
38
|
+
!@disabled_at[[provider, key]].nil?
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def reset!
|
|
43
|
+
@mutex.synchronize do
|
|
44
|
+
@index.clear
|
|
45
|
+
@disabled_at.clear
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def purge_expired(now)
|
|
52
|
+
@disabled_at.reject! { |_, t| now - t > COOLDOWN }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class << self
|
|
56
|
+
def instance
|
|
57
|
+
@instance ||= new
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def next_key(provider, keys)
|
|
61
|
+
instance.next_key(provider, keys)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def mark_bad(provider, key)
|
|
65
|
+
instance.mark_bad(provider, key)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def disabled?(provider, key)
|
|
69
|
+
instance.disabled?(provider, key)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def reset!
|
|
73
|
+
instance.reset!
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coordconver
|
|
4
|
+
module Geo
|
|
5
|
+
# 腾讯位置服务 WebService API
|
|
6
|
+
# 申请 key: https://lbs.qq.com/dev/console/application/mine (启用 WebServiceAPI)
|
|
7
|
+
# 坐标为 GCJ-02; 逆地理编码 location 参数为 "lat,lng" 顺序
|
|
8
|
+
module Tencent
|
|
9
|
+
extend self
|
|
10
|
+
|
|
11
|
+
# 视为"key 无效/配额超限"的状态码, 触发自动轮换下一个 key
|
|
12
|
+
# 110 请求来源未被授权 112 IP未被授权 120 每秒调用量超限 121 日调用量超限 199 key结构错误
|
|
13
|
+
KEY_ERROR_STATUSES = [110, 112, 120, 121, 199].freeze
|
|
14
|
+
|
|
15
|
+
# 地理编码: 结构化地址 -> 坐标 (GCJ-02)
|
|
16
|
+
# Coordconver::Tencent.geo("北京市海淀区彩和坊路海淀西大街74号")["result"]["location"]
|
|
17
|
+
# 支持可选参数: region(城市), get_poi, ...
|
|
18
|
+
def geo(address, options = {})
|
|
19
|
+
params = { address: address }.merge(options)
|
|
20
|
+
request("/geocoder/v1/", params)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# 逆地理编码: 坐标 (GCJ-02) -> 结构化地址
|
|
24
|
+
# Coordconver::Tencent.regeo(116.322987, 39.983424)["result"]["address"]
|
|
25
|
+
def regeo(lng, lat, options = {})
|
|
26
|
+
params = {
|
|
27
|
+
location: "#{lat},#{lng}", # 腾讯接口为 lat,lng 顺序
|
|
28
|
+
get_poi: 0
|
|
29
|
+
}.merge(options)
|
|
30
|
+
request("/geocoder/v1/", params)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# IP 定位: IP -> 城市 + 坐标 (GCJ-02)
|
|
34
|
+
def ip(ip = nil)
|
|
35
|
+
params = {}
|
|
36
|
+
params[:ip] = ip unless ip.nil? || ip.empty?
|
|
37
|
+
request("/location/v1/ip", params)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# 便捷方法: 地址 -> [lng, lat] (GCJ-02), 解析失败返回 nil
|
|
41
|
+
def geo_location(address, options = {})
|
|
42
|
+
loc = geo(address, options).dig("result", "location")
|
|
43
|
+
loc && [loc["lng"], loc["lat"]]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# 便捷方法: 坐标 -> 地址字符串 (GCJ-02), 解析失败返回 nil
|
|
47
|
+
def regeo_address(lng, lat, options = {})
|
|
48
|
+
result = regeo(lng, lat, options).dig("result") || {}
|
|
49
|
+
result.dig("formatted_addresses", "recommend") || result["address"]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def request(path, params)
|
|
55
|
+
Api.call(
|
|
56
|
+
provider: :tencent,
|
|
57
|
+
url: base_url + path,
|
|
58
|
+
params: params,
|
|
59
|
+
key_param: :key,
|
|
60
|
+
bad_result: ->(r) { KEY_ERROR_STATUSES.include?(r["status"].to_i) }
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def base_url
|
|
65
|
+
Configuration.settings[:tencent_base_url].sub(%r{/\z}, "")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# 高斯-克吕格投影(横轴墨卡托)正反算, 以及 3°/6° 带号与中央经线换算。
|
|
4
|
+
# 适用于 CGCS2000 / 北京54 / 西安80 / WGS-84 椭球, 精度毫米级。
|
|
5
|
+
#
|
|
6
|
+
# 经 Coordconver.extend(Projection) 挂载:
|
|
7
|
+
# Coordconver.gauss_kruger(121.47, 31.24) # => [x东坐标, y北坐标]
|
|
8
|
+
# Coordconver.inverse_gauss_kruger(543_000, 3_456_000, central_meridian: 120)
|
|
9
|
+
# Coordconver.zone_meridian(121.47, width: 3) # => [40, 120]
|
|
10
|
+
module Coordconver
|
|
11
|
+
module Projection
|
|
12
|
+
extend self
|
|
13
|
+
|
|
14
|
+
# 椭球参数: a 长半轴(米), rf 扁率倒数
|
|
15
|
+
ELLIPSOIDS = {
|
|
16
|
+
cgcs2000: [6_378_137.0, 298.257_222_101],
|
|
17
|
+
wgs84: [6_378_137.0, 298.257_223_563],
|
|
18
|
+
krassovsky: [6_378_245.0, 298.3], # 北京54
|
|
19
|
+
iag75: [6_378_140.0, 298.257] # 西安80
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
# 高斯-克吕格正算: 经纬度 -> 平面坐标 [东坐标 x, 北坐标 y] (米)
|
|
23
|
+
# central_meridian: 中央经线(度), 缺省按 zone_width 自动推算
|
|
24
|
+
# zone_width: 3 或 6 (度), 仅在未指定 central_meridian 时使用
|
|
25
|
+
# false_easting: 东坐标偏移(米), 默认 500_000 (测绘惯例, 保证 x 为正)
|
|
26
|
+
# ellipsoid: :cgcs2000 / :wgs84 / :krassovsky(北京54) / :iag75(西安80)
|
|
27
|
+
def gauss_kruger(lng, lat, central_meridian: nil, zone_width: 6,
|
|
28
|
+
ellipsoid: :cgcs2000, false_easting: 500_000, false_northing: 0)
|
|
29
|
+
a, f = ellipsoid_params(ellipsoid)
|
|
30
|
+
cm = central_meridian || zone_meridian(lng, width: zone_width)[1]
|
|
31
|
+
e2 = f * (2 - f)
|
|
32
|
+
ep2 = e2 / (1 - e2)
|
|
33
|
+
|
|
34
|
+
p = deg2rad(lat)
|
|
35
|
+
dl = deg2rad(lng - cm)
|
|
36
|
+
sin_p = Math.sin(p)
|
|
37
|
+
cos_p = Math.cos(p)
|
|
38
|
+
tan_p = Math.tan(p)
|
|
39
|
+
|
|
40
|
+
n = a / Math.sqrt(1 - e2 * sin_p * sin_p)
|
|
41
|
+
t = tan_p * tan_p
|
|
42
|
+
c = ep2 * cos_p * cos_p
|
|
43
|
+
aa = dl * cos_p
|
|
44
|
+
m = meridional_arc(p, a, e2)
|
|
45
|
+
|
|
46
|
+
x = false_easting + n * (aa +
|
|
47
|
+
(1 - t + c) * aa**3 / 6 +
|
|
48
|
+
(5 - 18 * t + t * t + 72 * c - 58 * ep2) * aa**5 / 120)
|
|
49
|
+
y = false_northing + m + n * tan_p * (aa**2 / 2 +
|
|
50
|
+
(5 - t + 9 * c + 4 * c * c) * aa**4 / 24 +
|
|
51
|
+
(61 - 58 * t + t * t + 600 * c - 330 * ep2) * aa**6 / 720)
|
|
52
|
+
[x.round(4), y.round(4)]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# 高斯-克吕格反算: 平面坐标 -> 经纬度 [lng, lat]
|
|
56
|
+
# central_meridian 必填(反算无法从 x, y 推出投影带)
|
|
57
|
+
def inverse_gauss_kruger(x, y, central_meridian:, ellipsoid: :cgcs2000,
|
|
58
|
+
false_easting: 500_000, false_northing: 0)
|
|
59
|
+
a, f = ellipsoid_params(ellipsoid)
|
|
60
|
+
e2 = f * (2 - f)
|
|
61
|
+
ep2 = e2 / (1 - e2)
|
|
62
|
+
|
|
63
|
+
m = y - false_northing
|
|
64
|
+
mu = m / (a * (1 - e2 / 4 - 3 * e2**2 / 64 - 5 * e2**3 / 256))
|
|
65
|
+
e1 = (1 - Math.sqrt(1 - e2)) / (1 + Math.sqrt(1 - e2))
|
|
66
|
+
|
|
67
|
+
p1 = footpoint_latitude(mu, e1)
|
|
68
|
+
sin_p1 = Math.sin(p1)
|
|
69
|
+
cos_p1 = Math.cos(p1)
|
|
70
|
+
tan_p1 = Math.tan(p1)
|
|
71
|
+
|
|
72
|
+
c1 = ep2 * cos_p1 * cos_p1
|
|
73
|
+
t1 = tan_p1 * tan_p1
|
|
74
|
+
n1 = a / Math.sqrt(1 - e2 * sin_p1 * sin_p1)
|
|
75
|
+
r1 = a * (1 - e2) / (1 - e2 * sin_p1 * sin_p1)**1.5
|
|
76
|
+
d = (x - false_easting) / n1
|
|
77
|
+
|
|
78
|
+
p = p1 - (n1 * tan_p1 / r1) * (d**2 / 2 -
|
|
79
|
+
(5 + 3 * t1 + 10 * c1 - 4 * c1 * c1 - 9 * ep2) * d**4 / 24 +
|
|
80
|
+
(61 + 90 * t1 + 298 * c1 + 45 * t1 * t1 - 252 * ep2 - 3 * c1 * c1) * d**6 / 720)
|
|
81
|
+
l = (d -
|
|
82
|
+
(1 + 2 * t1 + c1) * d**3 / 6 +
|
|
83
|
+
(5 - 2 * c1 + 28 * t1 - 3 * c1 * c1 + 8 * ep2 + 24 * t1 * t1) * d**5 / 120) / cos_p1
|
|
84
|
+
|
|
85
|
+
[rad2deg(l + deg2rad(central_meridian)), rad2deg(p)].map { |v| v.round(9) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# 带号与中央经线: zone_meridian(121.47) => [21, 123], zone_meridian(121.47, width: 3) => [40, 120]
|
|
89
|
+
def zone_meridian(lng, width: 6)
|
|
90
|
+
raise ArgumentError, "带宽仅支持 3 或 6 度" unless [3, 6].include?(width)
|
|
91
|
+
|
|
92
|
+
zone = width == 6 ? (lng / 6).floor + 1 : (lng / 3).round
|
|
93
|
+
[zone, width == 6 ? zone * 6 - 3 : zone * 3]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def ellipsoid_params(name)
|
|
99
|
+
params = ELLIPSOIDS[name.to_s.downcase.to_sym]
|
|
100
|
+
raise ArgumentError, "未知椭球: #{name.inspect} (支持 #{ELLIPSOIDS.keys.join(', ')})" unless params
|
|
101
|
+
|
|
102
|
+
[params[0], 1.0 / params[1]]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# 子午线弧长
|
|
106
|
+
def meridional_arc(p, a, e2)
|
|
107
|
+
a * ((1 - e2 / 4 - 3 * e2**2 / 64 - 5 * e2**3 / 256) * p -
|
|
108
|
+
(3 * e2 / 8 + 3 * e2**2 / 32 + 45 * e2**3 / 1024) * Math.sin(2 * p) +
|
|
109
|
+
(15 * e2**2 / 256 + 45 * e2**3 / 1024) * Math.sin(4 * p) -
|
|
110
|
+
(35 * e2**3 / 3072) * Math.sin(6 * p))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# 底点纬度迭代
|
|
114
|
+
def footpoint_latitude(mu, e1)
|
|
115
|
+
mu + (3 * e1 / 2 - 27 * e1**3 / 32) * Math.sin(2 * mu) +
|
|
116
|
+
(21 * e1**2 / 16 - 55 * e1**4 / 32) * Math.sin(4 * mu) +
|
|
117
|
+
(151 * e1**3 / 96) * Math.sin(6 * mu) +
|
|
118
|
+
(1097 * e1**4 / 512) * Math.sin(8 * mu)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def deg2rad(deg)
|
|
122
|
+
deg * Math::PI / 180.0
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def rad2deg(rad)
|
|
126
|
+
rad * 180.0 / Math::PI
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Coordconver
|
|
7
|
+
module Utils
|
|
8
|
+
# 轻量 HTTP 客户端, 仅依赖标准库。
|
|
9
|
+
# 网络层失败(超时/连接错误/非 2xx)会自动重试, 重试耗尽后抛出 Coordconver::NetworkError。
|
|
10
|
+
class Http
|
|
11
|
+
DEFAULT_OPEN_TIMEOUT = 5
|
|
12
|
+
DEFAULT_READ_TIMEOUT = 10
|
|
13
|
+
DEFAULT_RETRY_TIMES = 3
|
|
14
|
+
RETRY_BASE_SLEEP = 0.5
|
|
15
|
+
|
|
16
|
+
HTTP_VERB_MAP = {
|
|
17
|
+
get: Net::HTTP::Get,
|
|
18
|
+
post: Net::HTTP::Post,
|
|
19
|
+
put: Net::HTTP::Put,
|
|
20
|
+
delete: Net::HTTP::Delete
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
DEFAULT_HEADERS = {
|
|
24
|
+
"user-agent" => "coordconver/#{Coordconver::VERSION} (+https://github.com/cheenwe/coordconver)",
|
|
25
|
+
"accept" => "application/json"
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
RETRYABLE_ERRORS = [
|
|
29
|
+
Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNRESET,
|
|
30
|
+
Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError, EOFError
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
def initialize(method, url, params: nil, body: nil, headers: {})
|
|
34
|
+
method = method.to_s.downcase.to_sym
|
|
35
|
+
raise ArgumentError, "不支持的 HTTP method: #{method}" unless HTTP_VERB_MAP.key?(method)
|
|
36
|
+
|
|
37
|
+
@uri = URI(url)
|
|
38
|
+
@uri.query = build_query(params) if params && !params.empty?
|
|
39
|
+
@method = method
|
|
40
|
+
@body = body
|
|
41
|
+
@headers = headers
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def send_request
|
|
45
|
+
attempts = 0
|
|
46
|
+
begin
|
|
47
|
+
attempts += 1
|
|
48
|
+
perform
|
|
49
|
+
rescue *RETRYABLE_ERRORS => e
|
|
50
|
+
raise NetworkError, "#{@uri.host} 请求失败: #{e.class}: #{e.message}" if attempts >= DEFAULT_RETRY_TIMES
|
|
51
|
+
|
|
52
|
+
sleep(RETRY_BASE_SLEEP * (2**(attempts - 1)))
|
|
53
|
+
retry
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def perform
|
|
60
|
+
opts = {
|
|
61
|
+
use_ssl: @uri.scheme == "https",
|
|
62
|
+
open_timeout: open_timeout,
|
|
63
|
+
read_timeout: read_timeout
|
|
64
|
+
}
|
|
65
|
+
Net::HTTP.start(@uri.host, @uri.port, **opts) do |http|
|
|
66
|
+
response = http.request(build_request)
|
|
67
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
68
|
+
raise NetworkError, "#{@uri.host} 返回 HTTP #{response.code}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
response
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def build_request
|
|
76
|
+
request = HTTP_VERB_MAP[@method].new(@uri)
|
|
77
|
+
request.initialize_http_header(DEFAULT_HEADERS.merge(@headers))
|
|
78
|
+
request.body = @body.to_json if @body
|
|
79
|
+
request
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def build_query(params)
|
|
83
|
+
URI.encode_www_form(params)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def open_timeout
|
|
87
|
+
configured = Coordconver::Geo::Configuration.configured? && Coordconver::Geo::Configuration.settings[:open_timeout]
|
|
88
|
+
configured || DEFAULT_OPEN_TIMEOUT
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def read_timeout
|
|
92
|
+
configured = Coordconver::Geo::Configuration.configured? && Coordconver::Geo::Configuration.settings[:read_timeout]
|
|
93
|
+
configured || DEFAULT_READ_TIMEOUT
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coordconver
|
|
4
|
+
module Utils
|
|
5
|
+
module Request
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
def get(url, params: nil, headers: {})
|
|
9
|
+
send_request(:get, url, params: params, headers: headers)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def post(url, body: nil, headers: {})
|
|
13
|
+
send_request(:post, url, body: body, headers: headers)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def put(url, body: nil, headers: {})
|
|
17
|
+
send_request(:put, url, body: body, headers: headers)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def delete(url, params: nil, headers: {})
|
|
21
|
+
send_request(:delete, url, params: params, headers: headers)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def send_request(method, url, params: nil, body: nil, headers: {})
|
|
25
|
+
raw_response = Http.new(
|
|
26
|
+
method.to_sym,
|
|
27
|
+
url,
|
|
28
|
+
params: params,
|
|
29
|
+
body: body,
|
|
30
|
+
headers: headers
|
|
31
|
+
).send_request
|
|
32
|
+
|
|
33
|
+
parse_body(raw_response.body)
|
|
34
|
+
rescue JSON::ParserError => e
|
|
35
|
+
raise NetworkError, "响应不是合法 JSON: #{e.message}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parse_body(body)
|
|
39
|
+
JSON.parse(body)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/coordconver/version.rb
CHANGED
data/lib/coordconver.rb
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "coordconver/version"
|
|
4
|
+
require "coordconver/errors"
|
|
2
5
|
require "coordconver/coordconver"
|
|
3
6
|
require "coordconver/degree"
|
|
7
|
+
require "coordconver/formats"
|
|
8
|
+
require "coordconver/projection"
|
|
9
|
+
require "coordconver/geo/configuration"
|
|
10
|
+
require "coordconver/geo/key_pool"
|
|
11
|
+
require "coordconver/geo/cache"
|
|
12
|
+
require "coordconver/geo/api"
|
|
13
|
+
require "coordconver/geo/client"
|
|
14
|
+
require "coordconver/geo/baidu"
|
|
15
|
+
require "coordconver/geo/gaode"
|
|
16
|
+
require "coordconver/geo/tencent"
|
|
17
|
+
require "coordconver/utils/http"
|
|
18
|
+
require "coordconver/utils/request"
|
|
4
19
|
|
|
5
20
|
module Coordconver
|
|
6
|
-
|
|
7
|
-
|
|
21
|
+
# 坐标系转换/距离/方位角等方法已直接定义在 Coordconver 单例上,
|
|
22
|
+
# 如 Coordconver.wgs_gcj(lng, lat) / Coordconver.distance(...) / Coordconver.bearing(...)
|
|
23
|
+
extend Degree
|
|
24
|
+
extend Formats
|
|
25
|
+
extend Projection
|
|
26
|
+
|
|
27
|
+
# 便捷常量别名, 兼容 Coordconver::Baidu / Coordconver::Gaode 等用法
|
|
28
|
+
Baidu = Geo::Baidu
|
|
29
|
+
Gaode = Geo::Gaode
|
|
30
|
+
Tencent = Geo::Tencent
|
|
31
|
+
Client = Geo::Client
|
|
32
|
+
Configuration = Geo::Configuration
|
|
8
33
|
end
|
metadata
CHANGED
|
@@ -1,43 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: coordconver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- chenwei
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-10-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: bundler
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - "~>"
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1.12'
|
|
20
|
-
type: :development
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - "~>"
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '1.12'
|
|
27
13
|
- !ruby/object:Gem::Dependency
|
|
28
14
|
name: rake
|
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
|
30
16
|
requirements:
|
|
31
|
-
- - "
|
|
17
|
+
- - ">="
|
|
32
18
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
19
|
+
version: '13.0'
|
|
34
20
|
type: :development
|
|
35
21
|
prerelease: false
|
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
23
|
requirements:
|
|
38
|
-
- - "
|
|
24
|
+
- - ">="
|
|
39
25
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
26
|
+
version: '13.0'
|
|
41
27
|
- !ruby/object:Gem::Dependency
|
|
42
28
|
name: minitest
|
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -52,33 +38,59 @@ dependencies:
|
|
|
52
38
|
- - "~>"
|
|
53
39
|
- !ruby/object:Gem::Version
|
|
54
40
|
version: '5.0'
|
|
55
|
-
description:
|
|
41
|
+
description: 纯离线实现百度坐标(BD-09)、国测局坐标(GCJ-02)、地球坐标(WGS-84)互转(含厘米级精确逆变换), 两点距离/方位角/偏移点,
|
|
42
|
+
经纬度与度分秒互转, WKT/GeoJSON 互转, 高斯-克吕格投影正反算; 并封装百度/高德/腾讯 Web 服务 API 实现地址与坐标互转, 支持多 key
|
|
43
|
+
自动轮询与进程内缓存。附带命令行工具。
|
|
56
44
|
email:
|
|
57
45
|
- cheenwe@gmail.com
|
|
58
|
-
executables:
|
|
46
|
+
executables:
|
|
47
|
+
- coordconver
|
|
59
48
|
extensions: []
|
|
60
49
|
extra_rdoc_files: []
|
|
61
50
|
files:
|
|
51
|
+
- ".github/workflows/ci.yml"
|
|
62
52
|
- ".gitignore"
|
|
63
|
-
-
|
|
53
|
+
- CHANGELOG.md
|
|
64
54
|
- CODE_OF_CONDUCT.md
|
|
65
55
|
- Gemfile
|
|
66
56
|
- LICENSE.txt
|
|
57
|
+
- README.en.md
|
|
67
58
|
- README.md
|
|
68
59
|
- Rakefile
|
|
69
60
|
- bin/console
|
|
70
|
-
- bin/coordconver
|
|
71
61
|
- bin/setup
|
|
72
62
|
- coordconver.gemspec
|
|
63
|
+
- docs/cc-algorithms.js
|
|
64
|
+
- docs/index.html
|
|
65
|
+
- exe/coordconver
|
|
73
66
|
- lib/coordconver.rb
|
|
74
67
|
- lib/coordconver/coordconver.rb
|
|
75
68
|
- lib/coordconver/degree.rb
|
|
69
|
+
- lib/coordconver/errors.rb
|
|
70
|
+
- lib/coordconver/formats.rb
|
|
71
|
+
- lib/coordconver/geo/api.rb
|
|
72
|
+
- lib/coordconver/geo/baidu.rb
|
|
73
|
+
- lib/coordconver/geo/cache.rb
|
|
74
|
+
- lib/coordconver/geo/client.rb
|
|
75
|
+
- lib/coordconver/geo/configuration.rb
|
|
76
|
+
- lib/coordconver/geo/gaode.rb
|
|
77
|
+
- lib/coordconver/geo/key_pool.rb
|
|
78
|
+
- lib/coordconver/geo/tencent.rb
|
|
79
|
+
- lib/coordconver/projection.rb
|
|
80
|
+
- lib/coordconver/utils/http.rb
|
|
81
|
+
- lib/coordconver/utils/request.rb
|
|
76
82
|
- lib/coordconver/version.rb
|
|
77
83
|
homepage: https://github.com/cheenwe/coordconver
|
|
78
84
|
licenses:
|
|
79
85
|
- MIT
|
|
80
|
-
metadata:
|
|
81
|
-
|
|
86
|
+
metadata:
|
|
87
|
+
allowed_push_host: https://rubygems.org
|
|
88
|
+
homepage_uri: https://github.com/cheenwe/coordconver
|
|
89
|
+
source_code_uri: https://github.com/cheenwe/coordconver
|
|
90
|
+
changelog_uri: https://github.com/cheenwe/coordconver/blob/master/CHANGELOG.md
|
|
91
|
+
bug_tracker_uri: https://github.com/cheenwe/coordconver/issues
|
|
92
|
+
rubygems_mfa_required: 'true'
|
|
93
|
+
post_install_message:
|
|
82
94
|
rdoc_options: []
|
|
83
95
|
require_paths:
|
|
84
96
|
- lib
|
|
@@ -86,16 +98,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
86
98
|
requirements:
|
|
87
99
|
- - ">="
|
|
88
100
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
101
|
+
version: '2.7'
|
|
90
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
103
|
requirements:
|
|
92
104
|
- - ">="
|
|
93
105
|
- !ruby/object:Gem::Version
|
|
94
106
|
version: '0'
|
|
95
107
|
requirements: []
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
signing_key:
|
|
108
|
+
rubygems_version: 3.1.6
|
|
109
|
+
signing_key:
|
|
99
110
|
specification_version: 4
|
|
100
|
-
summary:
|
|
111
|
+
summary: 百度(BD-09)/火星(GCJ-02)/地球(WGS-84)坐标系互转、球面几何、高斯-克吕格投影, 以及百度/高德/腾讯地理编码
|
|
101
112
|
test_files: []
|
data/.travis.yml
DELETED
data/bin/coordconver
DELETED