MapLibre
The @ulm/maplibre package connects a layer manager to a MapLibre GL JS map. It adds and removes each layer's sources and style layers, shows and hides them, sets their opacity, and stacks them in the manager's order below the basemap's labels. See Installation to add it to your project.
Minimal usage
The simplest approach is to keep each layer's MapLibre style in layerData: the sources it reads, and its style layers from the bottom up. The adapter shows it from there by default.
import type { MapLibreLayerStyle } from '@ulm/maplibre';
import { LayerManager } from '@ulm/core';
import { MapLibreLayerManagerAdapter } from '@ulm/maplibre';
import * as maplibregl from 'maplibre-gl';
type LayerData = MapLibreLayerStyle;
const map = new maplibregl.Map({
container: 'map',
style: 'https://tiles.openfreemap.org/styles/bright',
center: [-0.1, 51.505],
zoom: 10,
});
const manager = new LayerManager<LayerData>();
manager.setAdapter(new MapLibreLayerManagerAdapter<LayerData>(map));
manager.addLayer({
layerConfig: {
layerId: 'railways',
layerName: 'Railways',
layerType: 'layer',
layerData: {
sources: { railways: { type: 'vector', url: 'https://tiles.openfreemap.org/planet' } },
layers: [{
'id': 'railways',
'type': 'line',
'source': 'railways',
'source-layer': 'transportation',
'filter': ['==', ['get', 'class'], 'rail'],
'paint': { 'line-color': '#e65100', 'line-width': 2 },
}],
},
},
visible: true,
});You can attach the adapter as soon as the map is created. It waits for the style to load before it shows anything.
Showing layers with renderLayer
To keep other data in layerData, such as a URL, give the adapter a renderLayer function. It returns the sources and style layers to show, or null to leave the layer off the map. It runs when a layer is added, and again when its layerData or timeInfo changes.
Here some layers keep a GeoJSON URL, and the rest keep a style, which the default shows:
import type { MapLibreLayerStyle } from '@ulm/maplibre';
import { defaultMapLibreRenderLayer } from '@ulm/maplibre';
type LayerData = { url: string } | MapLibreLayerStyle;
manager.setAdapter(
new MapLibreLayerManagerAdapter<LayerData>(map, {
renderLayer(info) {
if ('url' in info.layerData) {
return {
sources: { [info.layerId]: { type: 'geojson', data: info.layerData.url } },
layers: [{ id: info.layerId, type: 'line', source: info.layerId }],
};
}
return defaultMapLibreRenderLayer(info);
},
}),
);Updating the style already shown
When it runs again, renderLayer gets the style it returned last time as current. The adapter changes only what differs: new GeoJSON data goes through setData, and new tile URLs through setTiles or setUrl, without flickering. Return current to leave the map alone. Here a layer's tiles come from its date:
manager.setAdapter(
new MapLibreLayerManagerAdapter<LayerData>(map, {
renderLayer(info) {
const date = isSingleTimeInfo(info.timeInfo) ? info.timeInfo.value.toString() : 'latest';
return {
sources: { 'sea-ice': { type: 'raster', tiles: [`https://example.com/sea-ice/${date}/{z}/{x}/{y}.png`], tileSize: 256 } },
layers: [{ id: 'sea-ice', type: 'raster', source: 'sea-ice' }],
};
},
}),
);Settings and data from outside the layer
Put anything else a layer's look depends on, such as a theme or a language, in its layerData with updateLayerData. renderLayer then runs again.
For data you have to fetch, such as the data for a new date, start the fetch in the manager's onTimeInfoChanged callback and write the result into layerData. Until it arrives, return current.
Cleaning up with disposeLayer
disposeLayer undoes anything you set up outside the style. It runs when the adapter discards a style renderLayer returned: when the layer is removed, when renderLayer returns a different style, and when the adapter is detached. Hiding a layer, or new GeoJSON data or tile URLs, don't count.
For other changes, such as opacity, use the LayerManager callbacks. See the @ulm/maplibre reference for every option.
Sources and style layer IDs
Sources and style layers keep the IDs renderLayer gives them, so you can use them with the map directly:
map.on('click', 'sea-ice', (event) => {
console.log(event.features);
});- A listener like this keeps working when its style layer is removed and comes back.
- Give each style layer its own ID. Include
info.layerIdwhen more than one layer would otherwise use the same one, such asline. - If the map already has an ID you gave, such as from the basemap, the adapter leaves the layer off the map and reports a MapLibre
errorevent. - Layers that list the same source share it.
- To read a basemap source, name it in a style layer and leave it out of
sources.
How stacking works
The adapter draws your layers in one block, in the manager's order, directly below the basemap's first label layer. So they sit above the land, water and roads, and below the labels. With no labels, they go on top.
To place them somewhere else, name the style layer to draw below. On OpenFreeMap Bright, this puts them below the roads:
manager.setAdapter(new MapLibreLayerManagerAdapter<LayerData>(map, { drawBelow: 'tunnel-service-track-casing' }));A hidden layer stays in the style, with its visibility set to none.
How opacity works
MapLibre can fade a whole fill or line layer, so the adapter sets their fill-layer-opacity or line-layer-opacity. The style's own fill-opacity and line-opacity still apply on top.
Other kinds of style layer have no whole-layer opacity, so the adapter scales their own opacity instead, such as raster-opacity or circle-opacity. Hillshade layers have no opacity, so they can't be faded.
Switching the basemap
In MapLibre the basemap is the map's style, not a layer in the manager. Switch it with map.setStyle, and the adapter adds your layers back once the new style has loaded.
map.setStyle('https://tiles.openfreemap.org/styles/dark');Things to know
- Only layers are drawn. Groups have no style layers of their own, but hiding or fading a group hides or fades the layers inside it.
- Custom layers, which draw with their own WebGL code, are not supported. Add them to the map yourself.
- Provide images, such as icons and fill patterns, with
map.setMissingStyleImageResolver. Images added withmap.addImageare lost when the basemap changes. - Reject an invalid date before calling
setTimeInfo, so the map always shows the time the manager holds.