前言
本博客旨在于总结我这几天与地图培养的关系。如果能帮到一些像我一样对地图一无所知,却比我少了一个好的领导领路的开发人员,那最好不过。
免责条款
- 这是我的第一篇博客
- 我是一个菜鸟
- 我也是第一次认识离线地图
- 我也是第一次听说矢量瓦片
- 我还是第一次知道百度,高德等等地图的坐标系,平面投影这种东西。
- 我也没什么算法基础
参考资料
- 国内主要地图瓦片坐标系定义及计算原理:帮你认识各个地图的坐标系以及瓦片的概念与异同
- 上一篇文章中作者实现的各个地图中经纬度转瓦片像素、坐标的函数库
- GeoJsonVT作者的介绍:我的启蒙与思路都是来源于这个库
- GeoJsonVT仓库
心路历程
如果你知道如何在网页上加载地图,那你应该听说过高德,百度等等一系列厂家提供的地图API,但你可能不知道那些画面,那一整个canvas上的东西是怎么绘制出来的。如果你不是那种看到一样东西就会追求所以然的人(作为一个普通人),那可能你只有在业务覆盖到离线地图后才会了解到瓦片这种概念。在你了解瓦片地图这种概念之时,一般你会顺便了解到现在所有的地图都是使用矢量瓦片来绘制的(我不确定是不是所有)。这一步的时候,你可能不会被矢量瓦片吸引目光,因为你在往如何下载高德或者百度切割好的瓦片地图这方面努力。你知道了瓦片地图就是给一张张切割为256*256的部分地图图片,然后给它不同的xy坐标拼凑而成的一张完整地图。最后你借助网上公开的算法,或者自己研究实现缩放,拖动来加载不同位置的图片(这应该挺难的,但是对我来说,领导已经全弄好了)。我就完成了离线地图!
^_^ 以上是我认识到瓦片地图的过程,但是还没有触及矢量瓦片的概念。让我认识到矢量瓦片的是需要在地图基础上绘制大量的线路,如果使用基本的经纬度转像素全部绘制的话,就会得到一个卡卡的地图,而不是你的地图。我的领导拥有两个方向:一个是采用GeoJsonVT中使用到的优化线段像素的方法,然后再通过缩放优化那些不需要显示的线路;另一个就是像GeoJsonVT中一样,将数据转换为不同瓦片的像素点,也就是矢量瓦片(两个方法合在一起更好,但是单独的对我们来说也够用了)。两个方向都我来说都是一样的陌生与困难,好处是我不需要研究,领导会加油的。不过在我听到GeoJsonVT的时候感觉有一些被惊艳到,感觉视野被开阔了。反正我没什么着急事情,就花功夫研究了一下这个。因为这个库只支持高德,google,bing,腾讯这几个小厂家,没办法适用于百度。而高德好像不支持下载自定义样式的瓦片地图,百度可以。我就想将GeoJsonVT转换为支持百度的。
正文
在正文之前,还是要再强调一遍免责条款以及缺陷。因为难度问题,我移除了GeoJsonVT使用的所有优化算法,这会导致它还是卡卡的,不是我的。不过理论成果已经有了,我也不想努力了。
移除的内容
- 线路优化:这个算法大概是去除线路上转折不明显以及直线上重复的像素点。因为算法已经有了,这个纯属是懒得加
- 通过递归生成各个等级下的瓦片:这是很关键的一个优化,我觉得这个递归生成才是不会卡顿的关键,只不过它会在加载之时让人稍稍等待一丢丢时间。但是我看不太懂GeoJsonVT将不同的瓦片转成的数组下标的算法,更何况是再转换成百度的,所以直接放弃。
开始
在开始之前:正文的长度对不起前面那么多的废话,因为代码主要是调用tile-lnglat-transform这个仓库里的函数。
- 首先,将tile-lnglat-transform仓库里百度的转换类提取出来,然后在里面添加两个方法:平面像素转瓦片,瓦片像素转平面
/**
* 某一瓦片像素点到平面坐标
* @param {Array} pixel
* @param {number} x
* @param {number} y
* @param {number} z
*/
tilePixelToPoint(pixel, x, y, z) {
let [pixelX, pixelY] = pixel;
let pointX = (x * 256 + pixelX) / this._getRetain(z);
let pointY = (y * 256 + pixelY) / this._getRetain(z);
return [pointX, pointY];
}
/**
* 平面坐标到瓦片像素点
* @param {Array} point
* @param {number} x
* @param {number} y
* @param {number} z
*/
pointToPixel(point, x, y, z) {
let [pointX, pointY] = point;
let pixelX = pointX * this._getRetain(z) - x * 256;
let pixelY = pointY * this._getRetain(z) - y * 256;
return [pixelX, pixelY];
}
- 将GeoJsonData里的经纬度转换为平面像素点
function convertFeatures(data) {
return data.features.map((item) => ({
...item,
geometry: {
...item.geometry,
coordinates: lngLatToPointData(item.geometry.coordinates),
},
}));
}
function lngLatToPointData(coors) {
let pointData = [];
for (let i = 0; i < coors[0].length; i++) {
const { pointX: x, pointY: y } = TransFormClassBaidu.lnglatToPoint(...coors[0][i]);
pointData.push([x, y]);
}
return pointData;
}
- 其实就是最后一步了,根据不同的xyz将瓦片的左上角0,0,右小角256,256转换成平面像素,然后再递归转换后GeoJsonData,将符合范围的像素点坐标返回给它
getTile(z, x, y) {
const minPoint = TransFormClassBaidu.tilePixelToPoint([0, 0], x, y, z);
const maxPoint = TransFormClassBaidu.tilePixelToPoint([256, 256], x, y, z);
this.tiles = { ...this.tiles, [`${z}-${x}-${y}`]: { minPoint, maxPoint } };
let lines = [];
this.features.forEach((feature) => {
let line = [];
feature.geometry.coordinates.forEach((coor) => {
if (isPointInRect(minPoint, maxPoint, coor)) {
line.push(TransFormClassBaidu.pointToPixel(coor, x, y, z));
}
});
if (line.length > 0) lines.push(line);
});
return lines;
}
}
function isPointInRect(minP, maxP, point) {
const [minPx, minPy] = minP;
const [maxPx, maxPy] = maxP;
const [x, y] = point;
return x > minPx && x < maxPx && y > minPy && y < maxPy;
}
end
很明显这不是一个开箱即用的库,主在开阔视野。
祝所有人类{如果清明节不会勾起什么回忆?清明节快乐:生活幸福}。