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

+51 -9
View File
@@ -66,6 +66,15 @@ function motisDelaySeconds(actual, scheduled) {
return Number.isFinite(diff) ? Math.round(diff) : null;
}
// Geocode-Bias: letzte bekannte GPS-Position bevorzugen (bessere
// Trefferreihenfolge in Brandenburg), sonst Berlin-Mitte als Default
function geocodeBias() {
if (lastKnownLat != null && lastKnownLon != null) {
return `${lastKnownLat.toFixed(3)},${lastKnownLon.toFixed(3)}`;
}
return '52.52,13.41';
}
// GTFS-Farbe aus der API normalisieren ('DA421E' -> '#DA421E')
// Das sind die OFFIZIELLEN VBB-Linienfarben aus dem GTFS-Feed -
// kein Extra-Request nötig, sie stecken in stoptimes/plan/map-trips.
@@ -95,7 +104,7 @@ async function transitousLocations(query) {
// place=Berlin-Mitte als Bias, damit VBB-Gebiet zuerst kommt
// 6h Cache: Stationsnamen ändern sich praktisch nie
const matches = await transitousFetch(
`/v1/geocode?text=${encodeURIComponent(query)}&type=STOP&place=52.52,13.41&numResults=10`,
`/v1/geocode?text=${encodeURIComponent(query)}&type=STOP&place=${geocodeBias()}&numResults=10`,
6 * 60 * 60 * 1000
);
@@ -119,16 +128,19 @@ async function transitousStopQuery({ id, name, lat, lon }) {
return `center=${lat},${lon}&radius=250`;
}
// Nur Name bekannt (alte Favoriten): einmalig geocoden, Mapping cachen
const cacheKey = 'vbb_transitous_stop_' + name;
const cacheKey = STORAGE_KEYS.stopMapPrefix + name;
const cached = storageGet(cacheKey);
if (cached) return 'stopId=' + encodeURIComponent(cached);
// Neues Format {id, time}; altes Format (String) wird von
// cleanupStorage beim Start migriert, hier zur Sicherheit beides lesen
const cachedId = cached && (typeof cached === 'string' ? cached : cached.id);
if (cachedId) return 'stopId=' + encodeURIComponent(cachedId);
const matches = await transitousFetch(
`/v1/geocode?text=${encodeURIComponent(name)}&type=STOP&place=52.52,13.41&numResults=1`,
`/v1/geocode?text=${encodeURIComponent(name)}&type=STOP&place=${geocodeBias()}&numResults=1`,
6 * 60 * 60 * 1000
);
if (!matches || matches.length === 0) throw new Error('Station bei Transitous nicht gefunden');
storageSet(cacheKey, matches[0].id);
storageSet(cacheKey, { id: matches[0].id, time: Date.now() });
return 'stopId=' + encodeURIComponent(matches[0].id);
}
@@ -181,14 +193,14 @@ async function transitousPlace(station) {
return `${station.lat},${station.lon}`;
}
const matches = await transitousFetch(
`/v1/geocode?text=${encodeURIComponent(station.name)}&type=STOP&place=52.52,13.41&numResults=1`,
`/v1/geocode?text=${encodeURIComponent(station.name)}&type=STOP&place=${geocodeBias()}&numResults=1`,
6 * 60 * 60 * 1000
);
if (!matches || matches.length === 0) throw new Error('Station bei Transitous nicht gefunden');
return matches[0].id;
}
async function transitousJourneys(fromStation, toStation, { timeISO = null, arriveBy = false, products = null } = {}) {
async function transitousJourneys(fromStation, toStation, { timeISO = null, arriveBy = false, products = null, pageCursor = null } = {}) {
const fromPlace = await transitousPlace(fromStation);
const toPlace = await transitousPlace(toStation);
@@ -197,6 +209,8 @@ async function transitousJourneys(fromStation, toStation, { timeISO = null, arri
`&maxTransfers=3&minTransferTime=5`;
if (timeISO) url += `&time=${encodeURIComponent(timeISO)}&arriveBy=${arriveBy}`;
if (products) url += `&transitModes=${productsToMotisModes(products)}`;
// Blättern: Cursor aus der vorherigen Antwort ("Früher"/"Später")
if (pageCursor) url += `&pageCursor=${encodeURIComponent(pageCursor)}`;
// 30s Cache: identische Suche direkt hintereinander = 1 Request
const data = await transitousFetch(url, 30000);
@@ -220,12 +234,22 @@ async function transitousJourneys(fromStation, toStation, { timeISO = null, arri
arrival: leg.endTime,
plannedArrival: leg.scheduledEndTime,
departurePlatform: leg.from?.track || null,
arrivalPlatform: leg.to?.track || null
arrivalPlatform: leg.to?.track || null,
// Zwischenhalte (kamen bisher mit und wurden weggeworfen)
stopovers: (leg.intermediateStops || []).map(s => ({
stop: { name: s.name },
arrival: s.arrival || s.departure || null,
plannedArrival: s.scheduledArrival || s.scheduledDeparture || null
}))
};
})
}));
return { journeys };
return {
journeys,
previousPageCursor: data.previousPageCursor || null,
nextPageCursor: data.nextPageCursor || null
};
}
@@ -294,6 +318,24 @@ async function transitousTrip(tripId) {
return { trip: { stopovers } };
}
// Stationen im Kartenausschnitt (für die Stations-Punkte auf der Live-Map).
// Koordinaten gerundet -> stabile Cache-Keys, 5min TTL.
async function transitousMapStops(bounds) {
const stops = await transitousFetch(
`/v6/map/stops?min=${bounds.south.toFixed(3)},${bounds.west.toFixed(3)}` +
`&max=${bounds.north.toFixed(3)},${bounds.east.toFixed(3)}`,
5 * 60 * 1000
);
return (stops || [])
.filter(s => s.stopId && s.lat != null && s.lon != null)
.map(s => ({
id: TRANSITOUS_ID_PREFIX + s.stopId,
name: s.name,
lat: s.lat,
lon: s.lon
}));
}
// Kompletter Streckenverlauf einer Fahrt (für die Live-Map).
// map/trips liefert nur das Segment im Kartenausschnitt - die GANZE
// Route steckt in der legGeometry des /trip-Endpoints. Gleiche URL wie