style(home): refine activity symbol sizes and prevent marker overlap with collision detection
This commit is contained in:
parent
f534758f91
commit
c726d8f37c
2 changed files with 167 additions and 138 deletions
|
|
@ -49,6 +49,16 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
map.addControl(new maplibregl.NavigationControl(), 'top-left');
|
map.addControl(new maplibregl.NavigationControl(), 'top-left');
|
||||||
|
|
||||||
let currentMode = 'heatmap'; // 'heatmap' or 'points'
|
let currentMode = 'heatmap'; // 'heatmap' or 'points'
|
||||||
|
let activityGeojsonData = null;
|
||||||
|
|
||||||
|
const THEMATIC_COLORS = {
|
||||||
|
'nature': '#10b981',
|
||||||
|
'lighting': '#f59e0b',
|
||||||
|
'sign': '#3b82f6',
|
||||||
|
'roads': '#06b6d4',
|
||||||
|
'structures': '#8b5cf6',
|
||||||
|
'default': '#64748b'
|
||||||
|
};
|
||||||
|
|
||||||
const OPERATION_ICONS = {
|
const OPERATION_ICONS = {
|
||||||
'intervention': {
|
'intervention': {
|
||||||
|
|
@ -69,59 +79,90 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function registerOperationIcon(mapInstance, opKey, iconInfo, size = 32) {
|
function registerOperationBadge(mapInstance, thematicKey, thematicColor, opKey, iconInfo) {
|
||||||
const imageName = `op-icon-${opKey}`;
|
const imageName = `marker-${thematicKey}-${opKey}`;
|
||||||
if (mapInstance.hasImage(imageName)) return Promise.resolve();
|
if (mapInstance.hasImage(imageName)) return Promise.resolve();
|
||||||
|
|
||||||
|
const canvasSize = 44;
|
||||||
|
const center = canvasSize / 2;
|
||||||
|
const radius = 16;
|
||||||
|
const iconSize = 19;
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="-2.5 -2.5 21 21" fill="#ffffff">${iconInfo.svg}</svg>`;
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${iconSize}" height="${iconSize}" viewBox="-2.5 -2.5 21 21" fill="#ffffff">${iconInfo.svg}</svg>`;
|
||||||
|
|
||||||
|
function drawBadge(ctx, drawIconFn) {
|
||||||
|
ctx.clearRect(0, 0, canvasSize, canvasSize);
|
||||||
|
|
||||||
|
// 1. Ombre subtile sous le badge pour décoller du fond cartographique clair
|
||||||
|
ctx.shadowColor = 'rgba(0, 0, 0, 0.30)';
|
||||||
|
ctx.shadowBlur = 3.5;
|
||||||
|
ctx.shadowOffsetY = 1.2;
|
||||||
|
|
||||||
|
// 2. Disque plein de la couleur de la thématique
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(center, center, radius, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = thematicColor;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// 3. Bordure blanche nette
|
||||||
|
ctx.shadowColor = 'transparent';
|
||||||
|
ctx.lineWidth = 2.2;
|
||||||
|
ctx.strokeStyle = '#ffffff';
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 4. Dessin de l'icône au centre
|
||||||
|
drawIconFn(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = size;
|
canvas.width = canvasSize;
|
||||||
canvas.height = size;
|
canvas.height = canvasSize;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.clearRect(0, 0, size, size);
|
drawBadge(ctx, (c) => {
|
||||||
|
c.drawImage(img, center - (iconSize / 2), center - (iconSize / 2), iconSize, iconSize);
|
||||||
// Ombre douce pour faire ressortir l'icône blanche sur n'importe quel fond thématique
|
});
|
||||||
ctx.shadowColor = 'rgba(0, 0, 0, 0.45)';
|
const imgData = ctx.getImageData(0, 0, canvasSize, canvasSize);
|
||||||
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)) {
|
if (!mapInstance.hasImage(imageName)) {
|
||||||
mapInstance.addImage(imageName, imgData);
|
mapInstance.addImage(imageName, imgData, { pixelRatio: 2 });
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
// Fallback dessin via police bootstrap-icons
|
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = size;
|
canvas.width = canvasSize;
|
||||||
canvas.height = size;
|
canvas.height = canvasSize;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.clearRect(0, 0, size, size);
|
drawBadge(ctx, (c) => {
|
||||||
ctx.font = `${Math.round(size * 0.62)}px "bootstrap-icons"`;
|
c.font = '12px "bootstrap-icons"';
|
||||||
ctx.fillStyle = '#ffffff';
|
c.fillStyle = '#ffffff';
|
||||||
ctx.textAlign = 'center';
|
c.textAlign = 'center';
|
||||||
ctx.textBaseline = 'middle';
|
c.textBaseline = 'middle';
|
||||||
ctx.shadowColor = 'rgba(0, 0, 0, 0.45)';
|
c.fillText(String.fromCodePoint(parseInt(iconInfo.codepoint, 16)), center, center);
|
||||||
ctx.shadowBlur = 2.5;
|
});
|
||||||
ctx.shadowOffsetY = 1;
|
const imgData = ctx.getImageData(0, 0, canvasSize, canvasSize);
|
||||||
ctx.fillText(String.fromCodePoint(parseInt(iconInfo.codepoint, 16)), size / 2, size / 2);
|
|
||||||
const imgData = ctx.getImageData(0, 0, size, size);
|
|
||||||
if (!mapInstance.hasImage(imageName)) {
|
if (!mapInstance.hasImage(imageName)) {
|
||||||
mapInstance.addImage(imageName, imgData);
|
mapInstance.addImage(imageName, imgData, { pixelRatio: 2 });
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
|
img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
map.on('load', async () => {
|
map.on('load', async () => {
|
||||||
// Enregistrer les icônes d'opérations et lancer les requêtes GeoJSON en parallèle (gain réseau immédiat)
|
// Enregistrer les badges d'opérations et lancer les requêtes GeoJSON en parallèle (gain réseau immédiat)
|
||||||
|
const badgePromises = [];
|
||||||
|
for (const [tKey, tColor] of Object.entries(THEMATIC_COLORS)) {
|
||||||
|
for (const [opKey, iconInfo] of Object.entries(OPERATION_ICONS)) {
|
||||||
|
badgePromises.push(registerOperationBadge(map, tKey, tColor, opKey, iconInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const [maskData, actData] = await Promise.all([
|
const [maskData, actData] = await Promise.all([
|
||||||
fetch('/common/territory-mask-geojson/')
|
fetch('/common/territory-mask-geojson/')
|
||||||
.then(r => r.ok ? r.json() : null)
|
.then(r => r.ok ? r.json() : null)
|
||||||
|
|
@ -129,9 +170,11 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
fetch('/api/activity-geojson/')
|
fetch('/api/activity-geojson/')
|
||||||
.then(r => r.ok ? r.json() : null)
|
.then(r => r.ok ? r.json() : null)
|
||||||
.catch(e => { console.error("Could not load activity geojson:", e); return null; }),
|
.catch(e => { console.error("Could not load activity geojson:", e); return null; }),
|
||||||
...Object.entries(OPERATION_ICONS).map(([key, info]) => registerOperationIcon(map, key, info, 32))
|
...badgePromises
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
activityGeojsonData = actData;
|
||||||
|
|
||||||
// 1. Appliquer le masque de mise en lumière du territoire communal
|
// 1. Appliquer le masque de mise en lumière du territoire communal
|
||||||
if (maskData) {
|
if (maskData) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -258,7 +301,7 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
13, 26,
|
13, 26,
|
||||||
16, 42
|
16, 42
|
||||||
],
|
],
|
||||||
// En mode Heatmap, l'intensité s'estompe en zoomant
|
// En mode Heatmap, l'intensité s'estompe en zoomant vers la rue
|
||||||
'heatmap-opacity': [
|
'heatmap-opacity': [
|
||||||
'interpolate', ['linear'], ['zoom'],
|
'interpolate', ['linear'], ['zoom'],
|
||||||
13, 0.85,
|
13, 0.85,
|
||||||
|
|
@ -267,80 +310,52 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Layer B: Cercles représentant les points avec couleur selon la thématique
|
// Layer B: Symboles d'opérations (disque thématique + icône intégrée)
|
||||||
|
// Taille affinée et non-superposition stricte via le moteur de collision MapLibre
|
||||||
map.addLayer({
|
map.addLayer({
|
||||||
id: 'activity-points',
|
id: 'activity-points',
|
||||||
type: 'circle',
|
|
||||||
source: 'activity-data',
|
|
||||||
layout: {
|
|
||||||
'visibility': 'visible'
|
|
||||||
},
|
|
||||||
paint: {
|
|
||||||
'circle-radius': [
|
|
||||||
'interpolate', ['linear'], ['zoom'],
|
|
||||||
10, 6.5,
|
|
||||||
13, 9.5,
|
|
||||||
15, 12.5,
|
|
||||||
18, 17
|
|
||||||
],
|
|
||||||
'circle-color': [
|
|
||||||
'match',
|
|
||||||
['get', 'thematic_key'],
|
|
||||||
'nature', '#10b981',
|
|
||||||
'lighting', '#f59e0b',
|
|
||||||
'sign', '#3b82f6',
|
|
||||||
'roads', '#06b6d4',
|
|
||||||
'structures', '#8b5cf6',
|
|
||||||
'#64748b'
|
|
||||||
],
|
|
||||||
'circle-stroke-width': 2,
|
|
||||||
'circle-stroke-color': '#ffffff',
|
|
||||||
// Transition d'opacité fluide: invisibles au grand zoom, émergent dès zoom 13.2
|
|
||||||
'circle-opacity': [
|
|
||||||
'interpolate', ['linear'], ['zoom'],
|
|
||||||
13.2, 0,
|
|
||||||
14.8, 0.95
|
|
||||||
],
|
|
||||||
'circle-stroke-opacity': [
|
|
||||||
'interpolate', ['linear'], ['zoom'],
|
|
||||||
13.2, 0,
|
|
||||||
14.8, 1
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Layer C: Symboles de type d'opération positionnés à l'intérieur des cercles
|
|
||||||
map.addLayer({
|
|
||||||
id: 'activity-points-icons',
|
|
||||||
type: 'symbol',
|
type: 'symbol',
|
||||||
source: 'activity-data',
|
source: 'activity-data',
|
||||||
layout: {
|
layout: {
|
||||||
'visibility': 'visible',
|
'visibility': 'visible',
|
||||||
'icon-image': [
|
'icon-image': [
|
||||||
'match',
|
'concat',
|
||||||
['get', 'type'],
|
'marker-',
|
||||||
'intervention', 'op-icon-intervention',
|
['match', ['get', 'thematic_key'],
|
||||||
'observation', 'op-icon-observation',
|
'nature', 'nature',
|
||||||
'inspection', 'op-icon-inspection',
|
'lighting', 'lighting',
|
||||||
'control', 'op-icon-control',
|
'sign', 'sign',
|
||||||
'op-icon-intervention'
|
'roads', 'roads',
|
||||||
|
'structures', 'structures',
|
||||||
|
'default'
|
||||||
|
],
|
||||||
|
'-',
|
||||||
|
['match', ['get', 'type'],
|
||||||
|
'intervention', 'intervention',
|
||||||
|
'observation', 'observation',
|
||||||
|
'inspection', 'inspection',
|
||||||
|
'control', 'control',
|
||||||
|
'intervention'
|
||||||
|
]
|
||||||
],
|
],
|
||||||
'icon-size': [
|
'icon-size': [
|
||||||
'interpolate', ['linear'], ['zoom'],
|
'interpolate', ['linear'], ['zoom'],
|
||||||
10, 0.38,
|
10, 0.58, // ~13px
|
||||||
13, 0.55,
|
13, 0.72, // ~16px
|
||||||
15, 0.72,
|
15, 0.88, // ~19px
|
||||||
18, 1.0
|
18, 1.10 // ~24px
|
||||||
],
|
],
|
||||||
'icon-allow-overlap': true,
|
'icon-allow-overlap': false,
|
||||||
'icon-ignore-placement': true,
|
'icon-ignore-placement': false,
|
||||||
|
'icon-padding': 3,
|
||||||
'icon-anchor': 'center'
|
'icon-anchor': 'center'
|
||||||
},
|
},
|
||||||
paint: {
|
paint: {
|
||||||
|
// En mode Heatmap : les symboles s'estompent à distance pour dégager la vue thermique
|
||||||
'icon-opacity': [
|
'icon-opacity': [
|
||||||
'interpolate', ['linear'], ['zoom'],
|
'interpolate', ['linear'], ['zoom'],
|
||||||
13.2, 0,
|
14.2, 0,
|
||||||
14.8, 1
|
15.4, 1
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -378,27 +393,14 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
}
|
}
|
||||||
if (map.getLayer('activity-points')) {
|
if (map.getLayer('activity-points')) {
|
||||||
map.setLayoutProperty('activity-points', 'visibility', 'visible');
|
map.setLayoutProperty('activity-points', 'visibility', 'visible');
|
||||||
map.setPaintProperty('activity-points', 'circle-opacity', [
|
map.setPaintProperty('activity-points', 'icon-opacity', [
|
||||||
'interpolate', ['linear'], ['zoom'],
|
'interpolate', ['linear'], ['zoom'],
|
||||||
13.2, 0,
|
14.2, 0,
|
||||||
14.8, 0.95
|
15.4, 1
|
||||||
]);
|
|
||||||
map.setPaintProperty('activity-points', 'circle-stroke-opacity', [
|
|
||||||
'interpolate', ['linear'], ['zoom'],
|
|
||||||
13.2, 0,
|
|
||||||
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 {
|
} else {
|
||||||
// Mode Points pur
|
// Mode Points pur : affichage net de tous les symboles sans heatmap
|
||||||
btnPoints.classList.add('btn-primary', 'active');
|
btnPoints.classList.add('btn-primary', 'active');
|
||||||
btnPoints.classList.remove('btn-outline-primary');
|
btnPoints.classList.remove('btn-outline-primary');
|
||||||
btnHeatmap.classList.remove('btn-primary', 'active');
|
btnHeatmap.classList.remove('btn-primary', 'active');
|
||||||
|
|
@ -409,12 +411,7 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
}
|
}
|
||||||
if (map.getLayer('activity-points')) {
|
if (map.getLayer('activity-points')) {
|
||||||
map.setLayoutProperty('activity-points', 'visibility', 'visible');
|
map.setLayoutProperty('activity-points', 'visibility', 'visible');
|
||||||
map.setPaintProperty('activity-points', 'circle-opacity', 0.95);
|
map.setPaintProperty('activity-points', 'icon-opacity', 1);
|
||||||
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();
|
renderLegend();
|
||||||
|
|
@ -425,12 +422,12 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
|
|
||||||
// Clic pour afficher les détails du point
|
// Clic pour afficher les détails du point
|
||||||
map.on('click', function(e) {
|
map.on('click', function(e) {
|
||||||
const radiusInPixels = 14;
|
const radiusInPixels = 12;
|
||||||
const features = map.queryRenderedFeatures([
|
const features = map.queryRenderedFeatures([
|
||||||
[e.point.x - radiusInPixels, e.point.y - radiusInPixels],
|
[e.point.x - radiusInPixels, e.point.y - radiusInPixels],
|
||||||
[e.point.x + radiusInPixels, e.point.y + radiusInPixels]
|
[e.point.x + radiusInPixels, e.point.y + radiusInPixels]
|
||||||
], {
|
], {
|
||||||
layers: ['activity-points', 'activity-points-icons', 'activity-heatmap']
|
layers: ['activity-points']
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!features.length) return;
|
if (!features.length) return;
|
||||||
|
|
@ -439,6 +436,17 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
const p = f.properties;
|
const p = f.properties;
|
||||||
if (!p || !p.title) return;
|
if (!p || !p.title) return;
|
||||||
|
|
||||||
|
// Trouver toutes les opérations situées exactement à la même adresse (si présentes)
|
||||||
|
const coords = f.geometry && f.geometry.coordinates;
|
||||||
|
let colocated = [f];
|
||||||
|
if (coords && activityGeojsonData && Array.isArray(activityGeojsonData.features)) {
|
||||||
|
colocated = activityGeojsonData.features.filter(feat => {
|
||||||
|
const c = feat.geometry && feat.geometry.coordinates;
|
||||||
|
return c && Math.abs(c[0] - coords[0]) < 0.00008 && Math.abs(c[1] - coords[1]) < 0.00008;
|
||||||
|
});
|
||||||
|
if (!colocated.length) colocated = [f];
|
||||||
|
}
|
||||||
|
|
||||||
const thematicConfig = {
|
const thematicConfig = {
|
||||||
'nature': { color: '#10b981', label: 'Nature' },
|
'nature': { color: '#10b981', label: 'Nature' },
|
||||||
'lighting': { color: '#f59e0b', label: 'Éclairage' },
|
'lighting': { color: '#f59e0b', label: 'Éclairage' },
|
||||||
|
|
@ -446,34 +454,57 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
'roads': { color: '#06b6d4', label: 'Voirie' },
|
'roads': { color: '#06b6d4', label: 'Voirie' },
|
||||||
'structures': { color: '#8b5cf6', label: 'Ouvrages' },
|
'structures': { color: '#8b5cf6', label: 'Ouvrages' },
|
||||||
};
|
};
|
||||||
const th = thematicConfig[p.thematic_key] || { color: '#64748b', label: p.thematic || 'Patrimoine' };
|
|
||||||
|
|
||||||
const opConfig = {
|
const opConfig = {
|
||||||
'intervention': { icon: 'bi-tools', label: 'Intervention' },
|
'intervention': { icon: 'bi-tools', label: 'Intervention' },
|
||||||
'observation': { icon: 'bi-eye-fill', label: 'Observation' },
|
'observation': { icon: 'bi-eye-fill', label: 'Observation' },
|
||||||
'inspection': { icon: 'bi-search', label: 'Inspection' },
|
'inspection': { icon: 'bi-search', label: 'Inspection' },
|
||||||
'control': { icon: 'bi-clipboard-check', label: 'Contrôle' },
|
'control': { icon: 'bi-clipboard-check', label: 'Contrôle' },
|
||||||
};
|
};
|
||||||
const op = opConfig[p.type] || { icon: p.icon || 'bi-geo-alt', label: p.type_label || p.type };
|
|
||||||
|
|
||||||
const popupContent = `
|
function renderFeatureCard(itemFeat, isMultiple) {
|
||||||
<div style="min-width: 220px; font-family: system-ui, -apple-system, sans-serif;">
|
const props = itemFeat.properties || {};
|
||||||
<div class="d-flex align-items-center justify-content-between mb-2">
|
const th = thematicConfig[props.thematic_key] || { color: '#64748b', label: props.thematic || 'Patrimoine' };
|
||||||
<span class="badge text-white fw-semibold" style="background-color: ${th.color}; font-size: 0.74rem; padding: 4px 8px;">
|
const op = opConfig[props.type] || { icon: props.icon || 'bi-geo-alt', label: props.type_label || props.type };
|
||||||
<i class="bi ${op.icon} me-1.5"></i>${op.label} — ${th.label}
|
|
||||||
</span>
|
|
||||||
<small class="text-muted ms-2">${p.date || ''}</small>
|
|
||||||
</div>
|
|
||||||
<div class="fw-bold mb-1 text-dark" style="font-size: 0.92rem;">${p.title}</div>
|
|
||||||
<div class="text-secondary small mb-2"><i class="bi bi-geo-alt me-1"></i>${p.address || p.code}</div>
|
|
||||||
<div class="d-flex align-items-center justify-content-between mt-2 pt-2 border-top">
|
|
||||||
${statusFormatter(p.status)}
|
|
||||||
${p.url && p.url !== '#' ? `<a href="${p.url}" class="btn btn-sm btn-outline-primary py-0 px-2" style="font-size: 0.75rem;">Voir</a>` : ''}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
new maplibregl.Popup({ closeButton: true, offset: 12 })
|
return `
|
||||||
|
<div class="${isMultiple ? 'border rounded p-2 mb-2 bg-light' : ''}">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-1">
|
||||||
|
<span class="badge text-white fw-semibold" style="background-color: ${th.color}; font-size: 0.72rem; padding: 3px 6px;">
|
||||||
|
<i class="bi ${op.icon} me-1"></i>${op.label} — ${th.label}
|
||||||
|
</span>
|
||||||
|
<small class="text-muted ms-2" style="font-size: 0.70rem;">${props.date || ''}</small>
|
||||||
|
</div>
|
||||||
|
<div class="fw-bold mb-1 text-dark" style="font-size: 0.88rem; line-height: 1.25;">${props.title || '-'}</div>
|
||||||
|
<div class="text-secondary small mb-1.5" style="font-size: 0.75rem;"><i class="bi bi-geo-alt me-1"></i>${props.address || props.code}</div>
|
||||||
|
<div class="d-flex align-items-center justify-content-between pt-1 border-top">
|
||||||
|
${statusFormatter(props.status)}
|
||||||
|
${props.url && props.url !== '#' ? `<a href="${props.url}" class="btn btn-sm btn-outline-primary py-0 px-2" style="font-size: 0.72rem;">Voir</a>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let popupContent = '';
|
||||||
|
if (colocated.length > 1) {
|
||||||
|
popupContent = `
|
||||||
|
<div style="min-width: 240px; max-width: 290px; font-family: system-ui, -apple-system, sans-serif;">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-2 pb-1 border-bottom">
|
||||||
|
<span class="fw-semibold text-dark" style="font-size: 0.80rem;"><i class="bi bi-geo-alt-fill text-primary me-1"></i>${colocated.length} opérations ici</span>
|
||||||
|
</div>
|
||||||
|
<div style="max-height: 280px; overflow-y: auto;">
|
||||||
|
${colocated.map(cf => renderFeatureCard(cf, true)).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
popupContent = `
|
||||||
|
<div style="min-width: 230px; font-family: system-ui, -apple-system, sans-serif;">
|
||||||
|
${renderFeatureCard(f, false)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
new maplibregl.Popup({ closeButton: true, offset: 10 })
|
||||||
.setLngLat(f.geometry.coordinates || e.lngLat)
|
.setLngLat(f.geometry.coordinates || e.lngLat)
|
||||||
.setHTML(popupContent)
|
.setHTML(popupContent)
|
||||||
.addTo(map);
|
.addTo(map);
|
||||||
|
|
@ -481,8 +512,6 @@ if (mapContainer && typeof maplibregl !== 'undefined') {
|
||||||
|
|
||||||
map.on('mouseenter', 'activity-points', () => { map.getCanvas().style.cursor = 'pointer'; });
|
map.on('mouseenter', 'activity-points', () => { map.getCanvas().style.cursor = 'pointer'; });
|
||||||
map.on('mouseleave', 'activity-points', () => { map.getCanvas().style.cursor = ''; });
|
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() {
|
function renderLegend() {
|
||||||
const legendContainer = document.getElementById('activity-map-legend') || document.getElementById('map-legend');
|
const legendContainer = document.getElementById('activity-map-legend') || document.getElementById('map-legend');
|
||||||
|
|
|
||||||
|
|
@ -186,5 +186,5 @@
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="{% static 'interventions/status_colors.js' %}"></script>
|
<script src="{% static 'interventions/status_colors.js' %}"></script>
|
||||||
<script src="{% static 'home/home_index.js' %}?v=20260921_2"></script>
|
<script src="{% static 'home/home_index.js' %}?v=20260921_3"></script>
|
||||||
{% endblock script %}
|
{% endblock script %}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue