leaflet 叠加新标准分幅网格
简述
这些天在一些项目中遇到一些与标准分幅有关的事情。
大概就是这么一回事,遇到很多次需要获取某点或者某些区域属于哪个标准分幅。虽然可以计算,但是算起来比较麻烦,不如直接地图上看方便。但是我手头没有标准分幅边框的矢量,网上有的也需要下载,于是自己用 javascript
写了一个根据地理范围和比例尺生成范围内标准分幅边框的 geojson
矢量的实现,然后在 leaflet
上加载显示出来(要改在 openlayers
之类的也很容易),根据不同的缩放级别显示不同比例尺的标准分幅网格。
生成标准分幅边框矢量的代码
// 获取小数部分
const fractional = function(x) {
x = Math.abs(x);
return x - Math.floor(x);
}
const formatInt = function(x, len) {
let result = '' + x;
len = len - result.length;
while(len > 0) {
result = '0'+result;
len--;
}
return result;
}
// 创建标准分幅网格
// west,south,east,north 传入要创建的标准分幅网格的经纬度范围
// scalem 表示比例尺的分母(例如 10000 表示 1:1万)
// 返回一个 geojson 对象
// solym ymwh@foxmail.com 2020年12月13日
function makeStandardMapGrids(west,south,east,north, scalem) {
let lngDiff = 0;
let latDiff = 0;
let scaleCode = '';
switch (scalem) {
case 1000000:
lngDiff = 6;
latDiff = 4;
break;
case 500000:
lngDiff = 3;
latDiff = 2;
scaleCode = 'B';
break;
case 250000:
lngDiff = 1.5;
latDiff = 1;
scaleCode = 'C';
break;
case 100000:
lngDiff = 0.5;
latDiff = 1 / 3;
scaleCode = 'D';
break;
case 50000:
lngDiff = 0.25;
latDiff = 1 / 6;
scaleCode = 'E';
break;
case 25000:
lngDiff = 0.125;
latDiff = 1 / 12;
scaleCode = 'F';
break;
case 10000:
lngDiff = 0.0625;
latDiff = 1 / 24;
scaleCode = 'G';
break;
case 5000:
lngDiff = 0.03125;
latDiff = 1 / 48;
scaleCode = 'H';
break;
default:
return null;
}
const GridX0 = -180;
const GridX1 = 180;
const GridY0 = -88;
const GridY1 = 88;
let x0 = Math.max(GridX0, west);
let y0 = Math.max(GridY0, south);
let x1 = Math.min(GridX1, east);
let y1 = Math.min(GridY1, north);
if (((x1 - x0) < lngDiff) || ((y1 - y0) < latDiff)) {
return null;
}
let features = [];
// 计算标准分幅网格行列范围
const col0 = parseInt((x0 - GridX0) / lngDiff);
const col1 = parseInt((x1 - GridX0) / lngDiff);
const row0 = parseInt((y0 - GridY0) / latDiff);
const row1 = parseInt((y1 - GridY0) / latDiff);
const millionRowCode = 'ABCDEFGHIJKLMNOPQRSTUV';
for (let row = row0; row <= row1; row++) {
let gy0 = GridY0 + row * latDiff;
let gy1 = gy0 + latDiff;
let gcy = (gy0+gy1)*0.5; // 分幅中心点 y 坐标
let millionRow = parseInt((gy0 - 0)/4); // 1:100分幅行号
let Hemisphere = ''; // 南北半球标志
if(millionRow < 0){
millionRow = -1-millionRow;
Hemisphere = 'S';
}
for (let col = col0; col <= col1; col++) {
let gx0 = GridX0 + col * lngDiff;
let gx1 = gx0 + lngDiff;
let gcx = (gx0+gx1)*0.5; // 分幅中心点 x 坐标
let millionCol = parseInt((gcx-GridX0)/6)+1; // 1:100分幅列号(从1开始)
coordinates = [[
[gx0, gy0],
[gx1, gy0],
[gx1, gy1],
[gx0, gy1],
[gx0, gy0]
]];
millionCol = (millionCol<10)?('0'+millionCol):millionCol;
let gridID = Hemisphere + millionRowCode[millionRow] + millionCol;
if(scaleCode != '') {
// 计算当前分幅在 1:100万 分幅内的行列号
// 注意,这里行列号从左向右,从北向南,从1开始编号
let colID = parseInt((fractional((gcx -GridX0)/6)*6)/lngDiff) + 1;
let rowID = parseInt((fractional((GridY1 - gcy)/4)*4)/latDiff) + 1;
gridID += scaleCode + formatInt(rowID,3) + formatInt(colID,3);
}
let feature = {
type: "Feature",
geometry: {
type: "Polygon",
coordinates: coordinates
},
properties: {
ID: gridID,
fanwei:'西:'+gx0+' 东:' + gx1 + ' 南:'+gy0+' 北:'+gy1
}
};
features.push(feature);
}
}
return {
type: "FeatureCollection",
features: features
};
}
leaflet 上加载显示代码
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>标准分幅测试页面</title>
<style>
html,
body {
height: 100%;
width: 100%;
overflow: hidden;
margin: 0;
padding: 0;
}
#map {
position: relative;
width: 100%;
height: 100%;
background-color: dimgrey;
}
#nav{
position: fixed;
top: 5px;
left: 35%;
z-index:9999;
color:lightgreen;
background-color: rgba(255, 182, 193, 0.384);
}
</style>
<!-- 1.0.3-->
<script type='text/javascript' src='leaflet_1.7.1.js'></script>
<link type='text/css' rel='stylesheet' href='leaflet_1.7.1.css' />
</head>
<body>
<div id="nav"></div>
<table>
<tr>
<div id="map"></div>
</tr>
</table>
<script>
var map = L.map('map', {
crs: L.CRS.EPSG4326,
minZoom: 1,
maxZoom: 25,
maxBounds: L.latLngBounds([
[-90, -180],
[90, 180]
]),
attributionControl: false,
keyboard: false,
});
map.setView([32.9, 105.4], 4);
var tianditu = L.tileLayer(
"http://t1.tianditu.com/img_c/wmts?layer=img&style=default&tilematrixset=c&Service=WMTS&Request=GetTile&Version=1.0.0&Format=tiles&TileMatrix={z}&TileCol={x}&TileRow={y}&tk=93724b915d1898d946ca7dc7b765dda5", {
maxZoom: 17,
tileSize: 256,
zoomOffset: 1
})
map.addLayer(tianditu);
var gridLayer = null;
map.on('moveend', function () {
let bounds = map.getBounds();
document.getElementById('nav').innerText = bounds.toBBoxString();
if (gridLayer != null) {
gridLayer.remove();
gridLayer = null;
}
const zoom = map.getZoom();
let scale = 1000000;
if (zoom > 11) {
scale = 5000;
} else if (zoom > 10) {
scale = 10000;
} else if (zoom > 9) {
scale = 25000;
} else if (zoom > 8) {
scale = 50000;
} else if (zoom > 7) {
scale = 100000;
} else if (zoom > 6) {
scale = 250000;
} else if (zoom > 4) {
scale = 500000;
}
console.log('zoom=', zoom, ' scale=', scale);
let grids = makeStandardMapGrids(bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth(), scale);
if (grids == null) {
return;
}
// console.log(JSON.stringify(grids));
gridLayer = L.geoJSON(grids, {
style: function (feature) {
return {
color: 'red',
weight:1,
fillColor:'green',
fillOpacity:0
};
}
}).bindPopup(function (layer) {
return layer.feature.properties.ID + '<br>' + layer.feature.properties.fanwei;
});
gridLayer.addTo(map);
});
map.panTo([32, 116])
</script>
</body>
</html>
效果截图