Centrer la carte sur le GPS du téléphone (HTTPS terminé par le proxy)

Le bouton ⌖ recentre la carte sur la position GPS du téléphone. L'API
Geolocation exige un contexte sécurisé : la webapp sert désormais du HTTP
et le TLS est porté par le reverse proxy (Traefik, labels dans l'override
local) ; le webapp est transparent au proxy (URLs relatives, X-Forwarded-For
pour la restriction LIDAR_REGEN_CIDR). Ajoute le modèle d'override
docker-compose.webapp.override.yml.example, un rebuild d'index à la demande
et met à jour doc/déploiement ; les tuiles sont servies à la demande.
This commit is contained in:
Antoine Jacquin
2026-09-13 19:57:09 +02:00
parent dd1aca3cfe
commit e2533bac48
16 changed files with 407 additions and 279 deletions

View File

@ -1332,9 +1332,11 @@ _HTML_TEMPLATE = """<!DOCTYPE html>
<button id="zoomIn" title="Zoomer">+</button>
<button id="zoomOut" title="Dézoomer">−</button>
<button id="zoomReset" title="Recadrer sur l'ensemble des tuiles">⤢</button>
<button id="btnGps" title="Centrer la carte sur la position GPS du téléphone">⌖</button>
</nav>
<div id="zoomIndicator" class="chip">Zoom</div>
<div id="gpsToast" class="chip" hidden></div>
<div id="hint" class="chip">
Molette : zoom · Clic-glisser : déplacer<br>
Clic sur tuile : infos de génération
@ -1618,6 +1620,7 @@ body.zooming .ltile { will-change: transform; }
#mapTools { top: 12px; right: 12px; display: flex; gap: 6px; padding: 6px; }
#mapTools button { width: 32px; height: 32px; padding: 0; font-size: 16px; }
#btnMenu { display: none; }
#btnGps.busy { color: var(--accent); border-color: var(--accent); }
/* --- Puces d'état (bas d'écran) --- */
.chip {
@ -1627,6 +1630,10 @@ body.zooming .ltile { will-change: transform; }
font-size: 11px; color: var(--muted);
}
#zoomIndicator { bottom: 12px; left: var(--left-ui); font-variant-numeric: tabular-nums; }
#gpsToast {
top: 54px; left: 50%; transform: translateX(-50%);
max-width: 80vw; text-align: center; line-height: 1.5;
}
#hint { bottom: 12px; right: 12px; text-align: right; line-height: 1.5; }
/* --- Onglet Génération --- */
@ -2351,6 +2358,53 @@ document.getElementById('zoomReset').addEventListener('click', () => {
history.replaceState(null, '', location.pathname + location.search);
if (gridBounds) map.fitBounds(gridBounds, { padding: [24, 24] });
});
// ============================================================
// Position GPS du téléphone : centrer la carte dessus
// ============================================================
const gpsBtn = document.getElementById('btnGps');
const gpsToast = document.getElementById('gpsToast');
let gpsToastTimer = null;
let gpsMarker = null;
function gpsMsg(text, ms) {
gpsToast.textContent = text;
gpsToast.hidden = false;
clearTimeout(gpsToastTimer);
gpsToastTimer = setTimeout(() => { gpsToast.hidden = true; }, ms || 4000);
}
gpsBtn.addEventListener('click', () => {
if (!('geolocation' in navigator)) {
gpsMsg('Géolocalisation non prise en charge par ce navigateur ' +
'(connexion sécurisée HTTPS requise).', 6000);
return;
}
gpsBtn.classList.add('busy');
try {
navigator.geolocation.getCurrentPosition(pos => {
gpsBtn.classList.remove('busy');
const ll = [pos.coords.latitude, pos.coords.longitude];
map.setView(ll, Math.max(map.getZoom(), 16));
if (gpsMarker) gpsMarker.remove();
gpsMarker = L.circleMarker(ll, {
radius: 5, color: '#39d98a', fillColor: '#39d98a', fillOpacity: 0.9
}).addTo(map);
if (pos.coords.accuracy) {
gpsMarker.bindTooltip('Précision GPS ~' + Math.round(pos.coords.accuracy) + ' m');
}
gpsMsg('Carte centrée sur votre position GPS.');
}, err => {
gpsBtn.classList.remove('busy');
let why = 'Aucune position reçue';
if (err && err.code === 1) why = 'Autorisation de localisation refusée';
else if (err && err.code === 2) why = 'Position momentanément indisponible';
else if (err && err.code === 3) why = 'Délai de localisation dépassé';
gpsMsg(why + '.', 6000);
}, { enableHighAccuracy: true, timeout: 15000, maximumAge: 60000 });
} catch (e) {
gpsBtn.classList.remove('busy');
gpsMsg('La géolocalisation exige une connexion sécurisée (HTTPS).', 6000);
}
});
// will-change pendant l'animation de zoom uniquement : le scaling passe sur
// le GPU, et les couches sont libérées juste après (mémoire vidéo).
let zoomGpuTimer = null;