feat: implement interactive mini-map and fullscreen positioning mode for precise sign placement
This commit is contained in:
parent
d931827c84
commit
d138ee98c4
3 changed files with 229 additions and 17 deletions
|
|
@ -1669,8 +1669,184 @@
|
|||
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
|
||||
let miniMap = null;
|
||||
let miniMarker = null;
|
||||
let fullScreenPlacementMarker = null;
|
||||
let isMapPlacementActive = false;
|
||||
|
||||
function updateNewSignCoords(lat, lon, updateMini = false) {
|
||||
const inputLat = document.getElementById('new_sign_lat');
|
||||
const inputLon = document.getElementById('new_sign_lon');
|
||||
const coordsDisplay = document.getElementById('new_sign_gps_coords');
|
||||
|
||||
const fLat = parseFloat(lat);
|
||||
const fLon = parseFloat(lon);
|
||||
if (isNaN(fLat) || isNaN(fLon)) return;
|
||||
|
||||
if (inputLat) inputLat.value = fLat.toFixed(6);
|
||||
if (inputLon) inputLon.value = fLon.toFixed(6);
|
||||
if (coordsDisplay) {
|
||||
coordsDisplay.textContent = `Lat: ${fLat.toFixed(5)}, Lon: ${fLon.toFixed(5)}`;
|
||||
}
|
||||
|
||||
if (updateMini && miniMarker && miniMap) {
|
||||
miniMarker.setLngLat([fLon, fLat]);
|
||||
miniMap.setCenter([fLon, fLat]);
|
||||
}
|
||||
}
|
||||
|
||||
function initOrUpdateMiniMap(lat, lon) {
|
||||
const container = document.getElementById('new_sign_mini_map');
|
||||
if (!container || typeof maplibregl === 'undefined') return;
|
||||
|
||||
const fLat = parseFloat(lat);
|
||||
const fLon = parseFloat(lon);
|
||||
if (isNaN(fLat) || isNaN(fLon)) return;
|
||||
|
||||
if (!miniMap) {
|
||||
const styleUrl = window.MAP_STYLE_LIGHT || 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json';
|
||||
miniMap = new maplibregl.Map({
|
||||
container: 'new_sign_mini_map',
|
||||
style: styleUrl,
|
||||
center: [fLon, fLat],
|
||||
zoom: 18.5,
|
||||
attributionControl: false
|
||||
});
|
||||
|
||||
miniMarker = new maplibregl.Marker({ draggable: true, color: '#dc3545' })
|
||||
.setLngLat([fLon, fLat])
|
||||
.addTo(miniMap);
|
||||
|
||||
miniMarker.on('dragend', function() {
|
||||
const lngLat = miniMarker.getLngLat();
|
||||
updateNewSignCoords(lngLat.lat, lngLat.lng, false);
|
||||
});
|
||||
|
||||
miniMap.on('click', function(e) {
|
||||
miniMarker.setLngLat(e.lngLat);
|
||||
updateNewSignCoords(e.lngLat.lat, e.lngLat.lng, false);
|
||||
});
|
||||
} else {
|
||||
miniMap.setCenter([fLon, fLat]);
|
||||
if (miniMarker) miniMarker.setLngLat([fLon, fLat]);
|
||||
setTimeout(() => miniMap.resize(), 150);
|
||||
}
|
||||
}
|
||||
|
||||
modalEl.addEventListener('shown.bs.modal', function() {
|
||||
const inputLat = document.getElementById('new_sign_lat');
|
||||
const inputLon = document.getElementById('new_sign_lon');
|
||||
const lat = inputLat ? parseFloat(inputLat.value) : null;
|
||||
const lon = inputLon ? parseFloat(inputLon.value) : null;
|
||||
if (lat !== null && lon !== null && !isNaN(lat) && !isNaN(lon)) {
|
||||
initOrUpdateMiniMap(lat, lon);
|
||||
}
|
||||
if (miniMap) {
|
||||
setTimeout(() => miniMap.resize(), 200);
|
||||
}
|
||||
});
|
||||
|
||||
// Bouton Recentrer sur le GPS de l'appareil
|
||||
const btnResetGps = document.getElementById('btn_sign_reset_gps');
|
||||
if (btnResetGps) {
|
||||
btnResetGps.addEventListener('click', function() {
|
||||
let lat = null, lon = null;
|
||||
if (typeof userMarker !== 'undefined' && userMarker) {
|
||||
const lngLat = userMarker.getLngLat();
|
||||
lat = lngLat.lat;
|
||||
lon = lngLat.lng;
|
||||
} else if (typeof map !== 'undefined' && map) {
|
||||
const center = map.getCenter();
|
||||
lat = center.lat;
|
||||
lon = center.lng;
|
||||
}
|
||||
if (lat !== null && lon !== null) {
|
||||
updateNewSignCoords(lat, lon, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Mode Positionnement Plein Écran sur la grande carte
|
||||
const btnAdjustFullscreen = document.getElementById('btn_sign_adjust_fullscreen');
|
||||
const placementBar = document.getElementById('map-placement-bar');
|
||||
const btnConfirmPlacement = document.getElementById('btn-confirm-map-placement');
|
||||
const btnCancelPlacement = document.getElementById('btn-cancel-map-placement');
|
||||
|
||||
function onMapPlacementClick(e) {
|
||||
if (!isMapPlacementActive || !fullScreenPlacementMarker) return;
|
||||
fullScreenPlacementMarker.setLngLat(e.lngLat);
|
||||
}
|
||||
|
||||
if (btnAdjustFullscreen) {
|
||||
btnAdjustFullscreen.addEventListener('click', function() {
|
||||
const inputLat = document.getElementById('new_sign_lat');
|
||||
const inputLon = document.getElementById('new_sign_lon');
|
||||
let curLat = inputLat ? parseFloat(inputLat.value) : null;
|
||||
let curLon = inputLon ? parseFloat(inputLon.value) : null;
|
||||
|
||||
if (curLat === null || curLon === null || isNaN(curLat) || isNaN(curLon)) {
|
||||
if (typeof map !== 'undefined' && map) {
|
||||
const center = map.getCenter();
|
||||
curLat = center.lat;
|
||||
curLon = center.lng;
|
||||
}
|
||||
}
|
||||
|
||||
modal.hide();
|
||||
|
||||
if (typeof map !== 'undefined' && map && curLat !== null && curLon !== null) {
|
||||
isMapPlacementActive = true;
|
||||
if (placementBar) placementBar.classList.remove('d-none');
|
||||
|
||||
if (fullScreenPlacementMarker) {
|
||||
fullScreenPlacementMarker.remove();
|
||||
}
|
||||
|
||||
fullScreenPlacementMarker = new maplibregl.Marker({ draggable: true, color: '#dc3545' })
|
||||
.setLngLat([curLon, curLat])
|
||||
.addTo(map);
|
||||
|
||||
map.flyTo({ center: [curLon, curLat], zoom: 19 });
|
||||
|
||||
map.on('click', onMapPlacementClick);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (btnConfirmPlacement) {
|
||||
btnConfirmPlacement.addEventListener('click', function() {
|
||||
if (fullScreenPlacementMarker) {
|
||||
const lngLat = fullScreenPlacementMarker.getLngLat();
|
||||
updateNewSignCoords(lngLat.lat, lngLat.lng, true);
|
||||
fullScreenPlacementMarker.remove();
|
||||
fullScreenPlacementMarker = null;
|
||||
}
|
||||
isMapPlacementActive = false;
|
||||
if (placementBar) placementBar.classList.add('d-none');
|
||||
if (typeof map !== 'undefined' && map) {
|
||||
map.off('click', onMapPlacementClick);
|
||||
}
|
||||
modal.show();
|
||||
});
|
||||
}
|
||||
|
||||
if (btnCancelPlacement) {
|
||||
btnCancelPlacement.addEventListener('click', function() {
|
||||
if (fullScreenPlacementMarker) {
|
||||
fullScreenPlacementMarker.remove();
|
||||
fullScreenPlacementMarker = null;
|
||||
}
|
||||
isMapPlacementActive = false;
|
||||
if (placementBar) placementBar.classList.add('d-none');
|
||||
if (typeof map !== 'undefined' && map) {
|
||||
map.off('click', onMapPlacementClick);
|
||||
}
|
||||
modal.show();
|
||||
});
|
||||
}
|
||||
|
||||
btnAdd.addEventListener('click', function() {
|
||||
// Déterminer la position GPS
|
||||
// Déterminer la position GPS par défaut
|
||||
let lat = null, lon = null;
|
||||
if (typeof userMarker !== 'undefined' && userMarker) {
|
||||
const lngLat = userMarker.getLngLat();
|
||||
|
|
@ -1682,16 +1858,8 @@
|
|||
lon = center.lng;
|
||||
}
|
||||
|
||||
const inputLat = document.getElementById('new_sign_lat');
|
||||
const inputLon = document.getElementById('new_sign_lon');
|
||||
const coordsDisplay = document.getElementById('new_sign_gps_coords');
|
||||
|
||||
if (inputLat && inputLon && lat !== null && lon !== null) {
|
||||
inputLat.value = lat.toFixed(6);
|
||||
inputLon.value = lon.toFixed(6);
|
||||
if (coordsDisplay) {
|
||||
coordsDisplay.textContent = `Lat: ${lat.toFixed(5)}, Lon: ${lon.toFixed(5)}`;
|
||||
}
|
||||
if (lat !== null && lon !== null) {
|
||||
updateNewSignCoords(lat, lon, false);
|
||||
}
|
||||
|
||||
// Réinitialiser la liste des panneaux et l'aperçu photo
|
||||
|
|
|
|||
|
|
@ -16,14 +16,34 @@
|
|||
<form id="formQuickAddSign" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Coordonnées GPS -->
|
||||
<div class="card bg-light border-0 rounded-3 p-2.5 mb-3">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<span class="small fw-semibold text-secondary d-flex align-items-center gap-1">
|
||||
<i class="bi bi-geo-alt-fill text-danger"></i>{% translate "Position d'implantation" %}
|
||||
<!-- Coordonnées et Localisation exacte -->
|
||||
<div class="card border rounded-3 p-2.5 mb-3 bg-white shadow-xs">
|
||||
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||
<span class="small fw-bold text-dark d-flex align-items-center gap-1">
|
||||
<i class="bi bi-geo-alt-fill text-danger"></i>{% translate "Emplacement exact du mât" %}
|
||||
</span>
|
||||
<span class="badge bg-white text-dark border extra-small" id="new_sign_gps_coords">Lat: -, Lon: -</span>
|
||||
<span class="badge bg-light text-dark border extra-small" id="new_sign_gps_coords">Lat: -, Lon: -</span>
|
||||
</div>
|
||||
|
||||
<!-- Mini-carte interactive de précision -->
|
||||
<div class="position-relative mb-2 rounded-3 overflow-hidden border shadow-xs" style="height: 160px; background: #e9ecef;">
|
||||
<div id="new_sign_mini_map" style="width: 100%; height: 100%;"></div>
|
||||
<div class="position-absolute top-0 end-0 m-1.5 d-flex flex-column gap-1" style="z-index: 5;">
|
||||
<button type="button" id="btn_sign_adjust_fullscreen" class="btn btn-sm btn-white bg-white shadow-sm border py-1 px-2 d-flex align-items-center gap-1 extra-small" title="{% translate 'Ajuster sur la grande carte' %}">
|
||||
<i class="bi bi-arrows-fullscreen text-primary"></i>
|
||||
<span class="fw-semibold">{% translate "Grande carte" %}</span>
|
||||
</button>
|
||||
<button type="button" id="btn_sign_reset_gps" class="btn btn-sm btn-white bg-white shadow-sm border py-1 px-2 d-flex align-items-center gap-1 extra-small" title="{% translate 'Recentrer sur ma position GPS' %}">
|
||||
<i class="bi bi-crosshair text-primary"></i>
|
||||
<span>{% translate "Mon GPS" %}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between text-muted" style="font-size: 0.75rem;">
|
||||
<span><i class="bi bi-info-circle me-1"></i>{% translate "Glissez le repère rouge ou touchez la carte pour placer le mât au mètre près." %}</span>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="lat" id="new_sign_lat" value="">
|
||||
<input type="hidden" name="lon" id="new_sign_lon" value="">
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -234,6 +234,30 @@
|
|||
<!-- Carte MapLibre -->
|
||||
<div id="inspection-map"></div>
|
||||
|
||||
<!-- Bandeau de positionnement précis sur la grande carte -->
|
||||
<div id="map-placement-bar" class="position-absolute top-0 start-50 translate-middle-x mt-3 d-none shadow-lg" style="z-index: 1050; width: 92%; max-width: 520px;">
|
||||
<div class="card border-0 bg-dark bg-opacity-95 text-white rounded-4 p-2.5 shadow">
|
||||
<div class="d-flex align-items-center justify-content-between gap-2">
|
||||
<div class="d-flex align-items-center gap-2 overflow-hidden">
|
||||
<div class="spinner-grow spinner-grow-sm text-danger flex-shrink-0" role="status"></div>
|
||||
<div class="text-truncate">
|
||||
<div class="fw-bold small text-white text-truncate">{% translate "Emplacement exact du mât" %}</div>
|
||||
<div class="text-white-50 extra-small text-truncate">{% translate "Glissez le repère rouge ou touchez la carte" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-1.5 flex-shrink-0">
|
||||
<button type="button" class="btn btn-sm btn-outline-light py-1 px-2.5" id="btn-cancel-map-placement">
|
||||
{% translate "Annuler" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-success fw-bold py-1 px-3 d-flex align-items-center gap-1 shadow-sm" id="btn-confirm-map-placement">
|
||||
<i class="bi bi-check-lg"></i>
|
||||
<span>{% translate "Valider" %}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions Flottantes (Bouton Ajout Panneau & GPS) -->
|
||||
<div class="map-floating-actions">
|
||||
<button id="btn-add-quick-sign" class="btn btn-primary btn-lg shadow rounded-circle d-none align-items-center justify-content-center border-0" title="{% translate 'Ajouter un panneau de signalisation' %}" style="width: 52px; height: 52px;">
|
||||
|
|
|
|||
Loading…
Reference in a new issue