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.