// Fond de carte OSM AUTO-HÉBERGÉ : MapLibre GL + tuiles Protomaps (pmtiles) servies same-origin (/ops/tiles). // Zéro Mapbox → aucun jeton, aucun logo, aucun appel à api.mapbox.com. La seule attribution restante est // « © OpenStreetMap » (exigée par la licence ODbL des données). Routage = notre OSRM ; tuiles = notre pmtiles. import { ref } from 'vue' import maplibregl from 'maplibre-gl' import 'maplibre-gl/dist/maplibre-gl.css' import { Protocol } from 'pmtiles' import { layers, namedTheme } from 'protomaps-themes-base' // Chemin des tuiles = base de l'app + tiles/. Dev : public/tiles/. Prod : /opt/ops-app/tiles/ servi sous /ops/tiles (Traefik strip /ops). export const TILES_URL = `${import.meta.env.BASE_URL || '/'}tiles/southern-quebec.pmtiles` // Glyphes (étiquettes) + sprites (icônes) : assets ouverts Protomaps — pas de Mapbox. {fontstack}/{range} résolus par MapLibre. const GLYPHS = 'https://protomaps.github.io/basemaps-assets/fonts/{fontstack}/{range}.pbf' const SPRITE = (t) => `https://protomaps.github.io/basemaps-assets/sprites/v4/${t}` let _installed = false // Alias maplibregl → window.mapboxgl : l'API (Map/Marker/Popup/LngLatBounds/NavigationControl) est compatible, // donc tout le code existant qui référence window.mapboxgl continue de marcher sans réécriture. Enregistre aussi pmtiles://. export function installMaplibre () { if (!_installed) { const protocol = new Protocol() maplibregl.addProtocol('pmtiles', protocol.tile) window.mapboxgl = maplibregl // compat : marqueurs / popups / bounds existants _installed = true } return maplibregl } // Crée une carte MapLibre de planification avec les réglages COMMUNS (fond OSM + bouton zoom + Street View au clic droit). // Réutilisé par les 4 cartes de PlanificationPage (détail job · sélecteur domicile/dépôt · panneau à assigner · éditeur jour) // → une seule init au lieu de 4 blocs quasi identiques. Les marqueurs/couches spécifiques restent gérés par chaque appelant. export function createPlanMap (el, opts = {}) { installMaplibre() const map = new maplibregl.Map({ container: el, style: basemapStyle(opts.theme || 'light'), center: opts.center || [-73.6756, 45.1599], zoom: opts.zoom != null ? opts.zoom : 9 }) map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-right') if (typeof opts.streetView === 'function') map.on('contextmenu', (e) => opts.streetView(e.lngLat.lng, e.lngLat.lat)) // clic droit → Street View return map } // Style MapLibre (thème Protomaps) : 'dark' pour le board Dispatch (exception mode sombre), 'light' pour les Tournées. export function basemapStyle (theme = 'light') { const th = theme === 'dark' ? 'dark' : 'light' return { version: 8, glyphs: GLYPHS, sprite: SPRITE(th), sources: { protomaps: { type: 'vector', url: 'pmtiles://' + TILES_URL, attribution: '© OpenStreetMap' } }, layers: layers('protomaps', namedTheme(th), { lang: 'fr' }), // namedTheme() = objet flavor (v4) ; la string seule laisse les couleurs undefined } } // ── Couche SATELLITE (ESRI World Imagery — libre, sans jeton). Réutilisable par toutes les cartes createPlanMap. ── const SAT_TILES = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}' // Préférence satellite PARTAGÉE app-wide (même clé que PlanificationPage : 'plan_sat') → cohérente entre toutes les cartes. export const satellitePref = ref((() => { try { return localStorage.getItem('plan_sat') === '1' } catch (e) { return false } })()) export function setSatellitePref (on) { satellitePref.value = !!on; try { localStorage.setItem('plan_sat', on ? '1' : '0') } catch (e) {} } // Ajoute la couche raster satellite (sous les marqueurs, ajoutés APRÈS) + applique la visibilité. Idempotent. export function addSatelliteLayer (map, visible) { try { if (!map.getSource('sat')) map.addSource('sat', { type: 'raster', tiles: [SAT_TILES], tileSize: 256, attribution: '© Esri, Maxar, Earthstar Geographics' }) if (!map.getLayer('sat')) map.addLayer({ id: 'sat', type: 'raster', source: 'sat', layout: { visibility: visible ? 'visible' : 'none' } }) else map.setLayoutProperty('sat', 'visibility', visible ? 'visible' : 'none') } catch (e) { /* style pas prêt */ } } export function setSatelliteVisible (map, on) { try { if (map && map.getLayer && map.getLayer('sat')) map.setLayoutProperty('sat', 'visibility', on ? 'visible' : 'none') } catch (e) {} }