mapbox画瓦片网格
2023-05-10 本文已影响0人
姜治宇
经纬度转瓦片数字
瓦片栅格,就是我们在地图上打格子,比如zoom为0时是顶级,你画几个格子,然后标上行列号,比如0-0,0-1,然后随着zoom的不断变化,格子也不断裂变,行列号也不断变大。
那么,我们在地图上画网格,如何跟瓦片栅格的数字一致呢?
瓦片栅格数字,跟经纬度有个换算关系:
import mapboxgl from 'mapbox-gl';
import * as turf from '@turf/turf';
...
//经纬度转瓦片序列号
lngLat2Tile(coordinates, zoom) {
const { x, y } = mapboxgl.MercatorCoordinate.fromLngLat({
lng: coordinates[0],
lat: coordinates[1],
});
const scale = Math.pow(2, zoom);
const tileX = Math.floor(x * scale);
const tileY = Math.floor(y * scale);
return { x: tileX, y: tileY, z: zoom };
}
瓦片数字转bbox
那么知道了当前瓦片的数字,如何反推经纬度呢?实际上,一个瓦片对应的是一个格子,那么,我们要得到的,其实是一个边界信息。
var d2r = Math.PI / 180,
r2d = 180 / Math.PI;
/**
* Get the bbox of a tile
*
* @name tileToBBOX
* @param {Array<number>} tile
* @returns {Array<number>} bbox
* @example
* var bbox = tileToBBOX([5, 10, 10])
* //=bbox
*/
function tileToBBOX(tile) {
var e = tile2lon(tile[0] + 1, tile[2]);
var w = tile2lon(tile[0], tile[2]);
var s = tile2lat(tile[1] + 1, tile[2]);
var n = tile2lat(tile[1], tile[2]);
return [w, s, e, n];
}
function tile2lon(x, z) {
return (x / Math.pow(2, z)) * 360 - 180;
}
function tile2lat(y, z) {
var n = Math.PI - (2 * Math.PI * y) / Math.pow(2, z);
return r2d * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
}
export default {
tileToBBOX: tileToBBOX,
};
拿到了这个边界,我们可以借助turf库,拿到格子的坐标信息,这样就可以画出这个格子了。
import mapboxgl from 'mapbox-gl';
import * as turf from '@turf/turf';
import tilebelt from '../common/tilebelt';
...
initGrid() {
const key = 'draw-grid';
this.mapboxService.map.addSource(key, {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [],
},
});
this.mapboxService.map.addLayer({
id: key,
type: 'fill',
source: key,
paint: {
// 'fill-color': 'orange',
// 'fill-opacity': 0.3,
},
});
}
drawGrid(x,y,z){
const bbox = tilebelt.tileToBBOX([x, y, z]);
console.log('边界信息>>>', bbox);
const rect = turf.bboxPolygon([bbox[0], bbox[1], bbox[2], bbox[3]]);
console.log('rect', rect);
const featuresArr = [
{
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: rect.geometry.coordinates,
},
},
];
this.renderTaskGrid('draw-grid', featuresArr, {
'fill-color': 'orange',
'fill-opacity': 0.3,
});
}
renderTaskGrid(key, featureArr, options) {
//渲染任务格子
console.log('key', key);
if (this.mapboxService.map.getSource(key)) {
this.mapboxService.map.getSource(key).setData({
//设置数据
type: 'FeatureCollection',
features: featureArr,
});
// this.mapboxService.map.setPaintProperty(key, 'fill-color', color); //设置格子颜色
if (Object.keys(options).length > 0) {
Object.keys(options).map((v) => {
this.mapboxService.map.setPaintProperty(key, v, options[v]);
});
}
}
}
格子的外边界
我们画出瓦片的格子其实是填充的正方形,mapbox定义的fill类型的外边界stroke,默认就是1,无法改变。所以,我们要打网格,还要单独画line。
import mapboxgl from 'mapbox-gl';
import * as turf from '@turf/turf';
// import { CustomSource } from '../common/customSource';
import tilebelt from '../common/tilebelt';
...
polygonKeys = [
'active-taskgrid',
'draw-taskgrid-1',
'draw-taskgrid-2',
];
lineKeys = ['task-grid-draw-line'];
initGrid() {
//格子包括边框和填充,polygon无法改变stroke笔触
this.mapboxService.map.addSource(this.lineKeys[0], {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [],
},
});
this.mapboxService.map.addLayer({
id: this.lineKeys[0],
type: 'line',
source: this.lineKeys[0],
paint: {
'line-color': 'white',
'line-width': 2,
},
});
//填充
for (const key of this.polygonKeys) {
this.mapboxService.map.addSource(key, {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [],
},
});
this.mapboxService.map.addLayer({
id: key,
type: 'fill',
source: key,
paint: {
// 'fill-color': 'orange',
'fill-opacity': 0,
},
});
}
}
async drawGrid() {
const arr = this.getBBox(); //所有可见的格子
this.drawLine(arr); //画边界线
const res = [
{
name: '格子1',
status: 1,
desc: '已完成',
grid: { x: 27239, y: 12810, z: 15 },
},
{
name: '格子2',
status: 2,
desc: '二次标注',
grid: { x: 27239, y: 12812, z: 15 },
},
{
name:'格子3',
status:1,
desc:'已完成',
grid:{ x: 27244, y: 12809, z: 15 },
}
];
this.drawPolygon(res);
}
clearGrid() {
this.clearLine();
this.clearPolygon();
}
getBBox() {
const bboxArr: any = [];
const arr: any =
this.mapboxService.map.style._sourceCaches[
'other:img_tiles'
].getVisibleCoordinates();
// const arr = this.dataSource;
if (arr && arr.length > 0) {
for (const item of arr) {
if (item.overscaledZ === 15) {
// this.drawGrid(item.canonical.x,item.canonical.y,item.canonical.z,'red');
const x = item.canonical.x;
const y = item.canonical.y;
const z = item.canonical.z;
const bbox = tilebelt.tileToBBOX([x, y, z]);
bboxArr.push(bbox);
}
}
}
return bboxArr;
}
drawLine(arr) { //画格子的边界线
const lineArr: any = [];
if (arr && arr.length > 0) {
for (const bbox of arr) {
const line1 = {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [
[bbox[0], bbox[1]],
[bbox[2], bbox[1]],
],
},
};
const line2 = {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [
[bbox[0], bbox[1]],
[bbox[0], bbox[3]],
],
},
};
lineArr.push(line1);
lineArr.push(line2);
}
this.renderTaskGrid(this.lineKeys[0], lineArr, {});
}
}
drawPolygon(arr) {//画填充图形
const featuresArr: any = [];
const featuresArr2: any = [];
// const arr = this.getBBox();
if (arr && arr.length > 0) {
for (const item of arr) {
const bbox = tilebelt.tileToBBOX([
item.grid.x,
item.grid.y,
item.grid.z,
]);
const rect = turf.bboxPolygon([bbox[0], bbox[1], bbox[2], bbox[3]]);
console.log('rect', rect);
const feature = {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: rect.geometry.coordinates,
},
};
if (item.status === 1) {
featuresArr.push(feature);
} else {
featuresArr2.push(feature);
}
}
this.renderTaskGrid(this.polygonKeys[1], featuresArr, {
'fill-color': '#0EBC71',
'fill-opacity': 0.3,
}); //画图形
this.renderTaskGrid(this.polygonKeys[2], featuresArr2, {
'fill-color': '#5A49F6',
'fill-opacity': 0.3,
}); //画图形
}
}
clearLine() {
this.renderTaskGrid(this.lineKeys[0], [], {});
}
clearPolygon() {
for (const key of this.polygonKeys) {
this.renderTaskGrid(key, [], {}); //画图形
}
}