gigafibre-fsm/apps/ops/src/config/basemap.js
louispaulb 042c5545f7 ops: MapLibre/OSM basemap (drop Mapbox) + satellite toggle, unified Créer flow, wizard pricing, infra-first reverse client
- Maps: MapLibre GL + self-hosted Protomaps tiles + ESRI satellite toggle
- FAB 'Créer' chooser (incl. Soumission wizard) via useCreateSignal
- Quote wizard: new default tiers (80/500/1500 @ 39.95/49.95/49.95), competitor
  price-match, mini-map pin w/ green check, carry qualified address
- api/address.reverse() + reverseGeocodeOSM tries RQA-backed hub, Nominatim fallback

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 21:52:14 -04:00

66 lines
4.4 KiB
JavaScript

// 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) {} }