Skip to content

Map Provider

This example showcases a context provider and hook to manage multiple MapLibre GL map instances.

Mounted maps:

We will need a plugin to manage map synchronization. The only plugin I could find was mapbox-gl-sync-move however its a common js library that lacks type declarations. There exists a typed instance of the syncMaps definition in the issues which can be defined as shown below:

import * as maplibregl from 'maplibre-gl'
/**
* Sync movements of two maps.
*
* All interactions that result in movement end up firing
* a "move" event. The trick here, though, is to
* ensure that movements don't cycle from one map
* to the other and back again, because such a cycle
* - could cause an infinite loop
* - prematurely halts prolonged movements like
* double-click zooming, box-zooming, and flying
*/
export default function syncMaps(...maps: maplibregl.Map[]) {
// Create all the movement functions, because if they're created every time
// they wouldn't be the same and couldn't be removed.
let fns: Parameters<maplibregl.Map["on"]>[1][] = [];
maps.forEach((map, index) => {
// When one map moves, we turn off the movement listeners
// on all the maps, move it, then turn the listeners on again
fns[index] = () => {
off();
const center = map.getCenter();
const zoom = map.getZoom();
const bearing = map.getBearing();
const pitch = map.getPitch();
const clones = maps.filter((o, i) => i !== index);
clones.forEach((clone) => {
clone.jumpTo({
center: center,
zoom: zoom,
bearing: bearing,
pitch: pitch,
});
});
on();
};
});
const on = () => {
maps.forEach((map, index) => {
map.on("move", fns[index]);
});
};
const off = () => {
maps.forEach((map, index) => {
map.off("move", fns[index]);
});
};
on();
return () => {
off();
fns = [];
maps = [];
};
}

Save this file as sync.ts and use it to sync you’re maps.

import { createEffect, createMemo, createSignal, type Component, type JSX } from "solid-js";
import { Maplibre, MapsProvider, useMaps } from "solidjs-maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import syncMaps from './sync'
const MapCProvider: Component = (props) => {
const [maplist, setMapList] = createSignal<maplibregl.Map[]>([])
createEffect(() => {
syncMaps(...maplist());
})
return (
<div style={{position: 'relative', height: '100%', "min-height": '500px'}}>
<MapsProvider>
<MapsProbe />
<Maplibre
style={{
position: 'absolute',
width: '50%',
height: '100%',
}}
options={{
zoom: 12,
center: [-122.43, 37.78],
pitch: 30,
style:'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json'
}}
onidle={(e) => {
setMapList((m) => {
if (m.includes(e.target)) {
return m
} else {
return [...m, e.target]
}
})
}}
/>
<Maplibre
style={{
position: 'absolute',
left: '50%',
width: '50%',
height: '100%'
}}
options={{
zoom: 12,
center: [-122.43, 37.78],
pitch: 30,
style:'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json'
}}
onidle={(e) => {
setMapList((m) => {
if (m.includes(e.target)) {
return m
} else {
return [...m, e.target]
}
})
}}
/>
</MapsProvider>
</div>
);
};
export default MapCProvider;
const MapsProbe = () => {
const keys = createMemo(() =>
[...(useMaps()?.maps().keys() ?? [])].join(", "),
);
return <p style={mounted}>Mounted maps: <span style={{"font-family":'sans-serif'}}>{keys()}</span></p>;
};
const mounted:JSX.CSSProperties = {
'padding': '0.7rem 1rem',
background: '#3d3d3dff',
"border-top-right-radius": '16px'
}