菜鸟的地图认知以及将GeoJson数据切割为适配百度瓦片规则的矢量瓦片

849 阅读5分钟

前言

  本博客旨在于总结我这几天与地图培养的关系。如果能帮到一些像我一样对地图一无所知,却比我少了一个好的领导领路的开发人员,那最好不过。

免责条款

  • 这是我的第一篇博客
  • 我是一个菜鸟
  • 我也是第一次认识离线地图
  • 我也是第一次听说矢量瓦片
  • 我还是第一次知道百度,高德等等地图的坐标系,平面投影这种东西。
  • 我也没什么算法基础

参考资料

心路历程

  如果你知道如何在网页上加载地图,那你应该听说过高德,百度等等一系列厂家提供的地图API,但你可能不知道那些画面,那一整个canvas上的东西是怎么绘制出来的。如果你不是那种看到一样东西就会追求所以然的人(作为一个普通人),那可能你只有在业务覆盖到离线地图后才会了解到瓦片这种概念。在你了解瓦片地图这种概念之时,一般你会顺便了解到现在所有的地图都是使用矢量瓦片来绘制的(我不确定是不是所有)。这一步的时候,你可能不会被矢量瓦片吸引目光,因为你在往如何下载高德或者百度切割好的瓦片地图这方面努力。你知道了瓦片地图就是给一张张切割为256*256的部分地图图片,然后给它不同的xy坐标拼凑而成的一张完整地图。最后你借助网上公开的算法,或者自己研究实现缩放,拖动来加载不同位置的图片(这应该挺难的,但是对我来说,领导已经全弄好了)。我就完成了离线地图!
^_^ 以上是我认识到瓦片地图的过程,但是还没有触及矢量瓦片的概念。让我认识到矢量瓦片的是需要在地图基础上绘制大量的线路,如果使用基本的经纬度转像素全部绘制的话,就会得到一个卡卡的地图,而不是你的地图。我的领导拥有两个方向:一个是采用GeoJsonVT中使用到的优化线段像素的方法,然后再通过缩放优化那些不需要显示的线路;另一个就是像GeoJsonVT中一样,将数据转换为不同瓦片的像素点,也就是矢量瓦片(两个方法合在一起更好,但是单独的对我们来说也够用了)。两个方向都我来说都是一样的陌生与困难,好处是我不需要研究,领导会加油的。不过在我听到GeoJsonVT的时候感觉有一些被惊艳到,感觉视野被开阔了。反正我没什么着急事情,就花功夫研究了一下这个。因为这个库只支持高德,google,bing,腾讯这几个小厂家,没办法适用于百度。而高德好像不支持下载自定义样式的瓦片地图,百度可以。我就想将GeoJsonVT转换为支持百度的。

正文

  在正文之前,还是要再强调一遍免责条款以及缺陷。因为难度问题,我移除了GeoJsonVT使用的所有优化算法,这会导致它还是卡卡的,不是我的。不过理论成果已经有了,我也不想努力了。
移除的内容

  • 线路优化:这个算法大概是去除线路上转折不明显以及直线上重复的像素点。因为算法已经有了,这个纯属是懒得加
  • 通过递归生成各个等级下的瓦片:这是很关键的一个优化,我觉得这个递归生成才是不会卡顿的关键,只不过它会在加载之时让人稍稍等待一丢丢时间。但是我看不太懂GeoJsonVT将不同的瓦片转成的数组下标的算法,更何况是再转换成百度的,所以直接放弃。 开始
    在开始之前:正文的长度对不起前面那么多的废话,因为代码主要是调用tile-lnglat-transform这个仓库里的函数。
  1. 首先,将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];
  }
  1. 将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;
}
  1. 其实就是最后一步了,根据不同的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

很明显这不是一个开箱即用的库,主在开阔视野。
祝所有人类{如果清明节不会勾起什么回忆?清明节快乐:生活幸福}。