feat: Geschwindigkeits-Farbverlauf auf der Track-Karte und stabiler Leaflet-Lifecycle.
Verzögertes fitBounds, Error Boundary und sauberes Map-Cleanup beheben den Absturz, der die Logbuch-Ansicht leer ließ. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import type { TrackWaypoint } from '../services/trackUpload.js'
|
||||
|
||||
const NM_IN_METERS = 1852
|
||||
const MAX_PLAUSIBLE_KNOTS = 50
|
||||
const FALLBACK_GREEN = '#16a34a'
|
||||
|
||||
function haversineMeters(lat1: number, lon1: number, lat2: number, lon2: number): number {
|
||||
const R = 6371000
|
||||
const p1 = (lat1 * Math.PI) / 180
|
||||
const p2 = (lat2 * Math.PI) / 180
|
||||
const dLat = ((lat2 - lat1) * Math.PI) / 180
|
||||
const dLon = ((lon2 - lon1) * Math.PI) / 180
|
||||
const a =
|
||||
Math.sin(dLat / 2) ** 2 +
|
||||
Math.cos(p1) * Math.cos(p2) * Math.sin(dLon / 2) ** 2
|
||||
return 2 * R * Math.asin(Math.sqrt(a))
|
||||
}
|
||||
|
||||
function hasMeaningfulTimestamps(waypoints: TrackWaypoint[]): boolean {
|
||||
if (waypoints.length < 2) return false
|
||||
const first = waypoints[0].timestamp
|
||||
const last = waypoints[waypoints.length - 1].timestamp
|
||||
return last > first + 60_000
|
||||
}
|
||||
|
||||
export function getSegmentSpeedsKn(waypoints: TrackWaypoint[]): number[] {
|
||||
if (waypoints.length < 2) return []
|
||||
|
||||
const timed = hasMeaningfulTimestamps(waypoints)
|
||||
const speeds: number[] = []
|
||||
|
||||
for (let i = 1; i < waypoints.length; i++) {
|
||||
const prev = waypoints[i - 1]
|
||||
const curr = waypoints[i]
|
||||
let speedKn = 0
|
||||
|
||||
const tagged = [prev.speedKnots, curr.speedKnots].filter(
|
||||
(value): value is number => value != null && value > 0
|
||||
)
|
||||
if (tagged.length > 0) {
|
||||
speedKn = tagged.reduce((sum, value) => sum + value, 0) / tagged.length
|
||||
} else if (timed) {
|
||||
const dtMs = curr.timestamp - prev.timestamp
|
||||
const segmentM = haversineMeters(prev.lat, prev.lng, curr.lat, curr.lng)
|
||||
if (dtMs > 0 && segmentM > 0) {
|
||||
speedKn = (segmentM / NM_IN_METERS) / (dtMs / 3_600_000)
|
||||
}
|
||||
}
|
||||
|
||||
if (speedKn > MAX_PLAUSIBLE_KNOTS) speedKn = 0
|
||||
speeds.push(speedKn)
|
||||
}
|
||||
|
||||
return speeds
|
||||
}
|
||||
|
||||
export function hasSpeedGradientData(speeds: number[]): boolean {
|
||||
const valid = speeds.filter((speed) => speed > 0)
|
||||
if (valid.length < 2) return false
|
||||
const min = Math.min(...valid)
|
||||
const max = Math.max(...valid)
|
||||
return max - min >= 0.3
|
||||
}
|
||||
|
||||
/** Green (slow) → yellow → red (fast) */
|
||||
export function speedToTrackColor(speedKn: number, minKn: number, maxKn: number): string {
|
||||
if (speedKn <= 0 || maxKn <= minKn) return FALLBACK_GREEN
|
||||
const t = Math.max(0, Math.min(1, (speedKn - minKn) / (maxKn - minKn)))
|
||||
const hue = 120 - t * 120
|
||||
return `hsl(${hue}, 72%, 42%)`
|
||||
}
|
||||
|
||||
export function getTrackLineColor(speeds: number[]): string {
|
||||
return hasSpeedGradientData(speeds) ? '' : FALLBACK_GREEN
|
||||
}
|
||||
Reference in New Issue
Block a user