关于地图根据范围自适应缩放问题

879 阅读2分钟

1. 问题背景

问题的起因是在业务开发中,需要编写一个MapPicker组件,组件可拆分为PreviewPicker两个子组件。在Preview组件中,可接受一组geoJson数据, Preview组件需要在地图上绘制出区域,并且根据区域的尺寸自动调整到合适的缩放比例,为了让区域信息更好的展示。 具体效果如下图所示:

屏幕录制2022-08-12-14.46.33.gif

2. 实现思路

2.1 地图尺寸与缩放级别

首先需要了解全画幅地图尺寸和缩放级别的关系, 通过下图可以得到如下关系:

  • zoom: 为地图缩放级别
  • 全画幅地图宽度 = 256 * 2^zoom
  • 全画幅地图高度 = 256 * 2^zoom

aHR0cDovL2NjLmNvY2ltZy5jb20vYXBpL3VwbG9hZHMvMjAxNjAzMTYvMTQ1ODEwODQ1NTg0MDA0Ny5wbmc.png

2.2 像素与实际距离的关系

[注] 一像素(px)代表的实际距离(m)pxm指代。pxm(18)指代18级缩放下的pxm

通过上面的公式可以得到

  • 18级的缩放等级下, 全画幅地图的宽度为256 * 2^18 (一般18级为最大缩放等级)
  • 因为全画幅地图的宽度 = 赤道长度(40075016.68557849m), 所以在18级缩放等级下, pxm(18) = 40075016.68557849 / 256 * 2^18,
  • 整理下为: pxm(zoom) = 40075016.68557849 / 256 * 2^zoom,
  • 这里需要铺垫的是, pxm(18-n) / pxm(18) = 2^n

2.3 最后的验算

  1. 容器尺寸指: 地图在DOM中的尺寸, 这里以200px * 200px为例
  2. 讲解一般以18级别缩放为例, 是为了通过18级倒推结果, 当然你也可以从0级开始
  • 计算18级别缩放下容器宽度(200px)能够展现的实际距离vm(zoom) : vm(18) = 200 * pxm(18)
  • 假设现在需要展示的区域为矩形, 尺寸为1000m * 1000m, 中线点为[26,120](具体解析方法见代码详情)
  • 根据2.2的公式pxm(18-n) / pxm(18) = 2^n, 可知: vm(18) - pxm(18) = 2^n, 那么: n = lg(vm(18)/pxm(18))/lg(2)

3.具体代码

代码以javaScript语言为例

import * as turf from '@turf/turf';
// coordinates: Array<[number, number, numer]>
function fullViewWindow (coordinates) {
    const polygon = turf.polygon([[...coordinates]]);
    const center = turf.center(polygon).geometry.coordinates; //区域的几何中心
    //以矩形为例, 如果非矩形需要计算`bbox`转为矩形
    //1.通过三个相邻点位, 计算出矩形的最长边
    const pointA = turf.point(coordinates[0])
    const pointB = turf.point(coordinates[1])
    const pointC = turf.point(coordinates[2])
    const d1 = turf.distance(pointA, pointB, { units: 'meters' });
    const d2 = turf.distance(pointB, pointC, { units: 'meters' });
    const d = Math.max(d1, d2);
    //2.计算18级别缩放下的pxm(赤道周长/18级缩放全球地图总像素点)
    const pxm18 = 40075016.68557849 / (256 * Math.pow(2, 18));
    //3.计算视图范围宽度呈现的实际距离(单位米)
    //假设容器尺寸为(200px * 200px)
    const clientWidth = 200
    const clientHeight = 200
    const vm18 = Math.min(clientWidth, clientHeight) * pxm18;
    let zoom = 18 - Math.log2(d / viewMeter);
    zoom = zoom > 18 ? 18 : zoom;
    return { zoom, center }
}