Refactor departure time calculation to account for walk duration
Introduce `trainWalkDurationSeconds` in `useDepartureTime` hooks for both mobile and web apps to filter train journeys based on total arrival time including walking. Add default origin station constants in core package and use them in mobile store instead of returning null when no origin is saved. Normalize HAFAS coordinates in destination station hooks to handle large integer values. Update `cleanLocation` to preserve full addresses with commas and remove the 10KB request body limit on ICS parsing to support larger calendar files. Make rate limiting configurable via environment variables to handle higher API fan-out from calendar event pages.
This commit is contained in:
@@ -12,6 +12,12 @@ import { hafasDateTime, parseHafasJourneys } from "@timetoleave/core";
|
||||
|
||||
const DEFAULT_BASE_URL = "";
|
||||
|
||||
type SearchJourneyOptions = {
|
||||
arriveBy?: boolean;
|
||||
};
|
||||
|
||||
const ARRIVE_BY_FALLBACK_WINDOW_MS = 2 * 60 * 60 * 1000;
|
||||
|
||||
function buildUrl(base: string, path: string, params: Record<string, string> = {}): string {
|
||||
const queryString = Object.entries(params)
|
||||
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
||||
@@ -20,6 +26,36 @@ function buildUrl(base: string, path: string, params: Record<string, string> = {
|
||||
return queryString ? `${full}?${queryString}` : full;
|
||||
}
|
||||
|
||||
function buildTripSearchBody(
|
||||
fromStationExtId: string,
|
||||
toStationExtId: string,
|
||||
hafasDate: string,
|
||||
hafasTime: string,
|
||||
arriveBy: boolean,
|
||||
numF = 5,
|
||||
) {
|
||||
return {
|
||||
svcReqL: [
|
||||
{
|
||||
meth: "TripSearch",
|
||||
req: {
|
||||
depLocL: [{ type: "S", extId: fromStationExtId }],
|
||||
arrLocL: [{ type: "S", extId: toStationExtId }],
|
||||
outDate: hafasDate,
|
||||
outTime: hafasTime,
|
||||
outFrwd: !arriveBy,
|
||||
numF,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeHafasCoordinate(value: number | undefined): number | undefined {
|
||||
if (value == null) return undefined;
|
||||
return Math.abs(value) > 1000 ? value / 1e6 : value;
|
||||
}
|
||||
|
||||
export class ApiClient {
|
||||
private readonly baseUrl: string;
|
||||
|
||||
@@ -114,24 +150,30 @@ export class ApiClient {
|
||||
fromStationExtId: string,
|
||||
toStationExtId: string,
|
||||
date: Date,
|
||||
options: SearchJourneyOptions = {},
|
||||
): Promise<Journey[]> {
|
||||
const { date: hafasDate, time: hafasTime } = hafasDateTime(date);
|
||||
const arriveBy = options.arriveBy === true;
|
||||
const journeys = await this.fetchTripSearch(
|
||||
buildTripSearchBody(fromStationExtId, toStationExtId, hafasDate, hafasTime, arriveBy),
|
||||
hafasDate,
|
||||
date,
|
||||
);
|
||||
|
||||
const body = {
|
||||
svcReqL: [
|
||||
{
|
||||
meth: "TripSearch",
|
||||
req: {
|
||||
depLocL: [{ type: "S", extId: fromStationExtId }],
|
||||
arrLocL: [{ type: "S", extId: toStationExtId }],
|
||||
outDate: hafasDate,
|
||||
outTime: hafasTime,
|
||||
numF: 5,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
if (!arriveBy || journeys.length > 0) {
|
||||
return journeys;
|
||||
}
|
||||
|
||||
const fallbackDate = new Date(date.getTime() - ARRIVE_BY_FALLBACK_WINDOW_MS);
|
||||
const { date: fallbackHafasDate, time: fallbackHafasTime } = hafasDateTime(fallbackDate);
|
||||
return this.fetchTripSearch(
|
||||
buildTripSearchBody(fromStationExtId, toStationExtId, fallbackHafasDate, fallbackHafasTime, false, 10),
|
||||
fallbackHafasDate,
|
||||
fallbackDate,
|
||||
);
|
||||
}
|
||||
|
||||
private async fetchTripSearch(body: unknown, hafasDate: string, queryDate: Date): Promise<Journey[]> {
|
||||
const res = await fetch(`${this.baseUrl}/api/hafas`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -140,7 +182,7 @@ export class ApiClient {
|
||||
|
||||
if (!res.ok) throw new Error(`Journey search failed: ${res.status}`);
|
||||
const data = await res.json();
|
||||
return parseHafasJourneys(data, hafasDate, date);
|
||||
return parseHafasJourneys(data, hafasDate, queryDate);
|
||||
}
|
||||
|
||||
async reverseGeocode(lat: number, lng: number): Promise<GeocodeResult | null> {
|
||||
@@ -225,12 +267,21 @@ export class ApiClient {
|
||||
|
||||
// Find the closest station by Euclidean distance
|
||||
const closest = stationResults.reduce((best: HafasLocation, candidate: HafasLocation) => {
|
||||
const bestDist = Math.hypot((best.lat ?? lat) - lat, (best.lng ?? lng) - lng);
|
||||
const candDist = Math.hypot((candidate.lat ?? lat) - lat, (candidate.lng ?? lng) - lng);
|
||||
const bestLat = normalizeHafasCoordinate(best.lat) ?? lat;
|
||||
const bestLng = normalizeHafasCoordinate(best.lng ?? best.lon) ?? lng;
|
||||
const candidateLat = normalizeHafasCoordinate(candidate.lat) ?? lat;
|
||||
const candidateLng = normalizeHafasCoordinate(candidate.lng ?? candidate.lon) ?? lng;
|
||||
const bestDist = Math.hypot(bestLat - lat, bestLng - lng);
|
||||
const candDist = Math.hypot(candidateLat - lat, candidateLng - lng);
|
||||
return candDist < bestDist ? candidate : best;
|
||||
}, stationResults[0]);
|
||||
|
||||
return closest;
|
||||
return {
|
||||
name: closest.name,
|
||||
extId: closest.extId,
|
||||
lat: normalizeHafasCoordinate(closest.lat),
|
||||
lng: normalizeHafasCoordinate(closest.lng ?? closest.lon),
|
||||
};
|
||||
}
|
||||
|
||||
async monitorStops(stopIds: string[]): Promise<WienerLinienDeparture[]> {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { Station } from "./types";
|
||||
|
||||
export const DEFAULT_ORIGIN_ADDRESS = "Goethegasse 36, 2340 Moedling";
|
||||
export const DEFAULT_ORIGIN_LAT = 48.0806926;
|
||||
export const DEFAULT_ORIGIN_LNG = 16.2908052;
|
||||
export const DEFAULT_ORIGIN_STATION_NAME = "Mödling Bahnhof";
|
||||
export const DEFAULT_ORIGIN_STATION_EXT_ID = "1231701";
|
||||
|
||||
export const DEFAULT_ORIGIN_STATION: Station = {
|
||||
name: DEFAULT_ORIGIN_ADDRESS,
|
||||
extId: DEFAULT_ORIGIN_STATION_EXT_ID,
|
||||
lat: DEFAULT_ORIGIN_LAT,
|
||||
lng: DEFAULT_ORIGIN_LNG,
|
||||
};
|
||||
@@ -5,3 +5,4 @@ export * from './formatting';
|
||||
export * from './status-utils';
|
||||
export * from './hafas-time';
|
||||
export * from './hafas-parser';
|
||||
export * from './defaults';
|
||||
|
||||
Reference in New Issue
Block a user