mirror of
https://github.com/maputnik/editor.git
synced 2026-01-21 12:50:02 +00:00
This probably confused some people in the past, since vector tiles won't even display an access denied image ;) Before (no information on access keys and where to get them): <img width="411" alt="image" src="https://github.com/user-attachments/assets/8820fb20-bda4-460c-9cc9-8fec5daa480d"> After (add links to providers in info callout + add a field for Stadia Maps API keys): <img width="395" alt="image" src="https://github.com/user-attachments/assets/91ee732c-b3f5-45f8-81a6-8d896a93f644"> --------- Co-authored-by: Harel M <harel.mazor@gmail.com> Co-authored-by: Joscha <34318751+josxha@users.noreply.github.com> Co-authored-by: Hugues Tavernier <hugues.tavernier@protonmail.com> Co-authored-by: Keitaroh Kobayashi <keita@kbys.me>
156 lines
4.3 KiB
TypeScript
156 lines
4.3 KiB
TypeScript
import {derefLayers} from '@maplibre/maplibre-gl-style-spec'
|
|
import type {StyleSpecification, LayerSpecification} from 'maplibre-gl'
|
|
import tokens from '../config/tokens.json'
|
|
|
|
// Empty style is always used if no style could be restored or fetched
|
|
const emptyStyle = ensureStyleValidity({
|
|
version: 8,
|
|
sources: {},
|
|
layers: [],
|
|
})
|
|
|
|
function generateId() {
|
|
return Math.random().toString(36).substring(2, 9)
|
|
}
|
|
|
|
function ensureHasId(style: StyleSpecification & { id?: string }): StyleSpecification & { id: string } {
|
|
if(!('id' in style) || !style.id) {
|
|
style.id = generateId();
|
|
return style as StyleSpecification & { id: string };
|
|
}
|
|
return style as StyleSpecification & { id: string };
|
|
}
|
|
|
|
function ensureHasNoInteractive(style: StyleSpecification & {id: string}) {
|
|
const changedLayers = style.layers.map(layer => {
|
|
const changedLayer: LayerSpecification & { interactive?: any } = { ...layer }
|
|
delete changedLayer.interactive
|
|
return changedLayer
|
|
})
|
|
|
|
return {
|
|
...style,
|
|
layers: changedLayers
|
|
}
|
|
}
|
|
|
|
function ensureHasNoRefs(style: StyleSpecification & {id: string}) {
|
|
return {
|
|
...style,
|
|
layers: derefLayers(style.layers)
|
|
}
|
|
}
|
|
|
|
function ensureStyleValidity(style: StyleSpecification): StyleSpecification & { id: string } {
|
|
return ensureHasNoInteractive(ensureHasNoRefs(ensureHasId(style)))
|
|
}
|
|
|
|
function indexOfLayer(layers: LayerSpecification[], layerId: string) {
|
|
for (let i = 0; i < layers.length; i++) {
|
|
if(layers[i].id === layerId) {
|
|
return i
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
function getAccessToken(sourceName: string, mapStyle: StyleSpecification, opts: {allowFallback?: boolean}) {
|
|
const metadata = mapStyle.metadata || {} as any;
|
|
let accessToken = metadata[`maputnik:${sourceName}_access_token`]
|
|
|
|
if(opts.allowFallback && !accessToken) {
|
|
accessToken = tokens[sourceName as keyof typeof tokens]
|
|
}
|
|
|
|
return accessToken;
|
|
}
|
|
|
|
function replaceSourceAccessToken(mapStyle: StyleSpecification, sourceName: string, opts={}) {
|
|
const source = mapStyle.sources[sourceName]
|
|
if(!source) return mapStyle
|
|
if(!("url" in source) || !source.url) return mapStyle
|
|
|
|
let authSourceName = sourceName
|
|
if(sourceName === "thunderforest_transport" || sourceName === "thunderforest_outdoors") {
|
|
authSourceName = "thunderforest"
|
|
}
|
|
else if (("url" in source) && source.url?.match(/\.stadiamaps\.com/)) {
|
|
// The code currently usually assumes openmaptiles == MapTiler,
|
|
// so we need to check the source URL.
|
|
authSourceName = "stadia"
|
|
}
|
|
|
|
const accessToken = getAccessToken(authSourceName, mapStyle, opts)
|
|
|
|
if(!accessToken) {
|
|
// Early exit.
|
|
return mapStyle;
|
|
}
|
|
|
|
let sourceUrl: string
|
|
if (authSourceName == "stadia") {
|
|
// Stadia Maps does not always require an API key,
|
|
// so there is no placeholder in our styles.
|
|
// We append it at the end of the URL when exporting if necessary.
|
|
sourceUrl = `${source.url}?api_key=${accessToken}`
|
|
} else {
|
|
sourceUrl = source.url.replace('{key}', accessToken)
|
|
}
|
|
|
|
const changedSources = {
|
|
...mapStyle.sources,
|
|
[sourceName]: {
|
|
...source,
|
|
url: sourceUrl
|
|
}
|
|
}
|
|
const changedStyle = {
|
|
...mapStyle,
|
|
sources: changedSources
|
|
}
|
|
return changedStyle
|
|
}
|
|
|
|
function replaceAccessTokens(mapStyle: StyleSpecification, opts={}) {
|
|
let changedStyle = mapStyle
|
|
|
|
Object.keys(mapStyle.sources).forEach((sourceName) => {
|
|
changedStyle = replaceSourceAccessToken(changedStyle, sourceName, opts);
|
|
})
|
|
|
|
if (mapStyle.glyphs && (mapStyle.glyphs.match(/\.tilehosting\.com/) || mapStyle.glyphs.match(/\.maptiler\.com/))) {
|
|
const newAccessToken = getAccessToken("openmaptiles", mapStyle, opts);
|
|
if (newAccessToken) {
|
|
changedStyle = {
|
|
...changedStyle,
|
|
glyphs: mapStyle.glyphs.replace('{key}', newAccessToken)
|
|
}
|
|
}
|
|
}
|
|
|
|
return changedStyle
|
|
}
|
|
|
|
function stripAccessTokens(mapStyle: StyleSpecification) {
|
|
const changedMetadata = {
|
|
...mapStyle.metadata as any
|
|
};
|
|
delete changedMetadata['maputnik:openmaptiles_access_token'];
|
|
delete changedMetadata['maputnik:thunderforest_access_token'];
|
|
delete changedMetadata['maputnik:stadia_access_token'];
|
|
return {
|
|
...mapStyle,
|
|
metadata: changedMetadata
|
|
};
|
|
}
|
|
|
|
export default {
|
|
ensureStyleValidity,
|
|
emptyStyle,
|
|
indexOfLayer,
|
|
generateId,
|
|
getAccessToken,
|
|
replaceAccessTokens,
|
|
stripAccessTokens,
|
|
}
|