fifteen/apps/web/lib/valhalla.ts

62 lines
1.8 KiB
TypeScript

import { nextTuesdayDeparture } from "@transportationer/shared";
const VALHALLA_BASE = process.env.VALHALLA_URL ?? "http://valhalla:8002";
const VALHALLA_TRANSIT_BASE = process.env.VALHALLA_TRANSIT_URL ?? VALHALLA_BASE;
export type ValhallaCosting = "pedestrian" | "bicycle" | "auto";
const COSTING_MAP: Record<string, ValhallaCosting> = {
walking: "pedestrian",
cycling: "bicycle",
driving: "auto",
};
export interface IsochroneOpts {
lng: number;
lat: number;
travelMode: string;
contourMinutes: number[];
polygons?: boolean;
}
export async function fetchIsochrone(opts: IsochroneOpts): Promise<object> {
const isTransit = opts.travelMode === "transit";
const costing = isTransit ? "multimodal" : (COSTING_MAP[opts.travelMode] ?? "pedestrian");
const body: Record<string, unknown> = {
locations: [{ lon: opts.lng, lat: opts.lat }],
costing,
contours: opts.contourMinutes.map((time) => ({ time })),
polygons: opts.polygons ?? true,
show_locations: false,
};
if (isTransit) {
body.costing_options = { transit: { use_bus: 1.0, use_rail: 1.0, use_transfers: 1.0 } };
body.date_time = { type: 1, value: nextTuesdayDeparture() };
}
const base = isTransit ? VALHALLA_TRANSIT_BASE : VALHALLA_BASE;
const res = await fetch(`${base}/isochrone`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
signal: AbortSignal.timeout(30_000),
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Valhalla error ${res.status}: ${text}`);
}
return res.json();
}
export async function checkValhalla(): Promise<boolean> {
try {
const res = await fetch(`${VALHALLA_BASE}/status`, {
signal: AbortSignal.timeout(3000),
});
return res.ok;
} catch {
return false;
}
}