fix(carte): fond de carte avec clé CARTO, repli Plan IGN sans clé

CARTO Basemaps exige une clé depuis le 23/09/2026 : sans elle, chaque tuile
porte le filigrane « API KEY REQUIRED ». Clé lue au runtime
(NUXT_PUBLIC_CARTO_KEY) ; sans clé, repli sur le Plan IGN (Géoplateforme).
FicheDetail quitte aussi les tuiles OSM (418, politique d'usage).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jules
2026-09-24 12:00:49 +02:00
co-authored by Claude Opus 5.5
parent 946ed53aba
commit b5aca80bbc
6 changed files with 38 additions and 19 deletions
+2 -1
View File
@@ -183,6 +183,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const baseTiles = useBaseTiles()
import type { Org } from '~/types/org' import type { Org } from '~/types/org'
const props = defineProps<{ org: Org }>() const props = defineProps<{ org: Org }>()
@@ -283,7 +284,7 @@ onMounted(async () => {
attributionControl: false, attributionControl: false,
}) })
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { L.tileLayer(baseTiles.url(document.documentElement.classList.contains('dark')), {
maxZoom: 18, maxZoom: 18,
}).addTo(map) }).addTo(map)
+4 -7
View File
@@ -5,6 +5,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const baseTiles = useBaseTiles()
import type { Map, Marker, DivIcon } from 'leaflet' import type { Map, Marker, DivIcon } from 'leaflet'
interface Org { interface Org {
@@ -90,12 +91,10 @@ async function initMap() {
// Fond de carte CartoDB Positron (light ou dark selon theme) // Fond de carte CartoDB Positron (light ou dark selon theme)
const isDark = typeof document !== 'undefined' && document.documentElement.classList.contains('dark') const isDark = typeof document !== 'undefined' && document.documentElement.classList.contains('dark')
const tileUrl = isDark const tileUrl = baseTiles.url(isDark)
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
tileLayerInstance = L.tileLayer(tileUrl, { tileLayerInstance = L.tileLayer(tileUrl, {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>', attribution: baseTiles.attribution,
maxZoom: 19, maxZoom: 19,
}) })
tileLayerInstance.addTo(mapInstance!) tileLayerInstance.addTo(mapInstance!)
@@ -212,9 +211,7 @@ function updateTileTheme(dark: boolean) {
if (!mapInstance || !tileLayerInstance) return if (!mapInstance || !tileLayerInstance) return
const L = (window as any).L const L = (window as any).L
if (!L) return if (!L) return
const url = dark const url = baseTiles.url(dark)
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
tileLayerInstance.setUrl(url) tileLayerInstance.setUrl(url)
} }
+4 -7
View File
@@ -5,6 +5,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const baseTiles = useBaseTiles()
import type { Map, Marker, DivIcon } from 'leaflet' import type { Map, Marker, DivIcon } from 'leaflet'
import type { StructureV2 } from '~/types/structure-v2' import type { StructureV2 } from '~/types/structure-v2'
@@ -87,12 +88,10 @@ async function initMap() {
}) })
const isDark = typeof document !== 'undefined' && document.documentElement.classList.contains('dark') const isDark = typeof document !== 'undefined' && document.documentElement.classList.contains('dark')
const tileUrl = isDark const tileUrl = baseTiles.url(isDark)
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
tileLayerInstance = L.tileLayer(tileUrl, { tileLayerInstance = L.tileLayer(tileUrl, {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>', attribution: baseTiles.attribution,
maxZoom: 19, maxZoom: 19,
}) })
tileLayerInstance.addTo(mapInstance!) tileLayerInstance.addTo(mapInstance!)
@@ -213,9 +212,7 @@ function updateTileTheme(dark: boolean) {
if (!mapInstance || !tileLayerInstance) return if (!mapInstance || !tileLayerInstance) return
const L = (window as any).L const L = (window as any).L
if (!L) return if (!L) return
const url = dark const url = baseTiles.url(dark)
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
tileLayerInstance.setUrl(url) tileLayerInstance.setUrl(url)
} }
+3 -4
View File
@@ -38,6 +38,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const baseTiles = useBaseTiles()
import type { Map as LeafletMap, TileLayer } from 'leaflet' import type { Map as LeafletMap, TileLayer } from 'leaflet'
interface Org { interface Org {
@@ -111,9 +112,7 @@ function createPinIcon(L: any, isPrioritaire: boolean, isSelected = false) {
} }
function getTileUrl(dark: boolean) { function getTileUrl(dark: boolean) {
return dark return baseTiles.url(dark)
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
} }
async function initSingleMap(domName: string) { async function initSingleMap(domName: string) {
@@ -132,7 +131,7 @@ async function initSingleMap(domName: string) {
touchZoom: true, keyboard: false, touchZoom: true, keyboard: false,
}) })
const tileLayer = L.tileLayer(getTileUrl(isDark), { const tileLayer = L.tileLayer(getTileUrl(isDark), {
attribution: '© OpenStreetMap contributors © CARTO', maxZoom: 19, attribution: baseTiles.attribution, maxZoom: 19,
}) })
tileLayer.addTo(map) tileLayer.addTo(map)
tileLayers[domName] = tileLayer as unknown as TileLayer tileLayers[domName] = tileLayer as unknown as TileLayer
+22
View File
@@ -0,0 +1,22 @@
// Fond de carte commun aux cartes Leaflet.
// CARTO exige une clé depuis le 23/09/2026 (sans clé : filigrane « API KEY REQUIRED »).
// Clé fournie au runtime par NUXT_PUBLIC_CARTO_KEY ; sans clé, repli sur le Plan IGN (Géoplateforme, sans clé).
const IGN_URL = 'https://data.geopf.fr/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0'
+ '&LAYER=GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2&STYLE=normal&TILEMATRIXSET=PM_0_19&FORMAT=image/png'
+ '&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}'
export function useBaseTiles() {
const key = useRuntimeConfig().public.cartoKey as string
function url(dark: boolean) {
if (!key) return IGN_URL
const style = dark ? 'dark_all' : 'light_all'
return `https://{s}.basemaps.cartocdn.com/${style}/{z}/{x}/{y}{r}.png?key=${encodeURIComponent(key)}`
}
const attribution = key
? '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
: '© <a href="https://www.ign.fr/">IGN</a> – Géoplateforme'
return { url, attribution }
}
+3
View File
@@ -27,6 +27,9 @@ export default defineNuxtConfig({
codevAdminPassword: 'admin2026', // NUXT_CODEV_ADMIN_PASSWORD codevAdminPassword: 'admin2026', // NUXT_CODEV_ADMIN_PASSWORD
ragPeUrl: process.env.RAG_PE_URL || 'http://localhost:9621', ragPeUrl: process.env.RAG_PE_URL || 'http://localhost:9621',
referencesTableId: process.env.REFERENCES_TABLE_ID || process.env.NUXT_REFERENCES_TABLE_ID || '', referencesTableId: process.env.REFERENCES_TABLE_ID || process.env.NUXT_REFERENCES_TABLE_ID || '',
public: {
cartoKey: '', // NUXT_PUBLIC_CARTO_KEY — clé CARTO Basemaps (gratuite), lue au runtime
},
}, },
// Leaflet ne fonctionne pas en SSR — forcer le rendu côté client // Leaflet ne fonctionne pas en SSR — forcer le rendu côté client