-
Notifications
You must be signed in to change notification settings - Fork 23
/
PRCoords.js
225 lines (193 loc) · 7.08 KB
/
PRCoords.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
"use strict"
// <nowiki>
/**
* People's Rectified [[T:Coord|Coordinates]]
* @file Utils for inserting valid WGS-84 coords from GCJ-02/BD-09 input
* @author User:Artoria2e5
* @url https://github.com/Artoria2e5/PRCoords
*
* @see [[:en:GCJ-02]]
* @see https://github.com/caijun/geoChina (GPLv3)
* @see https://github.com/googollee/eviltransform (MIT)
* @see https://on4wp7.codeplex.com/SourceControl/changeset/view/21483#353936 (Anonymous)
* @see https://github.com/zxteloiv/pycoordtrans (BSD-3)
*
* @license CC0
* To the greatest extent possible, this implementation of obfuscations designed
* in hope that they will screw y'all up is dedicated into the public domain
* under CC0 1.0 <https://creativecommons.org/publicdomain/zero/1.0/>.
*
* Happy geotagging/ingressing/whatever.
*
* To make my FSF membership shine brighter, this conversion implementation is
* additionally licensed under GPLv3+:
* @license GPLv3+
* @copyright 2016 Mingye Wang (User:Artoria2e5)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/// Krasovsky 1940 ellipsoid
/// @const
var GCJ_A = 6378245
var GCJ_EE = 0.00669342162296594323 // f = 1/298.3; e^2 = 2*f - f**2
/// Epsilon to use for "exact" iterations.
/// Wanna troll? Use Number.EPSILON. 1e-13 in 15 calls for gcj.
/// @const
var PRC_EPS = 1e-5
/// Baidu's artificial deviations
/// @const
var BD_DLAT = 0.0060
var BD_DLON = 0.0065
/// Mean Earth Radius
/// @const
var EARTH_R = 6371000
/// Distance for haversine method; suitable over short distances like
/// conversion deviation checking
function distance(a, b) {
function hav(θ) {
return Math.pow(Math.sin(θ/2), 2)
}
var Δ = _coord_diff(a, b)
return 2 * EARTH_R * Math.asin(Math.sqrt(
hav(Δ.lat * Math.PI / 180) +
Math.cos(a.lat * Math.PI / 180) *
Math.cos(b.lat * Math.PI / 180) *
hav(Δ.lon * Math.PI / 180)
))
}
function sanity_in_china_p(coords) {
return coords.lat >= 0.8293 && coords.lat <= 55.8271 &&
coords.lon >= 72.004 && coords.lon <= 137.8347
}
function _coord_diff(a, b) {
return {
lat: a.lat - b.lat,
lon: a.lon - b.lon,
}
}
function _coord_error(a, b) {
var diff = _coord_diff(a, b)
return Math.max(Math.abs(diff.lat), Math.abs(diff.lon))
}
function wgs_gcj(wgs, checkChina = true) {
if (checkChina && !sanity_in_china_p(wgs)) {
console.warn(`Non-Chinese coords found, returning as-is: ` +
`(${wgs.lat}, ${wgs.lon})`)
return wgs
}
var x = wgs.lon - 105, y = wgs.lat - 35
// These distortion functions accept (x = lon - 105, y = lat - 35).
// They return distortions in terms of arc lengths, in meters.
//
// In other words, you can pretty much figure out how much you will be off
// from WGS-84 just through evaulating them...
//
// For example, at the (mapped) center of China (105E, 35N), you get a
// default deviation of <300, -100> meters.
var dLat_m = -100 + 2 * x + 3 * y + 0.2 * y * y + 0.1 * x * y +
0.2 * Math.sqrt(Math.abs(x)) + (
2 * Math.sin(x * 6 * Math.PI) + 2 * Math.sin(x * 2 * Math.PI) +
2 * Math.sin(y * Math.PI) + 4 * Math.sin(y / 3 * Math.PI) +
16 * Math.sin(y / 12 * Math.PI) + 32 * Math.sin(y / 30 * Math.PI)
) * 20 / 3
var dLon_m = 300 + x + 2 * y + 0.1 * x * x + 0.1 * x * y +
0.1 * Math.sqrt(Math.abs(x)) + (
2 * Math.sin(x * 6 * Math.PI) + 2 * Math.sin(x * 2 * Math.PI) +
2 * Math.sin(x * Math.PI) + 4 * Math.sin(x / 3 * Math.PI) +
15 * Math.sin(x / 12 * Math.PI) + 30 * Math.sin(x / 30 * Math.PI)
) * 20 / 3
var radLat = wgs.lat / 180 * Math.PI
var magic = 1 - GCJ_EE * Math.pow(Math.sin(radLat), 2) // just a common expr
// [[:en:Latitude#Length_of_a_degree_of_latitude]]
var lat_deg_arclen = (Math.PI / 180) * (GCJ_A * (1 - GCJ_EE)) * Math.pow(magic, 1.5)
// [[:en:Longitude#Length_of_a_degree_of_longitude]]
var lon_deg_arclen = (Math.PI / 180) * (GCJ_A * Math.cos(radLat) / Math.sqrt(magic))
// The screwers pack their deviations into degrees and disappear.
// Note how they are mixing WGS-84 and Krasovsky 1940 ellipsoids here...
return {
lat: wgs.lat + (dLat_m / lat_deg_arclen),
lon: wgs.lon + (dLon_m / lon_deg_arclen),
}
}
// rev_transform_rough; accuracy ~2e-6 deg (meter-level)
function gcj_wgs(gcj, checkChina = true) {
return _coord_diff(gcj, _coord_diff(wgs_gcj(gcj, checkChina), gcj))
}
function gcj_bd(gcj, __dummy__ = false) {
var x = gcj.lon
var y = gcj.lat
// trivia: pycoordtrans actually describes how these values are calculated
var r = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * Math.PI * 3000 / 180)
var θ = Math.atan2(y, x) + 0.000003 * Math.cos(x * Math.PI * 3000 / 180)
// Hard-coded default deviations again!
return {
lat: r * Math.sin(θ) + BD_DLAT,
lon: r * Math.cos(θ) + BD_DLON,
}
}
// Yes, we can implement a "precise" one too.
// accuracy ~1e-7 deg (decimeter-level; exceeds usual data accuracy)
function bd_gcj(bd, __dummy__ = false) {
var x = bd.lon - BD_DLON
var y = bd.lat - BD_DLAT
// trivia: pycoordtrans actually describes how these values are calculated
var r = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * Math.PI * 3000 / 180)
var θ = Math.atan2(y, x) - 0.000003 * Math.cos(x * Math.PI * 3000 / 180)
return {
lat: r * Math.sin(θ),
lon: r * Math.cos(θ),
}
}
function bd_wgs(bd, checkChina = true) {
return gcj_wgs(bd_gcj(bd), checkChina)
}
function wgs_bd(bd, checkChina = true) {
return gcj_bd(wgs_gcj(bd, checkChina))
}
// generic "bored function" factory, Caijun 2014
// gcj: 4 calls to wgs_gcj; ~0.1mm acc
function __bored__(fwd, rev) {
return function rev_bored(bad, checkChina = true) {
var wgs = rev(bad, checkChina)
var old = bad
// Wait till we hit fixed point or get bored
for (var i = 0; i < 10 && _coord_error(wgs, old) > PRC_EPS; i++) {
old = wgs
wgs = _coord_diff(wgs, _coord_diff(fwd(wgs, false), bad))
}
return wgs
}
}
var exports = {
distance: distance,
wgs_gcj: wgs_gcj,
gcj_wgs: gcj_wgs,
gcj_bd: gcj_bd,
bd_gcj: bd_gcj,
wgs_bd: wgs_bd,
bd_wgs: bd_wgs,
// Precise functions using caijun 2014 method
//
// Why "bored"? Because they usually exceed source data accuracy -- the
// original GCJ implementation contains noise from a linear-modulo PRNG,
// and Baidu seems to do similar things with their API too.
__bored__: __bored__,
gcj_wgs_bored: __bored__(wgs_gcj, gcj_wgs),
bd_gcj_bored: __bored__(gcj_bd, bd_gcj),
bd_wgs_bored: __bored__(wgs_bd, bd_wgs),
}
if (typeof module === "object" && exports) {
module.exports = exports
} else if (typeof window !== "undefined") {
window.PRCoords = exports
}