console
_GRID_SIZE_ = {
1: {
size: '256° x 256°'
},
2: {
size: '128° x 128°'
},
3: {
size: '64° x 64°'
},
4: {
size: '32° x 32°'
},
5: {
size: '16° x 16°'
},
6: {
size: '8° x 8°, ~1024km'
},
7: {
size: '4° x 4°, ~512km'
},
8: {
size: '2° x 2°, ~256km'
},
9: {
size: '1° x 1°, ~128km'
},
10: {
size: `32' x 32'`
},
11: {
size: `16' x 16'`
},
12: {
size: `8' x 8'`
},
13: {
size: `4' x 4'`
},
14: {
size: `2' x 2'`
},
15: {
size: `1' x 1', ~2km`
},
16: {
size: `32" x 32", ~1km`
},
17: {
size: '16" x 16"'
},
18: {
size: '8" x 8"'
},
19: {
size: '4" x 4"'
},
20: {
size: '2" x 2", ~64m'
},
21: {
size: '1" x 1", ~32m'
},
22: {
size: '1/2" x 1/2"'
},
23: {
size: '1/4" x 1/4"'
},
24: {
size: '1/8" x 1/8", ~ 4m'
},
25: {
size: '1/16" x 1/16"'
},
26: {
size: '1/32" x 1/32"'
},
27: {
size: '1/64" x 1/64"'
},
28: {
size: '1/128" x 1/128"'
},
29: {
size: '1/256" x 1/256"'
},
30: {
size: '1/512" x 1/512"'
},
31: {
size: '1/1024" x 1/1024"'
},
32: {
size: '1/2048" x 1/2048", 1.5cm'
}
}
class GridCell {
constructor(level, x, y) {
this.x = x;
this.y = y;
this.level = level;
}
toQuaternary1D() {
const formatter = (lngBit, latBit) => `${latBit * 2 + lngBit}`;
let s = this._format(9, 32, formatter);
s += '-';
s += this._format(6, 23, formatter);
s += '-';
s += this._format(6, 17, formatter);
s += '.';
s += this._format(11, 11, formatter);
return s;
}
toBinary1D() {
const formatter = (lngBit, latBit) => `${latBit}${lngBit}`;
let s = this._format(9, 32, formatter);
s += '-';
s += this._format(6, 23, formatter);
s += '-';
s += this._format(6, 17, formatter);
s += '.';
s += this._format(11, 11, formatter);
return s;
}
toBinary2D() {
const d1 = this.toBinary1D();
let lng = '';
let lat = '';
let pos = 0;
d1.split('').forEach((value, i) => {
if(value === '-' || value === '.') {
lng += value;
lat += value;
return;
}
if(pos % 2 === 0) {
lat += value;
} else {
lng += value;
}
pos++;
})
return `纬: ${lat}, 经: ${lng}`;
}
getNeighbours() {
return [
{x: this.x - 1, y: this.y - 1, pos: '西南'},
{x: this.x, y: this.y - 1, pos: '南侧'},
{x: this.x + 1, y: this.y - 1, pos: '东南'},
{x: this.x - 1, y: this.y, pos: '西侧'},
{x: this.x + 1, y: this.y, pos: '东侧'},
{x: this.x - 1, y: this.y + 1, pos: '西北'},
{x: this.x, y: this.y + 1, pos: '北侧'},
{x: this.x + 1, y: this.y + 1, pos: '东北'}
]
}
getLng() {
return this._decode(this.x)
}
getLat() {
return this._decode(this.y)
}
_decode(code) {
const code32 = code << (32 - this.level) >>> 0;
const degree = (code32 & 0xFF800000) >>> 23;
const minute = (code32 & 0x007E0000) >>> 17;
const second = (code32 & 0x0001F800) >>> 11;
const subSecond = code32 & 0x000007FF;
return `${degree}度 ${minute}分 ${second}.${subSecond}秒`;
}
_format(bits, from, formatter) {
let s = '';
for(let i = 1; i <= bits; i++) {
const latBit = ((this.y << (32 - this.level) >>> (from - i)) & 1);
const lngBit = ((this.x << (32 - this.level) >>> (from - i)) & 1);
s += formatter(lngBit, latBit);
}
return s;
}
}
class GeoSOT {
constructor(level) {
if(isNaN(level) || level> 32 || level < 1) {
throw('invalid grid level, it MUST be from 1 through 32!!!');
}
this.level = level;
}
encode(lng, lat) {
return new GridCell(this.level, this._encode(lng), this._encode(lat))
}
_encode(lnglat) {
const {degree, minute, second, subSecond} = this._decompose(lnglat);
return ((degree << 23) + (minute << 17) + (second << 11) + subSecond) >>> (32 - this.level);
}
_decompose(lnglat) {
const degree = Math.trunc(lnglat);
let tmp = (lnglat - degree) * 60;;
const minute = Math.trunc(tmp);
tmp = (tmp - minute) * 60;
const second = Math.trunc(tmp);
tmp = (tmp - second) * 10000;
const subSecond = Math.trunc(tmp);
return {
degree, minute, second, subSecond
}
}
}
async function getAddress(lng, lat) {
return fetch(`http://apbapi.test.ytcsc.cn:20055/api/region/location/address?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsiNCIsIjQ1MDAwMCJdLCJleHAiOjE2MjY0Nzc0MjcsImlhdCI6MTYyNjIxODIyN30.7K7b1D1nJCIbTFAm4wQVWMAGr2_Y-BpwyLhez1eVjjU&lat=${lat}&lon=${lng}`)
.then(value=>value.json())
.then(value => value.data)
}
function $(id) {
return document.getElementById(id);
}
async function run() {
const lng = $("lng").value * 1;
const lat = $("lat").value * 1;
const level = $("level").value * 1;
const geo = new GeoSOT(level);
let cell = geo.encode(lng, lat);
const address = await getAddress(lng, lat);
$("address").innerText = `${address}`;
$("corner-coord").innerText = `纬: ${cell.getLat()}, 经: ${cell.getLng()}`;
$("grid-size").innerText = _GRID_SIZE_[level].size;
$("quad-code").innerText = cell.toQuaternary1D();
$("binary-code1").innerText = `${cell.toBinary1D()}`;
$("binary-code2").innerText = `${cell.toBinary2D()}`;
$("decimal-code").innerText = `纬: ${cell.y}, 经: ${cell.x}, `;
$("decimal-fix-code").innerText = `纬: ${cell.y << (32 - level)}, 经: ${cell.x << (32 - level)}`;
$("sql").innerText = `global_grid_x in (${cell.x-1},${cell.x},${cell.x+1}) AND global_grid_y in (${cell.y-1},${cell.y},${cell.y+1})`;
const grids = cell.getNeighbours()
let table = "<table><thead><td>序号</td><td>位置</td><td>经度码</td><td>纬度码</td></thead>";
grids.forEach((grid, i) => {
table += `<tr><td>${i+1}</td><td>${grid.pos}</td><td>${grid.x}</td><td>${grid.y}</td></tr>`
})
table += "</table>"
$("neighbours").innerHTML = table;
}
run();
<html>
<body>
<h1>GeoSOT 网格位置码示例</h1>
<div class="param">
<input id="lng" placeholder="输入经度" value="108.362317">
<input id="lat" placeholder="输入纬度" value="22.825582">
<select id="level">
<option value="8">8层级网格码</option>
<option value="9">9层级网格码</option>
<option value="15">15层级网格码</option>
<option value="16" selected>16层级网格码</option>
<option value="20">20层级网格码</option>
<option value="24">24层级网格码</option>
<option value="32">32层级网格码</option>
</select>
<button onclick="run()">编码</button>
</div>
<hr />
<div class="result">
<div>
<span class="title">位置解析:</span>
<span id='address' class="value"></span>
</div>
<div>
<span class="title">定位角点坐标:</span>
<span id='corner-coord' class="value"></span>
</div>
<div>
<span class="title">网格大小:</span>
<span id='grid-size' class="value"></span>
</div>
<div>
<span class="title">四进制一维码:</span>
<span id='quad-code' class="value"></span>
</div>
<div>
<span class="title">二进制一维码:</span>
<span id='binary-code1' class="value"></span>
</div>
<div>
<span class="title">二进制二维码:</span>
<span id='binary-code2' class="value"></span>
</div>
<div>
<span class="title">十进制二维码(变长):</span>
<span id='decimal-code' class="value"></span>
</div>
<div>
<span class="title">十进制二维码(定长):</span>
<span id='decimal-fix-code' class="value"></span>
</div>
<div style='display: flex'>
<span class="title">SQL相邻查询:</span>
<span id='sql' class="value"></span>
</div>
<div style='display: flex'>
<span class="title">相邻网格:</span>
<span id='neighbours' class="value"></span>
</div>
</div>
<div class='copyright'>北京中科锐景科技有限公司(天津研发)</div>
</body>
</html>
body {
margin: 20px;
}
table {
display: inline-block;
width: 360px;
}
td {
width: 100px;
border-bottom: 1px solid #666;
}
.result {
margin-top: 20px;
}
.result div {
height: 36px;
}
.title {
display: inline-flex;
width: 180px;
justify-content: flex-end;
}
.value {
margin-left: 20px;
}
.copyright {
position: fixed;
bottom: 10px;
left: 0px;
right: 0px;
text-align: center;
font-size: 12px;
font-family: PingFangSC;
color: #999;
}