From 64568ba6859c1e4d02b39897c9ad7ea86e720362 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 26 Sep 2026 12:14:41 +0000 Subject: [PATCH] My UPG Neighbors: the flat projection is applied on MapLibre too Four lenses (send-map, lang-cluster, the cockpit's MapCell and FlowerClusters) called setProjectionOn(m, 'mercator') straight after initializeMap(). MapLibre throws "Style is not done loading" from setProjection before the style loads; setProjectionOn swallows it and returns false, so on MapLibre the projection was never set (rega, C2 at 0f136d25). Mapbox accepts it early, which is why only one engine showed it. A small plugin helper, src/whenStyleReady.js, runs the call at once if the style has loaded, else on 'style.load'. Headless, against a MapLibre-shaped map that throws before load, with the real setProjectionOn: before, it returns false and the projection stays null; after, it is mercator, both when the style loads later and when it is already loaded. --- .../upg-neighbor/src/cockpit/FlowerClusters.vue | 4 +++- plugin-designs/upg-neighbor/src/cockpit/MapCell.vue | 4 +++- plugin-designs/upg-neighbor/src/lang-cluster.vue | 4 +++- plugin-designs/upg-neighbor/src/send-map.vue | 4 +++- plugin-designs/upg-neighbor/src/whenStyleReady.js | 13 +++++++++++++ 5 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 plugin-designs/upg-neighbor/src/whenStyleReady.js diff --git a/plugin-designs/upg-neighbor/src/cockpit/FlowerClusters.vue b/plugin-designs/upg-neighbor/src/cockpit/FlowerClusters.vue index 776e19f54..8a4e851f2 100644 --- a/plugin-designs/upg-neighbor/src/cockpit/FlowerClusters.vue +++ b/plugin-designs/upg-neighbor/src/cockpit/FlowerClusters.vue @@ -33,6 +33,7 @@ import langFamilyByLanguage from '@map/data/langFamilyByLanguage.json' import bakedReached from '../data/reached-neighbors.json' import { useNeighborT } from '../composables/useNeighborT.js' import { setProjectionOn } from '@map/composables/useMapProvider.js' +import { whenStyleReady } from '../whenStyleReady.js' const nt = useNeighborT() const C_RED = '#e11d48', C_RED_DK = '#9f1239', C_NAVY = '#002e6e', C_GOLD = '#f59e0b' @@ -279,7 +280,8 @@ async function fetchDoxa() { onMounted(async () => { await initializeMap() const m = map.value - setProjectionOn(m, 'mercator') // flat mercator, never globe (Driver); per-engine shape via C2 helper + // flat mercator, never globe (Driver); applied once the style is ready (MapLibre throws before) + whenStyleReady(m, () => setProjectionOn(m, 'mercator')) try { doxaFeatures.value = await fetchDoxa() } catch (e) { statusMsg.value = 'Live doxa API unreachable — ' + e.message; appReady.value = true; return } appReady.value = true diff --git a/plugin-designs/upg-neighbor/src/cockpit/MapCell.vue b/plugin-designs/upg-neighbor/src/cockpit/MapCell.vue index 75e58b847..c23f8823d 100644 --- a/plugin-designs/upg-neighbor/src/cockpit/MapCell.vue +++ b/plugin-designs/upg-neighbor/src/cockpit/MapCell.vue @@ -23,6 +23,7 @@ import { useMapInstance } from '@map/composables/useMapInstance.js' import { useShadowStyles } from '@map/composables/useShadowStyles.js' import { useCockpitStore, COLORS, redColor, reachedColor, logRadius, fmtPop } from './useCockpit.js' import { setProjectionOn } from '@map/composables/useMapProvider.js' +import { whenStyleReady } from '../whenStyleReady.js' const store = useCockpitStore() @@ -365,7 +366,8 @@ onMounted(async () => { await initializeMap() const m = map.value if (!m) { return } // no/invalid token — overlay shows the hint, store still drives the rest - setProjectionOn(m, 'mercator') // flat mercator, never globe (Driver); per-engine shape via C2 helper + // flat mercator, never globe (Driver); applied once the style is ready (MapLibre throws before) + whenStyleReady(m, () => setProjectionOn(m, 'mercator')) // Gated test hook (inert in production): lets a harness use the live map.project(). if (cfg.value.debugExpose && typeof window !== 'undefined') { diff --git a/plugin-designs/upg-neighbor/src/lang-cluster.vue b/plugin-designs/upg-neighbor/src/lang-cluster.vue index 65814014c..22c74d071 100644 --- a/plugin-designs/upg-neighbor/src/lang-cluster.vue +++ b/plugin-designs/upg-neighbor/src/lang-cluster.vue @@ -389,6 +389,7 @@ function onMove(ev) { if (tip.value.show) { tip.value.x = ev.clientX; tip.value. import bakedReached from './data/reached-neighbors.json' import bakedAdmin1 from './data/admin1-membership.json' import { setProjectionOn } from '@map/composables/useMapProvider.js' +import { whenStyleReady } from './whenStyleReady.js' async function loadJSON(relCandidates) { for (const url of relCandidates) { try { const r = await fetch(url); if (r.ok) return await r.json() } catch (_) {} @@ -411,7 +412,8 @@ async function fetchDoxa() { onMounted(async () => { await initializeMap() const m = map.value - setProjectionOn(m, 'mercator') // flat mercator, never globe (Driver); per-engine shape via C2 helper + // flat mercator, never globe (Driver); applied once the style is ready (MapLibre throws before) + whenStyleReady(m, () => setProjectionOn(m, 'mercator')) // baked data (own bundle dir). `base` lets the host say where data/ is served from // (platform deploy serves data/ alongside the bundle, like send-map/flower-mesh). diff --git a/plugin-designs/upg-neighbor/src/send-map.vue b/plugin-designs/upg-neighbor/src/send-map.vue index c45de648c..dd2d02ebc 100644 --- a/plugin-designs/upg-neighbor/src/send-map.vue +++ b/plugin-designs/upg-neighbor/src/send-map.vue @@ -402,6 +402,7 @@ function flyToSender(n) { // profile-config `dataUrl` still overrides live. import bakedReached from './data/reached-neighbors.json' import { setProjectionOn } from '@map/composables/useMapProvider.js' +import { whenStyleReady } from './whenStyleReady.js' async function loadDataset() { const cfgUrl = profileConfig?.value?.dataUrl if (cfgUrl) { @@ -413,7 +414,8 @@ async function loadDataset() { onMounted(async () => { await initializeMap() const m = map.value - setProjectionOn(m, 'mercator') // flat mercator, never globe (Driver); per-engine shape via C2 helper + // flat mercator, never globe (Driver); applied once the style is ready (MapLibre throws before) + whenStyleReady(m, () => setProjectionOn(m, 'mercator')) // Test-only hook (inert in production — platform never sets debugExpose). Lets an // automated harness use the live map.project() to click exact circle positions. diff --git a/plugin-designs/upg-neighbor/src/whenStyleReady.js b/plugin-designs/upg-neighbor/src/whenStyleReady.js new file mode 100644 index 000000000..b7fe594d2 --- /dev/null +++ b/plugin-designs/upg-neighbor/src/whenStyleReady.js @@ -0,0 +1,13 @@ +/** + * whenStyleReady.js — run once the map's style has loaded (immediately if it already has). + * + * MapLibre throws "Style is not done loading" from setProjection before that, and setProjectionOn + * swallows the throw, so the lenses that set 'mercator' right after initializeMap() never got it on + * MapLibre (rega, C2: lang-cluster, send-map, the bloom cockpit's MapCell). Mapbox accepts it early, + * which is why this only showed on one engine. + */ +export function whenStyleReady(map, fn) { + if (!map) return + if (typeof map.isStyleLoaded === 'function' && map.isStyleLoaded()) fn() + else map.once('style.load', fn) +}