// Reiseroute für die Kilometer-Statistik, aus Besuchen und Wohnorten (Luftlinie): // - Reise: Wohnort → Ziel(e) → Wohnort. Welcher Wohnort gilt, entscheidet das "seit"-Datum der Wohnorte. // - Rundreise: Besuche mit Anreise "vom vorherigen Ort" (oder automatisch: höchstens 1 Tag Abstand) // gehören zur selben Reise: Wohnort → A → B → Wohnort. // - Ausflug: Besuch, der während eines längeren Aufenthalts beginnt – Hin- und Rückweg zum Aufenthaltsort. // - Umzug: Wechsel des Wohnorts (auch ein längerer Auslandsaufenthalt) zählt als eigene Strecke. // Ohne Wohnort fehlen Hin- und Heimwege; die Reisen werden dann direkt nacheinander verbunden. import { distanceKm } from './laender.js'; const day = (d) => Math.round(Date.parse(`${d}T12:00:00Z`) / 864e5); const nextDay = (d) => new Date(Date.parse(`${d}T12:00:00Z`) + 864e5).toISOString().slice(0, 10); const samePlace = (a, b) => Boolean(a && b && a.lat === b.lat && a.lon === b.lon); /** Wohnort an einem Tag; vor dem ersten Datum gilt der früheste. homes: sortiert, "von Anfang an" zuerst. */ export function homeAt(homes, date) { let found = null; for (const h of homes) if (!h.since || h.since <= date) found = h; return found || homes[0] || null; } // Besuche zu Reisen zusammenfassen (mit Ausflügen je Besuch) function groupTrips(visits, cities) { const byId = new Map(cities.map((c) => [c.id, c])); const items = visits .map((v) => ({ ...v, city: byId.get(v.cityId), to: v.dateTo || v.dateFrom, excursions: [] })) .filter((v) => v.city) .sort((a, b) => a.dateFrom.localeCompare(b.dateFrom) || b.to.localeCompare(a.to)); const trips = []; for (const v of items) { const trip = trips.at(-1); const stay = trip?.visits.findLast((s) => v.dateFrom >= s.dateFrom && v.dateFrom <= s.to); if (stay) { stay.excursions.push(v); continue; } const continued = v.continued ?? (trip && day(v.dateFrom) - day(trip.end) <= 1); if (trip && continued) { trip.visits.push(v); if (v.to > trip.end) trip.end = v.to; } else { trips.push({ visits: [v], start: v.dateFrom, end: v.to }); } } return trips; } export function travelRoute(visits, cities, homes = []) { const trips = groupTrips(visits, cities); const dated = homes.filter((h) => h.since); const legs = []; const leg = (from, to, kind) => { if (!from || !to || samePlace(from, to)) return; legs.push({ from: from.name, to: to.name, km: distanceKm(from, to), kind }); }; let home = homes[0] || null; // aktueller Wohnort let pos = home; // wo man gerade ist let next = dated.indexOf(home) + 1; // nächster noch nicht verarbeiteter Wohnort-Wechsel // Wohnort-Wechsel bis zu einem Tag als Umzug verbuchen (nur zwischen den Reisen) const moveUntil = (date) => { while (next < dated.length && dated[next].since <= date) { leg(pos, dated[next], 'Umzug'); pos = home = dated[next]; next += 1; } }; let realTrips = 0; let excursions = 0; for (const trip of trips) { moveUntil(trip.start); if (!trip.visits.every((v) => samePlace(v.city, home))) realTrips += 1; trip.visits.forEach((v, i) => { leg(pos, v.city, i === 0 && home ? 'Hinweg' : 'Weiterreise'); pos = v.city; for (const ex of v.excursions) { excursions += 1; leg(pos, ex.city, 'Ausflug'); leg(ex.city, pos, 'Ausflug zurück'); } }); if (home) { // Zurück zum Wohnort, der nach der Reise gilt – hat er sich während der Reise geändert, ist es ein Umzug const after = homeAt(homes, nextDay(trip.end)); leg(pos, after, after === home ? 'Heimweg' : 'Umzug'); while (next < dated.length && dated[next].since <= nextDay(trip.end)) next += 1; pos = home = after; } } moveUntil('9999-12-31'); return { legs, km: legs.reduce((s, l) => s + l.km, 0), trips: realTrips, excursions }; } // Reisetage: jeder Kalendertag einmal; Tage am damaligen Wohnort (z. B. Auslandsaufenthalt) zählen nicht export function travelDays(visits, cities, homes = []) { const byId = new Map(cities.map((c) => [c.id, c])); const days = new Set(); for (const v of visits) { const city = byId.get(v.cityId); if (!city) continue; const end = v.dateTo || v.dateFrom; for (let d = v.dateFrom; d <= end; d = nextDay(d)) { if (!samePlace(city, homeAt(homes, d))) days.add(d); } } return days.size; }