Feature: Added real-time GPS location tracking for field use
Deploy Bürgerwind / deploy (push) Successful in 16s Details

This commit is contained in:
Johannes Baumeister 2026-05-13 12:51:56 +02:00
parent 0d9306741e
commit 4479d261d4
3 changed files with 156 additions and 3 deletions

81
app.js
View File

@ -2084,6 +2084,87 @@ document.addEventListener('DOMContentLoaded', async () => {
reader.readAsText(file);
});
// Geolocation Logic
let watchId = null;
let userMarker = null;
let userAccuracyCircle = null;
const btnLocate = document.getElementById('btnLocate');
function stopTracking() {
if (watchId !== null) {
navigator.geolocation.clearWatch(watchId);
watchId = null;
}
if (userMarker) state.map.removeLayer(userMarker);
if (userAccuracyCircle) state.map.removeLayer(userAccuracyCircle);
userMarker = null;
userAccuracyCircle = null;
btnLocate.classList.remove('active');
document.getElementById('statusInfo').innerText = "Standortverfolgung beendet.";
}
function startTracking() {
if (!("geolocation" in navigator)) {
alert("Geolocation wird von Ihrem Browser nicht unterstützt.");
return;
}
btnLocate.classList.add('active');
document.getElementById('statusInfo').innerText = "Suche Standort...";
watchId = navigator.geolocation.watchPosition((position) => {
const { latitude, longitude, accuracy } = position.coords;
const latlng = L.latLng(latitude, longitude);
if (!userMarker) {
userMarker = L.marker(latlng, {
icon: L.divIcon({
className: 'user-location-marker pulse',
iconSize: [16, 16],
iconAnchor: [8, 8]
})
}).addTo(state.map);
userAccuracyCircle = L.circle(latlng, {
radius: accuracy,
className: 'user-location-accuracy'
}).addTo(state.map);
// Center on first found position
state.map.setView(latlng, 16);
} else {
userMarker.setLatLng(latlng);
userAccuracyCircle.setLatLng(latlng);
userAccuracyCircle.setRadius(accuracy);
}
document.getElementById('statusInfo').innerText = `Standort aktualisiert (Genauigkeit: ${accuracy.toFixed(0)}m)`;
}, (err) => {
console.error("Geolocation error:", err);
let msg = "Fehler bei der Standorterkennung.";
if (err.code === 1) msg = "Standortzugriff verweigert.";
else if (err.code === 2) msg = "Standort nicht verfügbar.";
else if (err.code === 3) msg = "Zeitüberschreitung.";
alert(msg);
stopTracking();
}, {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
});
}
if (btnLocate) {
btnLocate.onclick = () => {
if (watchId === null) {
startTracking();
} else {
stopTracking();
}
};
}
updateLegend();
console.log("WindPlaner initialisiert.");
document.getElementById('statusInfo').innerText = "System bereit. Karte geladen.";

View File

@ -258,6 +258,9 @@
</div>
<main id="map"></main>
<button id="btnLocate" class="map-overlay-btn" title="Standort anzeigen">
<span class="gps-icon">🎯</span>
</button>
<!-- JS Dependencies -->
<script src="libs/proj4.js"></script>

View File

@ -788,7 +788,76 @@ body {
/* Grey -> Light Blue */
/* Selection specific styles */
select.status-select option {
background: var(--bg-dark);
color: white;
/* Map Overlay Buttons (GPS, etc.) */
.map-overlay-btn {
position: absolute;
bottom: 25px;
left: 285px; /* Right of the sidebar */
z-index: 1000;
width: 44px;
height: 44px;
background: var(--panel-bg);
backdrop-filter: blur(8px);
border: 1px solid var(--primary-color);
border-radius: 50%;
color: var(--primary-color);
font-size: 1.2rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.4);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.sidebar.collapsed ~ .map-overlay-btn {
left: 25px;
}
.map-overlay-btn:hover {
background: var(--primary-color);
color: black;
transform: scale(1.1);
}
.map-overlay-btn.active {
background: var(--primary-color);
color: black;
box-shadow: 0 0 15px var(--primary-color);
}
/* User Location Marker */
.user-location-marker {
background: #3498db;
border: 3px solid white;
border-radius: 50%;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
}
.user-location-accuracy {
stroke: #3498db;
stroke-width: 2;
fill: #3498db;
fill-opacity: 0.1;
transition: all 0.5s ease;
}
.pulse {
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0.7);
animation: pulse-blue 2s infinite;
}
@keyframes pulse-blue {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 15px rgba(52, 152, 219, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0);
}
}