唯杰地图 logo唯杰地图
VJMAP
EN

sync-view

vjmap/references/overlay/sync-view.md 4.1 KB138 行
原文

本文回答:多地图视图同步(center/zoom/bearing/pitch)的几种实现与坑。

零、内置同步(推荐)

const unsync = vjmap.syncMaps(map1, map2, map3);   // 互相同步 center / zoom / bearing / pitch,内部已防递归
unsync();                                           // 解绑

卷帘直接用 new vjmap.Compare(mapA, mapB, container, { orientation: "vertical" })(自带滑块并自动同步,remove() 解除)。下面是手写方案。

一、手写同步

function syncMove(source, target) {
  source.on("move", () => {
    target.jumpTo({
      center:  source.getCenter(),
      zoom:    source.getZoom(),
      bearing: source.getBearing(),
      pitch:   source.getPitch()
    });
  });
}

// 双向
syncMove(map1, map2);
syncMove(map2, map1);

注意双向会 无限递归:jumpTo 内部也会 fire move。加 guard:

let syncing = false;
function syncMove(src, dst) {
  src.on("move", () => {
    if (syncing) return;
    syncing = true;
    dst.jumpTo({
      center:  src.getCenter(),
      zoom:    src.getZoom(),
      bearing: src.getBearing(),
      pitch:   src.getPitch()
    });
    syncing = false;
  });
}

二、封装多图同步

function linkMaps(...maps) {
  let syncing = false;
  maps.forEach(src => {
    src.on("move", () => {
      if (syncing) return;
      syncing = true;
      const { lng, lat } = src.getCenter();
      const zoom = src.getZoom();
      const bearing = src.getBearing();
      const pitch = src.getPitch();
      maps.filter(m => m !== src)
          .forEach(m => m.jumpTo({ center: [lng, lat], zoom, bearing, pitch }));
      syncing = false;
    });
  });
}
linkMaps(map1, map2, map3);

三、卷帘(Compare)

推荐自建 DOM 方案,不引入额外依赖:

<div id="container" style="position:relative;width:100%;height:100%">
  <div id="before" style="position:absolute;inset:0"></div>
  <div id="after"  style="position:absolute;inset:0;clip-path:inset(0 50% 0 0)"></div>
  <div id="handle" style="position:absolute;top:0;bottom:0;left:50%;width:4px;background:#000;cursor:ew-resize;z-index:10"></div>
</div>
linkMaps(mapBefore, mapAfter);

const handle = document.getElementById("handle");
const after  = document.getElementById("after");
let dragging = false;
handle.onmousedown = () => { dragging = true; };
window.onmouseup   = () => { dragging = false; };
window.onmousemove = e => {
  if (!dragging) return;
  const rect = container.getBoundingClientRect();
  const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
  handle.style.left = ratio * 100 + "%";
  after.style.clipPath = `inset(0 ${(1 - ratio) * 100}% 0 0)`;
};

四、与 vjmap CAD 的结合

同一张图的 GeomRender vs 栅格

const map1 = new vjmap.Map({ container: "m1", style: svc.rasterStyle(), ... });
const map2 = new vjmap.Map({ container: "m2", style: svc.vectorStyle(), ... });

同一张图深浅主题对比

// 传 openMapLightStyle / openMapDarkStyle 两次打开

两个 mapid(不同 CAD 图)

两图 mapid 不同时坐标系可能也不同:

  • 都用 LnglatProjection:同步就是经纬度对齐。
  • 都用 GeoProjection:两个 prj 独立,同步后 center 只是"经纬度数值相同",但对应的 CAD 坐标不同。可通过 prj1.fromLngLat ↔ prj2.toLngLat 做桥接。

五、同步内容的细粒度

属性 是否同步
center 一般同步
zoom 一般同步
bearing 看需求(3D 比对)
pitch 看需求
图层显隐 不要同步(各图独立)

六、常见坑

  1. 无限递归:对 move 事件同步时不加 guard,两张图互相 fire 导致死循环。
  2. jumpTo vs easeTo:同步用 jumpTo(无动画),avoid easeTo 的 per-frame 堆叠。
  3. 不同容器像素比:retina 和非 retina 混合时,同 zoom 下实际视觉不同;通常可接受。
  4. 第三张图加入后旧的 sync 不生效:需要重新调用 linkMaps 重建。