Feature: Added real-time GPS location tracking for field use
Deploy Bürgerwind / deploy (push) Successful in 16s
Details
Deploy Bürgerwind / deploy (push) Successful in 16s
Details
This commit is contained in:
parent
0d9306741e
commit
4479d261d4
81
app.js
81
app.js
|
|
@ -2084,6 +2084,87 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
reader.readAsText(file);
|
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();
|
updateLegend();
|
||||||
console.log("WindPlaner initialisiert.");
|
console.log("WindPlaner initialisiert.");
|
||||||
document.getElementById('statusInfo').innerText = "System bereit. Karte geladen.";
|
document.getElementById('statusInfo').innerText = "System bereit. Karte geladen.";
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main id="map"></main>
|
<main id="map"></main>
|
||||||
|
<button id="btnLocate" class="map-overlay-btn" title="Standort anzeigen">
|
||||||
|
<span class="gps-icon">🎯</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- JS Dependencies -->
|
<!-- JS Dependencies -->
|
||||||
<script src="libs/proj4.js"></script>
|
<script src="libs/proj4.js"></script>
|
||||||
|
|
|
||||||
75
style.css
75
style.css
|
|
@ -788,7 +788,76 @@ body {
|
||||||
/* Grey -> Light Blue */
|
/* Grey -> Light Blue */
|
||||||
|
|
||||||
/* Selection specific styles */
|
/* Selection specific styles */
|
||||||
select.status-select option {
|
/* Map Overlay Buttons (GPS, etc.) */
|
||||||
background: var(--bg-dark);
|
.map-overlay-btn {
|
||||||
color: white;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue