Skip to content

Sync Movement of Multiple Maps ​

Display two maps side by side and keep their camera positions synchronized — useful for before/after comparisons or style comparisons.

Pan or zoom either map — both stay in sync.

Sync Pattern ​

The simplest approach: listen to the move event on one map and call jumpTo on the other. Use a syncing flag to prevent infinite loops.

javascript
function syncMaps(source, target) {
  let syncing = false;

  source.on('move', () => {
    if (syncing) return; // prevent feedback loop
    syncing = true;

    target.jumpTo({
      center: source.getCenter(),
      zoom: source.getZoom(),
      bearing: source.getBearing(),
      pitch: source.getPitch(),
    });

    syncing = false;
  });
}

// Sync both ways
syncMaps(mapA, mapB);
syncMaps(mapB, mapA);

Sync Multiple Maps ​

javascript
const maps = [mapA, mapB, mapC];

maps.forEach((source, i) => {
  source.on('move', () => {
    maps.forEach((target, j) => {
      if (i === j) return; // skip self
      target.jumpTo({
        center: source.getCenter(),
        zoom: source.getZoom(),
        bearing: source.getBearing(),
        pitch: source.getPitch(),
      });
    });
  });
});

Camera State Methods ​

javascript
map.getCenter()    // → LngLat { lng, lat }
map.getZoom()      // → number
map.getBearing()   // → number (degrees)
map.getPitch()     // → number (degrees)

Complete Example ​

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <link href="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.css" rel="stylesheet" />
    <script src="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.js"></script>
    <style>
      body { margin: 0; }
      #container { display: flex; height: 100vh; }
      #map-a, #map-b { flex: 1; }
    </style>
  </head>
  <body>
    <div id="container">
      <div id="map-a"></div>
      <div id="map-b"></div>
    </div>
    <script>
      const init = { center: [10, 50], zoom: 4 };

      const mapA = new mapmetricsgl.Map({ container: 'map-a', style: '<StyleFile_URL_with_Token>', ...init });
      const mapB = new mapmetricsgl.Map({ container: 'map-b', style: '<StyleFile_URL_with_Token>', ...init });

      function sync(src, tgt) {
        let busy = false;
        src.on('move', () => {
          if (busy) return;
          busy = true;
          tgt.jumpTo({ center: src.getCenter(), zoom: src.getZoom(), bearing: src.getBearing(), pitch: src.getPitch() });
          busy = false;
        });
      }

      mapA.on('load', () => mapB.on('load', () => {
        sync(mapA, mapB);
        sync(mapB, mapA);
      }));
    </script>
  </body>
</html>
jsx
import React, { useEffect, useRef } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

function syncMaps(source, target) {
  let syncing = false;
  source.on('move', () => {
    if (syncing) return;
    syncing = true;
    target.jumpTo({
      center: source.getCenter(),
      zoom: source.getZoom(),
      bearing: source.getBearing(),
      pitch: source.getPitch(),
    });
    syncing = false;
  });
}

const SyncMultipleMaps = () => {
  const containerA = useRef(null);
  const containerB = useRef(null);
  const mapA = useRef(null);
  const mapB = useRef(null);

  useEffect(() => {
    if (mapA.current) return;
    const init = { style: '<StyleFile_URL_with_Token>', center: [10, 50], zoom: 4 };
    mapA.current = new mapmetricsgl.Map({ container: containerA.current, ...init });
    mapB.current = new mapmetricsgl.Map({ container: containerB.current, ...init });

    let loadedCount = 0;
    const onLoad = () => {
      loadedCount++;
      if (loadedCount === 2) {
        syncMaps(mapA.current, mapB.current);
        syncMaps(mapB.current, mapA.current);
      }
    };
    mapA.current.on('load', onLoad);
    mapB.current.on('load', onLoad);

    return () => {
      mapA.current?.remove(); mapA.current = null;
      mapB.current?.remove(); mapB.current = null;
    };
  }, []);

  return (
    <div style={{ display: 'flex', gap: 4, height: 500 }}>
      <div ref={containerA} style={{ flex: 1 }} />
      <div ref={containerB} style={{ flex: 1 }} />
    </div>
  );
};

export default SyncMultipleMaps;

For more information, visit the MapMetrics GitHub repository.