layer_format_maplibre.mjs
/**
### mapp.layer.formats.maplibre()
@module /layer/formats/maplibre
*/
// Assign maplibre from window if already loaded.
let promise,
maplibregl = window.maplibregl;
async function MaplibreGL() {
if (maplibregl) return new maplibregl.Map(...arguments);
// Create promise to load maplibre from esm.sh
if (!promise)
promise = new Promise((resolve) => {
import('https://esm.sh/maplibre-gl@4.7.1').then((mod) => {
maplibregl = mod.default;
// Avoid loading RTL Text Plugin twice, else it will error
if (
!['deferred', 'loaded'].includes(maplibregl.getRTLTextPluginStatus())
) {
maplibregl.setRTLTextPlugin(
'https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.2.3/mapbox-gl-rtl-text.min.js',
true, // Lazy load the plugin
);
}
resolve();
});
});
await promise;
if (!maplibregl) return;
return new maplibregl.Map(...arguments);
}
export default async (layer) => {
const className = `mapp-layer-${layer.key} maplibre`;
layer.container = mapp.utils.html.node`<div
class=${className}
style="visibility: hidden; position: absolute; width: 100%; height: 100%;">`;
layer.mapview.Map.getTargetElement().prepend(layer.container);
// Check mapbox style URL and accessToken
if (layer.style?.URL && layer.accessToken) {
const testUrl = normalizeStyleURL(layer.style.URL, layer.accessToken);
layer.style.object = await mapp.utils.xhr(testUrl);
// The format method must return without creating a layer or even importing the maplibre library if it is not possible to retrieve a mapbox style object.
if (layer.style.object instanceof Error) {
return;
}
}
// A layer style object is required to render vector tiles.
if (!layer.style?.object) {
console.warn(
`${layer.key} failed to load, check that an access token and a style.URL are provided.`,
);
return;
}
const maplibreMap = await MaplibreGL({
container: layer.container,
pixelRatio: 1,
style: layer.style?.object,
attributionControl: false,
boxZoom: false,
doubleClickZoom: false,
dragPan: false,
dragRotate: false,
interactive: false,
keyboard: false,
pitchWithRotate: false,
scrollZoom: false,
touchZoomRotate: false,
preserveDrawingBuffer: layer.preserveDrawingBuffer,
transformRequest: (url, resourceType) => {
if (url.indexOf('mapbox:') === 0) {
return transformMapboxUrl(url, resourceType, layer.accessToken);
}
// Do any other transforms you want
return { url };
},
});
if (!Map) return;
// The Maplibre Map control must resize with mapview Map targetElement.
layer.mapview.Map.getTargetElement().addEventListener('resize', () =>
maplibreMap.resize(),
);
// Handle layer.style.zIndex deprecation.
if (layer.style.zIndex) {
console.warn(
`Layer: ${layer.key}, layer.style.zIndex has been deprecated. Use layer.zIndex instead.`,
);
}
layer.L = new ol.layer.Layer({
key: layer.key,
zIndex: layer.zIndex,
render: (frameState) => {
if (!layer.display) return;
layer.container.style.visibility = 'visible';
// adjust view parameters in mapbox
maplibreMap.jumpTo({
center: ol.proj.toLonLat(frameState.viewState.center),
zoom: frameState.viewState.zoom - 1,
bearing: (-frameState.viewState.rotation * 180) / Math.PI,
animate: false,
});
return layer.container;
},
});
};
// transformMapboxUrl and supporting functions are taken from https://github.com/rowanwins/maplibregl-mapbox-request-transformer
function transformMapboxUrl(url, resourceType, accessToken) {
if (url.indexOf('/styles/') > -1 && url.indexOf('/sprite') === -1) {
return {
url: normalizeStyleURL(url, accessToken),
};
}
if (url.indexOf('/sprites/') > -1) {
return {
url: normalizeSpriteURL(url, accessToken),
};
}
if (url.indexOf('/fonts/') > -1) {
return {
url: normalizeGlyphsURL(url, accessToken),
};
}
if (url.indexOf('/v4/') > -1) {
return {
url: normalizeSourceURL(url, accessToken),
};
}
if (resourceType === 'Source') {
return {
url: normalizeSourceURL(url, accessToken),
};
}
}
function parseUrl(url) {
const parts = url.match(/^(\w+):\/\/([^/?]*)(\/[^?]+)?\??(.+)?/);
if (!parts) {
throw new Error('Unable to parse URL object');
}
return {
protocol: parts[1],
authority: parts[2],
path: parts[3] || '/',
params: parts[4] ? parts[4].split('&') : [],
};
}
function formatUrl(urlObject, accessToken) {
const apiUrlObject = parseUrl('https://api.mapbox.com');
urlObject.protocol = apiUrlObject.protocol;
urlObject.authority = apiUrlObject.authority;
urlObject.params.push(`access_token=${accessToken}`);
const params = urlObject.params.length
? `?${urlObject.params.join('&')}`
: '';
return `${urlObject.protocol}://${urlObject.authority}${urlObject.path}${params}`;
}
function normalizeStyleURL(url, accessToken) {
const urlObject = parseUrl(url);
urlObject.path = `/styles/v1${urlObject.path}`;
return formatUrl(urlObject, accessToken);
}
function normalizeGlyphsURL(url, accessToken) {
const urlObject = parseUrl(url);
urlObject.path = `/fonts/v1${urlObject.path}`;
return formatUrl(urlObject, accessToken);
}
function normalizeSourceURL(url, accessToken) {
const urlObject = parseUrl(url);
urlObject.path = `/v4/${urlObject.authority}.json`;
urlObject.params.push('secure');
return formatUrl(urlObject, accessToken);
}
function normalizeSpriteURL(url, accessToken) {
const urlObject = parseUrl(url);
const path = urlObject.path.split('.');
urlObject.path = `/styles/v1${path[0]}/sprite.${path[1]}`;
return formatUrl(urlObject, accessToken);
}