diff options
Diffstat (limited to 'src/frontend/app/maps')
| -rw-r--r-- | src/frontend/app/maps/styleloader.ts | 92 |
1 files changed, 85 insertions, 7 deletions
diff --git a/src/frontend/app/maps/styleloader.ts b/src/frontend/app/maps/styleloader.ts index 7d90116..62ab2fc 100644 --- a/src/frontend/app/maps/styleloader.ts +++ b/src/frontend/app/maps/styleloader.ts @@ -3,6 +3,7 @@ import type { Theme } from "~/AppContext"; export interface StyleLoaderOptions { includeTraffic?: boolean; + language?: string; } export const DEFAULT_STYLE: StyleSpecification = { @@ -13,19 +14,76 @@ export const DEFAULT_STYLE: StyleSpecification = { layers: [], }; +/** + * Builds a MapLibre text-field expression that prefers the given language. + */ +function buildLanguageTextField(language: string): unknown[] { + const lang = language.toLowerCase().split("-")[0]; + switch (lang) { + case "es": + return [ + "coalesce", + ["get", "name:es"], + ["get", "name:latin"], + ["get", "name"], + ]; + case "gl": + return [ + "coalesce", + ["get", "name:gl"], + ["get", "name:es"], + ["get", "name:latin"], + ["get", "name"], + ]; + case "en": + return [ + "coalesce", + ["get", "name:en"], + ["get", "name_en"], + ["get", "name:latin"], + ["get", "name"], + ]; + default: + return ["coalesce", ["get", "name:latin"], ["get", "name"]]; + } +} + +/** + * Returns true for text-field expressions that encode multi-language name + * logic (they reference name:latin or name_en). These are the label layers + * produced by OpenMapTiles / OpenFreeMap that need localisation. + */ +function isMultiLanguageTextField(textField: unknown): boolean { + if (!Array.isArray(textField)) return false; + const str = JSON.stringify(textField); + return str.includes('"name:latin"') || str.includes('"name_en"'); +} + +/** + * Mutates the loaded style to replace multi-language label expressions with + * a localised version appropriate for the given language code. + */ +function applyLanguageToStyle(style: any, language: string): void { + const newTextField = buildLanguageTextField(language); + for (const layer of style.layers ?? []) { + if ( + layer.layout?.["text-field"] && + isMultiLanguageTextField(layer.layout["text-field"]) + ) { + layer.layout["text-field"] = newTextField; + } + } +} + export async function loadStyle( styleName: string, colorScheme: Theme, options?: StyleLoaderOptions ): Promise<StyleSpecification> { - const { includeTraffic = true } = options || {}; + const { includeTraffic = true, language } = options || {}; - if (colorScheme == "system") { - const isDarkMode = window.matchMedia( - "(prefers-color-scheme: dark)" - ).matches; - colorScheme = isDarkMode ? "dark" : "light"; - } + // Always use the light style as the single canonical base style. + colorScheme = "light"; if (styleName == "openfreemap") { const url = `/maps/styles/openfreemap-${colorScheme}.json`; @@ -45,6 +103,16 @@ export async function loadStyle( delete style.sources?.vigo_traffic; } + // Remove the pseudo-3D building-top layer (fill-translate shadow effect). + style.layers = (style.layers || []).filter( + (layer: any) => layer.id !== "building-top" + ); + + // Apply language-aware label expressions. + if (language) { + applyLanguageToStyle(style, language); + } + return style as StyleSpecification; } @@ -106,5 +174,15 @@ export async function loadStyle( } } + // Remove the pseudo-3D building-top layer. + style.layers = (style.layers || []).filter( + (layer: any) => layer.id !== "building-top" + ); + + // Apply language-aware label expressions. + if (language) { + applyLanguageToStyle(style, language); + } + return style as StyleSpecification; } |
