<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta name="author" content="Mingye Wang">
<meta name="description" content="A Public Domain GCJ-02/BD-09 to WGS-84 Deobfscator (not eviltransform, but PRCoords)">
<link href="https://upload.wikimedia.org/wikipedia/commons/a/ac/Globe%2C_distorted_China.svg" type="image/svg+xml" rel="icon">
<title>People Rectify Coordinates</title>
</head>
<body onload="from_query()">
<h1>People Rectify Coordinates</h1>
<div style="margin-top: -1em"><small>With <a href="https://en.wikipedia.org/wiki/GCJ-02">restrictions</a> from the People’s Republic of China</small></div>
<a href="https://commons.wikimedia.org/wiki/File:Globe,_distorted_China.svg">
<img class="logo" src="https://upload.wikimedia.org/wikipedia/commons/a/ac/Globe%2C_distorted_China.svg" width="100" height="100" alt="logo: China? Where?"></a>
<article>
<article><section>
<h2>Input</h2>
<form id="inputc" onsubmit="return false"><!-- also make edge happy -->
<ul>
<li><label>Degrees:</label><ul id="deg-in">
<li><label>lat=</label> <input type="number" class="dnum" name="lat" value="35" step="any" min="-90" max="90">;
<li><label>lon=</label> <input type="number" class="dnum" name="lon" value="105" step="any" min="-180" max="180">.</ul>
<li><label>Or dms:</label><ul id="dms-in">
<li><label>lat=</label>
<input type="number" name="dlat" min="0" max="90" step="1" value="35">°
<input type="number" name="mlat" min="0" max="59" step="1" value="0">′
<input type="number" name="slat" min="0" max="60" value="0" step=any>″
<select name="hlat"><option>N</option><option>S</option></select>;
<li><label>lon=</label>
<input type="number" name="dlon" min="0" max="180" step="1" value="105">°
<input type="number" name="mlon" min="0" max="59" step="1" value="0">′
<input type="number" name="slon" min="0" max="69" value="0" step=any>″
<select name="hlon"><option>E</option><option>W</option></select>.</ul></ul>
<button onclick="return fill_output() && false">Screw that!</button>
</form>
<script src="https://cdn.rawgit.com/Artoria2e5/PRCoords/1ea97f2/js/PRCoords.js"></script>
<h2>Results</h2>
<table id="output">
<tr>
<th>Operation
<th>Result
<th title="How wrong have I been?">ΔObfs/m
<th title="How precise is this operation?">ΔRoundtrip/m</tr>
<tr id="egcj"><td>WGS → GCJ<td><td><td>
<tr id="ebd"><td>WGS → BD<td><td><td>
<tr id="dgcj"><td>GCJ → WGS<td><td><td>
<tr id="dbd"><td>BD → WGS<td><td><td>
<tr id="cgcj"><td>GCJ →<sup>cai</sup> WGS<td><td><td>
<tr id="cbd"><td>BD →<sup>cai</sup> WGS<td><td><td>
<tr id="bbd"><td>GCJ → BD<td><td><td>
<tr id="bgcj"><td>BD → GCJ<td><td><td>
<tr id="bcgcj"><td>BD →<sup>cai</sup> GCJ<td><td><td>
</table>
<p><a id="permalink" href="http://output.jsbin.com/zonafut">Permalink to this result</a>. Toggle sections: <a href="javascript:toggle('notes')">Notes</a>, <a href="javascript:toggle('faq')">FAQ</a>, <a href="javascript:toggle('footer')">footer</a>.
</section></article>
<section id="notes">
<h2>Notes</h2>
<ol>
<li>Caijun’s iterative method is included for precise decoding.
It's most useful for bored folks whose GPS data is pretty accurate
and GCJ-02 obfuscation not tainted by the original
<abbr title="linear congruential pseudo-random number generator">LCPRNG</abbr>.
<p>If you are doing Wikipedia or any kind of archival work, use it to avoid introducing extra error.
<li>BD is defined in terms of GCJ, hence the last three functions.
<li>This demo omits the “in China” sanity check. Data regarding
Baidu’s behavior with overseas maps is needed for further
decisions. Observations:<ul>
<li>Unlike Google Maps, Baidu's map in Hong Kong is fully subject to
BD-09 ∘ GCJ-02 chained distortions.
<li>Coordinates in Russia, outside of the sanity check rectangle, uses WGS-84 or and/or friends.
<li>TODO: check along the boundary.
</ol>
</section>
<section id="faq">
<h2>FAQ</h2>
<dl>
<dt id="what">What is this all about?</dt>
<dd>The PRC government requires all local map services to use an
obfuscated, <a href="https://github.com/Artoria2e5/PRCoords/blob/1ea97f2/js/PRCoords.js#L102-L109">
deviation-orienated</a> coordinate system.
Click on the “restriction” link to read the full Wikipedia article.</dd>
<dt id="why">Why should I care?</dt>
<dd>With half a kilometer of deviation, GCJ-02 and friends fucks up your
<a href="https://github.com/iitc-project/ingress-intel-total-conversion/blob/75a517b/plugins/fix-googlemap-china-offset.user.js">Ingress games</a>,
causes <a href="https://www.zhihu.com/question/29806566/answer/46099380">crazy errors</a>
in elevation profiles along cycle routes, and cheerfully leads you
into roadside ditches plus a bone fracture.</dd>
<dt id="google">Why doesn’t Google/Bing correct its Chinese data served to global users?</dt>
<dd><a href="https://productforums.google.com/forum/#!topic/maps/NunCUpRwLA0">I don’t know</a>.
Perhaps they are afraid of getting fined or further kicked out of China.
Maybe try <a href="https://openstreetmap.org">OpenStreetMap</a> next time?</dd>
<dt id="wheel">Why are you writing another implementation?</dt>
<dd><a href="https://github.com/Artoria2e5/PRCoords#why-another-wheel">Because I got bored</a>.</dd>
<dt id="caijun">How does Caijun’s iterative method work?</dt>
<dd>Cai has explained the method in full in his
<a href="https://github.com/caijun/geoChina/blob/5c6284b/R/cst.R#L101-L107"> R implementation</a>. Go read it, or read Wikipedia.
</section>
</article>
<footer id="footer">
<hr>
Powered by <a href="https://github.com/Artoria2e5/PRCoords">PRCoords</a>. Try playing with <code>window.PRCoords</code> in your console!
<p xmlns:dct="http://purl.org/dc/terms/" xmlns:vcard="http://www.w3.org/2001/vcard-rdf/3.0#">
<a rel="license"
href="http://creativecommons.org/publicdomain/zero/1.0/">
<img src="https://licensebuttons.net/p/zero/1.0/88x31.png" style="border-style: none;" alt="CC0"></a>
<br>
To the extent possible under law,
<a rel="dct:publisher"
href="https://zh.wikipedia.org/wiki/User:Artoria2e5">
<span property="dct:title">Mingye Wang</span></a>
has waived all copyright and related or neighboring rights to
<span property="dct:title">People Rectify Coordinates</span>.
This work is published from:
<span property="vcard:Country" datatype="dct:ISO3166"
content="US" about="http://jsbin.com/zonafut">
United States</span>.</p>
</footer>
</body>
</html>
body {
font-family: sans-serif;
margin: 0 auto;
max-width: 50em;
padding: 0 1em;
}
@media print {
body {
max-width: none;
padding: 0;
}
}
h1, h2, h3, h4, h5, h6 {
font-family: serif;
}
dt {
font-weight: bold;
}
h1, h2, h3 {
border-bottom: 1px solid #a2a9b1;
}
input[type='number'].dnum {
appearance:textfield;
}
input[type='number'].dnum::outer-spin-button,
input[type='number'].dnum::inner-spin-button {
appearance: none;
}
input[type='number'] {
width: 4em;
}
input[type='number'][name^=s] {
width: 6em
}
#deg-in input[type='number'] {
width: 8em
}
#output > table {
width: 100%
}
img.logo {
position: absolute;
top: 1em;
right: 1em;
border: none;
}
footer {
font-size: smaller;
}
table {
border-collapse: collapse;
}
td {
border: thin solid #a2a9b1;
padding: 0.2em 0.4em;
}
tbody td:nth-child(n+2) {
font-family: monospace;
}
#egcj td:nth-child(1), #ebd td:nth-child(1), #bbd td:nth-child(1) {
background: LightSalmon;
}
#dgcj td:nth-child(1), #dbd td:nth-child(1), #bgcj td:nth-child(1) {
background: PaleGreen;
}
#cgcj td:nth-child(1), #cbd td:nth-child(1), #bcgcj td:nth-child(1) {
background: Aquamarine;
}
"use strict";
// dms/deg
var coordInfo = {
'N': [+1, 'lat'],
'S': [-1, 'lat'],
'E': [+1, 'lon'],
'W': [-1, 'lon'],
}
var coordBack = {
'+lat': 'N',
'-lat': 'S',
'+lon': 'E',
'-lon': 'W',
}
function dmsToDec(d, m, s, hemisphere = 'N', type = '') {
if (type !== '') {
if (coordInfo[hemisphere][1] !== type) {
throw new RangeError('' + hemisphere + ' ' + type)
}
}
return coordInfo[hemisphere][0] * Math.round(((+d) + (+m)/60 + (+s)/3600)*1e8)/1e8
}
function decToDms(dec, type) {
var sign = (+dec) >= 0 ? 1 : -1
var hemi = coordBack[(sign == -1 ? '-' : '+') + type]
dec *= sign
var d = Math.floor(dec)
var m = Math.floor((dec-d)*60)
var s = Math.round((dec-d-m/60)*3600*1e4)/1e4
return [d, m, s, hemi]
}
// hooks
var inputs = document.getElementById('inputc')
function updFromDeg (ev) {
if (ev.target.value === '')
return;
var type = ev.target.name
var dmsh = [
inputs['d'+type],
inputs['m'+type],
inputs['s'+type],
inputs['h'+type],
]
;[
dmsh[0].value,
dmsh[1].value,
dmsh[2].value,
dmsh[3].value,
] = decToDms(+ev.target.value, type)
}
function updFromDms (ev) {
if (ev.target.value === '')
return;
var type = ev.target.name.substring(1)
inputs[type].value = dmsToDec(
inputs['d'+type].value,
inputs['m'+type].value,
inputs['s'+type].value,
inputs['h'+type].value,
type
)
}; // <- happy edge
// note: make edge happy:
// * make an array for a iterator in Edge
// * Don't use spread or Edge freaks out
for (let i of Array.from(inputs.querySelectorAll('#deg-in input')))
i.onchange = updFromDeg
for (let i of Array.from(inputs.querySelectorAll('#dms-in input, #dms-in select')))
i.onchange = updFromDms
// handler..
var a_perm = document.getElementById('permalink')
var baseurl = a_perm.href
function fill_output() {
// Edge shit
a_perm = a_perm || document.getElementById('permalink')
baseurl = baseurl || a_perm.href
// end Edge
var get_inverse = function (fname) {
var comp = fname.split('_')
return comp[1] + '_' + comp[0]/* +
(bored &&
comp[0] !== 'wgs' &&
!(comp[0] === 'gcj' && comp[1] === 'bd')) ?
'_bored' : ''*/
}
var fnames = {
dgcj: 'gcj_wgs',
dbd: 'bd_wgs',
egcj: 'wgs_gcj',
ebd: 'wgs_bd',
cgcj: 'gcj_wgs_bored',
cbd: 'bd_wgs_bored',
bgcj: 'bd_gcj',
bbd: 'gcj_bd',
bcgcj: 'bd_gcj_bored',
}
var incoords = {
lat: +inputs.lat.value,
lon: +inputs.lon.value,
}
console.log(incoords)
var coordToHtml = function(c) {
var lat = c.lat.toFixed(8)
var lon = c.lon.toFixed(8)
var ret = '(' + lat + ', ' + lon + ')<br/>'
var dms = decToDms(lat, 'lat')
ret += dms[0] + '°' + dms[1] + '′' + dms[2] + '″ ' + dms[3] + ', '
dms = decToDms(lon, 'lon')
return ret + dms[0] + '°' + dms[1] + '′' + dms[2] + '″ ' + dms[3]
}
for (var i in fnames) {
var row = document.getElementById(i)
var fun = PRCoords[fnames[i]]
var inv = PRCoords[get_inverse(fnames[i])]
var out = Array.from(row.childNodes).slice(1)
var res = fun(incoords, false)
var dObfs = PRCoords.distance(res, incoords)
var dRoundtrip = PRCoords.distance(inv(res, false), incoords)
out[0].innerHTML = coordToHtml(res)
out[1].innerText = dObfs.toExponential()
out[2].innerText = dRoundtrip.toExponential()
}
a_perm.href = baseurl + '?lat=' + incoords.lat + '&lon=' + incoords.lon // + '#output'
return false
}
/// *** polyfill handling *** ///
// https://philipwalton.com/articles/loading-polyfills-only-when-needed/
function loadScript(src, done) {
var js = document.createElement('script')
js.src = src
js.onload = function() {
done()
}
js.onerror = function() {
done(new Error('Failed to load script ' + src))
}
document.head.appendChild(js)
}
/// ^^^ polyfill handling ^^^ ///
function __from_query() {
function qe(s, d) {
return s ? s : d
}
try {
let params = new URLSearchParams(location.search.slice(1))
inputs.lat.value = qe(params.get('lat'), inputs.lat.value)
inputs.lon.value = qe(params.get('lon'), inputs.lon.value)
// trigger a dms update
var event = new Event("change")
for (let i of Array.from(inputs.querySelectorAll('#deg-in input')))
i.dispatchEvent(event)
}
catch (e) {
console.error(e)
}
return fill_output()
}
function from_query() {
if (typeof URLSearchParams !== 'undefined') {
return __from_query()
} else {
// https://github.com/WebReflection/url-search-params
loadScript("https://cdn.rawgit.com/WebReflection/url-search-params/774ee42/build/url-search-params.js", __from_query)
}
}
function toggle(id) {
var el = document.getElementById(id)
if (el.style.display !== 'none')
el.style.display = 'none'
else
el.style.display = ''
}
console.log('syntax looks right')
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |