SOURCE

console 命令行工具 X clear

                    
>
console
/**
 * !!! 中科锐景
 * !!! GJB 8896-2017 GeoSOT 空间剖分网格位置码
 */


_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;
}