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 = ``;
+ 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.
`;
}