Add files via upload

This commit is contained in:
Serverlele30 authored and GitHub committed 2026-07-10 11:34:25 +02:00
1 parent 67a9468142
commit 97a156ce8f
12 files changed
+666 -26

No files matched your search

+162 -1
View File
@@ -230,6 +230,7 @@
// die lokale Animation zwischenspeichern
liveMapSegments = await transitousRadarSegments(bounds, zoom);
renderVehiclesFromSegments();
updateMapStops(); // Stations-Punkte im selben Takt pflegen
} catch (error) {
console.error('Load vehicles error:', error);
@@ -242,6 +243,164 @@
}
}
// ==========================================
// EIGENER STANDORT AUF DER KARTE
// 📍-Button: blauer Punkt + Karte zentrieren
// ==========================================
let userLocationMarker = null;
document.addEventListener('DOMContentLoaded', () => {
const locateBtn = document.getElementById('locateBtn');
if (!locateBtn) return;
locateBtn.addEventListener('click', () => {
if (!navigator.geolocation || !liveMap) return;
locateBtn.textContent = '⏳';
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
rememberPosition(latitude, longitude);
// Blauer Punkt (wird bei erneutem Tap nur bewegt)
if (userLocationMarker) {
userLocationMarker.setLatLng([latitude, longitude]);
} else {
userLocationMarker = L.circleMarker([latitude, longitude], {
radius: 8,
color: '#FFFFFF',
weight: 3,
fillColor: '#2A93EE',
fillOpacity: 1
}).addTo(liveMap);
}
liveMap.setView([latitude, longitude], Math.max(liveMap.getZoom(), 15));
locateBtn.textContent = '📍';
if (navigator.vibrate) navigator.vibrate(10);
},
(error) => {
console.warn('Standort nicht verfügbar:', error.message);
locateBtn.textContent = '📍';
const countEl = document.getElementById('vehicleCount');
if (countEl) countEl.textContent = '⚠️ Standort nicht verfügbar';
},
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 60000 }
);
});
});
// ==========================================
// STATIONS-PUNKTE AUF DER KARTE (ab Zoom 15)
// Tap auf einen Punkt -> Popup mit den nächsten 3 Abfahrten
// (1 stoptimes-Request, 15s-Cache) + Sprung zur Abfahrten-View.
// ==========================================
const STOPS_MIN_ZOOM = 15;
let stopMarkerMap = new Map(); // stopId -> marker
async function updateMapStops() {
if (!liveMap) return;
// Bei niedrigem Zoom oder im Fokus-Modus: Punkte ausblenden
const zoom = liveMap.getZoom ? liveMap.getZoom() : 12;
if (zoom < STOPS_MIN_ZOOM || focusedVehicleTripId) {
clearMapStops();
return;
}
const b = liveMap.getBounds();
let stops;
try {
stops = await transitousMapStops({
north: b.getNorth(), south: b.getSouth(),
west: b.getWest(), east: b.getEast()
});
} catch (e) {
return; // Punkte sind nice-to-have - Fehler still schlucken
}
const seen = new Set();
for (const stop of stops) {
seen.add(stop.id);
if (stopMarkerMap.has(stop.id)) continue;
const marker = L.circleMarker([stop.lat, stop.lon], {
radius: 6,
color: '#FFED00',
weight: 2,
fillColor: '#111',
fillOpacity: 0.9
});
marker.bindPopup(stopPopupHTML(stop, null), { minWidth: 220 });
marker.on('popupopen', () => loadStopPopupDepartures(stop, marker));
marker.addTo(liveMap);
stopMarkerMap.set(stop.id, marker);
}
// Punkte außerhalb des Ausschnitts entfernen
for (const [id, marker] of stopMarkerMap) {
if (!seen.has(id)) {
marker.remove();
stopMarkerMap.delete(id);
}
}
}
function clearMapStops() {
for (const [, marker] of stopMarkerMap) marker.remove();
stopMarkerMap.clear();
}
function stopPopupHTML(stop, departures) {
let depsHTML = '<div class="stop-popup-loading">⏳ Lade Abfahrten…</div>';
if (departures) {
depsHTML = departures.length === 0
? '<div class="stop-popup-loading">Keine Abfahrten in Kürze</div>'
: departures.map(dep => {
const mins = Math.max(0, Math.round((new Date(dep.when) - Date.now()) / 60000));
const color = dep.line?.color || getBVGLineColor(dep.line?.name || '');
return `
<div class="stop-popup-dep">
<span class="stop-popup-line" style="background:${color};color:${bestTextColor(color)}">${escapeHtml(dep.line?.name || '?')}</span>
<span class="stop-popup-dir">${escapeHtml(dep.direction || '')}</span>
<span class="stop-popup-min">${mins === 0 ? 'Jetzt' : mins + ' min'}</span>
</div>`;
}).join('');
}
return `
<div class="stop-popup">
<div class="stop-popup-name">🚏 ${escapeHtml(stop.name)}</div>
${depsHTML}
<button class="map-stop-open" data-id="${escapeHtml(stop.id)}" data-name="${escapeHtml(stop.name)}"
data-lat="${stop.lat}" data-lon="${stop.lon}">
Alle Abfahrten →
</button>
</div>`;
}
async function loadStopPopupDepartures(stop, marker) {
try {
const data = await transitousDepartures(
{ id: stop.id, name: stop.name, lat: stop.lat, lon: stop.lon }, 3
);
marker.setPopupContent(stopPopupHTML(stop, (data.departures || []).slice(0, 3)));
} catch (e) {
marker.setPopupContent(stopPopupHTML(stop, []));
}
}
// "Alle Abfahrten" im Stations-Popup -> Abfahrten-View
document.addEventListener('click', (e) => {
const btn = e.target.closest('.map-stop-open');
if (!btn) return;
switchView('departures');
selectStation(
btn.dataset.id, btn.dataset.name,
btn.dataset.lat ? parseFloat(btn.dataset.lat) : null,
btn.dataset.lon ? parseFloat(btn.dataset.lon) : null
);
});
// ==========================================
// STRECKEN-ANZEIGE BEIM FAHRZEUG-KLICK
// Die Polylines liegen bereits dekodiert in liveMapSegments -
@@ -265,8 +424,9 @@
}).addTo(liveMap);
}
// Fokus-Modus aktivieren: andere Fahrzeuge ausblenden
// Fokus-Modus aktivieren: andere Fahrzeuge + Stations-Punkte ausblenden
renderVehiclesFromSegments();
clearMapStops();
// 2) ...dann die KOMPLETTE Route nachladen (map/trips liefert nur
// das Segment im Ausschnitt; die ganze Strecke steckt in /trip).
@@ -295,6 +455,7 @@
if (focusedVehicleTripId) {
focusedVehicleTripId = null;
renderVehiclesFromSegments(); // alle Fahrzeuge wieder zeigen
updateMapStops(); // Stations-Punkte wiederherstellen
}
}