diff --git a/loko/home/static/home/home_index.js b/loko/home/static/home/home_index.js index 3fff8d0..c6fca9d 100644 --- a/loko/home/static/home/home_index.js +++ b/loko/home/static/home/home_index.js @@ -50,7 +50,82 @@ if (mapContainer && typeof maplibregl !== 'undefined') { let currentMode = 'heatmap'; // 'heatmap' or 'points' + const OPERATION_ICONS = { + 'intervention': { + svg: '', + codepoint: 'f5db' + }, + 'observation': { + svg: '', + codepoint: 'f33e' + }, + 'inspection': { + svg: '', + codepoint: 'f52a' + }, + 'control': { + svg: '', + codepoint: 'f28b' + } + }; + + function registerOperationIcon(mapInstance, opKey, iconInfo, size = 32) { + const imageName = `op-icon-${opKey}`; + if (mapInstance.hasImage(imageName)) return Promise.resolve(); + + return new Promise((resolve) => { + const img = new Image(); + const svg = `${iconInfo.svg}`; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, size, size); + + // Ombre douce pour faire ressortir l'icône blanche sur n'importe quel fond thématique + ctx.shadowColor = 'rgba(0, 0, 0, 0.45)'; + ctx.shadowBlur = 2.5; + ctx.shadowOffsetY = 1; + + ctx.drawImage(img, 0, 0, size, size); + const imgData = ctx.getImageData(0, 0, size, size); + if (!mapInstance.hasImage(imageName)) { + mapInstance.addImage(imageName, imgData); + } + resolve(); + }; + img.onerror = () => { + // Fallback dessin via police bootstrap-icons + const canvas = document.createElement('canvas'); + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, size, size); + ctx.font = `${Math.round(size * 0.62)}px "bootstrap-icons"`; + ctx.fillStyle = '#ffffff'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.shadowColor = 'rgba(0, 0, 0, 0.45)'; + ctx.shadowBlur = 2.5; + ctx.shadowOffsetY = 1; + ctx.fillText(String.fromCodePoint(parseInt(iconInfo.codepoint, 16)), size / 2, size / 2); + const imgData = ctx.getImageData(0, 0, size, size); + if (!mapInstance.hasImage(imageName)) { + mapInstance.addImage(imageName, imgData); + } + resolve(); + }; + img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg); + }); + } + map.on('load', async () => { + // 0. Enregistrer les icônes d'opérations (symboles à l'intérieur des points) + await Promise.all( + Object.entries(OPERATION_ICONS).map(([key, info]) => registerOperationIcon(map, key, info, 32)) + ); + // 1. Charger le masque de mise en lumière du territoire communal try { const maskResp = await fetch('/common/territory-mask-geojson/'); @@ -196,8 +271,7 @@ if (mapContainer && typeof maplibregl !== 'undefined') { } }); - // Layer B: Points individuels avec couleur selon la typologie - // En mode Heatmap: émergent progressivement lorsque la heatmap s'estompe ! + // Layer B: Cercles représentant les points avec couleur selon la thématique map.addLayer({ id: 'activity-points', type: 'circle', @@ -208,9 +282,10 @@ if (mapContainer && typeof maplibregl !== 'undefined') { paint: { 'circle-radius': [ 'interpolate', ['linear'], ['zoom'], - 10, 5, - 14, 8, - 18, 14 + 10, 6.5, + 13, 9.5, + 15, 12.5, + 18, 17 ], 'circle-color': [ 'match', @@ -224,7 +299,7 @@ if (mapContainer && typeof maplibregl !== 'undefined') { ], 'circle-stroke-width': 2, 'circle-stroke-color': '#ffffff', - // Transition d'opacité fluide: invisibles au grand zoom, émergent dès zoom 13.5 + // Transition d'opacité fluide: invisibles au grand zoom, émergent dès zoom 13.2 'circle-opacity': [ 'interpolate', ['linear'], ['zoom'], 13.2, 0, @@ -238,7 +313,47 @@ if (mapContainer && typeof maplibregl !== 'undefined') { } }); - renderLegend(); + // Layer C: Symboles de type d'opération positionnés à l'intérieur des cercles + map.addLayer({ + id: 'activity-points-icons', + type: 'symbol', + source: 'activity-data', + layout: { + 'visibility': 'visible', + 'icon-image': [ + 'match', + ['get', 'type'], + 'intervention', 'op-icon-intervention', + 'observation', 'op-icon-observation', + 'inspection', 'op-icon-inspection', + 'control', 'op-icon-control', + 'op-icon-intervention' + ], + 'icon-size': [ + 'interpolate', ['linear'], ['zoom'], + 10, 0.38, + 13, 0.55, + 15, 0.72, + 18, 1.0 + ], + 'icon-allow-overlap': true, + 'icon-ignore-placement': true, + 'icon-anchor': 'center' + }, + paint: { + 'icon-opacity': [ + 'interpolate', ['linear'], ['zoom'], + 13.2, 0, + 14.8, 1 + ] + } + }); + + if (currentMode === 'points') { + setMode('points'); + } else { + renderLegend(); + } } } catch (e) { console.error("Could not load activity geojson:", e); @@ -278,6 +393,14 @@ if (mapContainer && typeof maplibregl !== 'undefined') { 14.8, 1 ]); } + if (map.getLayer('activity-points-icons')) { + map.setLayoutProperty('activity-points-icons', 'visibility', 'visible'); + map.setPaintProperty('activity-points-icons', 'icon-opacity', [ + 'interpolate', ['linear'], ['zoom'], + 13.2, 0, + 14.8, 1 + ]); + } } else { // Mode Points pur btnPoints.classList.add('btn-primary', 'active'); @@ -293,6 +416,10 @@ if (mapContainer && typeof maplibregl !== 'undefined') { map.setPaintProperty('activity-points', 'circle-opacity', 0.95); map.setPaintProperty('activity-points', 'circle-stroke-opacity', 1); } + if (map.getLayer('activity-points-icons')) { + map.setLayoutProperty('activity-points-icons', 'visibility', 'visible'); + map.setPaintProperty('activity-points-icons', 'icon-opacity', 1); + } } renderLegend(); } @@ -302,12 +429,12 @@ if (mapContainer && typeof maplibregl !== 'undefined') { // Clic pour afficher les détails du point map.on('click', function(e) { - const radiusInPixels = 12; + const radiusInPixels = 14; const features = map.queryRenderedFeatures([ [e.point.x - radiusInPixels, e.point.y - radiusInPixels], [e.point.x + radiusInPixels, e.point.y + radiusInPixels] ], { - layers: ['activity-points', 'activity-heatmap'] + layers: ['activity-points', 'activity-points-icons', 'activity-heatmap'] }); if (!features.length) return; @@ -358,6 +485,8 @@ if (mapContainer && typeof maplibregl !== 'undefined') { map.on('mouseenter', 'activity-points', () => { map.getCanvas().style.cursor = 'pointer'; }); map.on('mouseleave', 'activity-points', () => { map.getCanvas().style.cursor = ''; }); + map.on('mouseenter', 'activity-points-icons', () => { map.getCanvas().style.cursor = 'pointer'; }); + map.on('mouseleave', 'activity-points-icons', () => { map.getCanvas().style.cursor = ''; }); function renderLegend() { const legendContainer = document.getElementById('activity-map-legend') || document.getElementById('map-legend'); @@ -383,11 +512,11 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
Ouvrages & Bâtiments
Opérations (symboles)
-
- Interv. - Obs. - Insp. - Ctrl. +
+ Interv. + Obs. + Insp. + Ctrl.
`; }