import type { BikeRoute, BikeStep } from "@timetoleave/core"; import { OSRM_URL } from "./constants"; import { ApiClient } from "./api-service"; // --------------------------------------------------------------------------- // OSRM response types (internal, not exported) // --------------------------------------------------------------------------- interface OsrmStep { name: string; distance: number; duration: number; maneuver: { instruction?: string; type: string; modifier?: string }; } interface OsrmRoute { distance: number; duration: number; legs: Array<{ steps: OsrmStep[] }>; } interface OsrmResponse { code: string; routes: OsrmRoute[]; } // --------------------------------------------------------------------------- // Helper: Build human-readable instruction from OSRM step data // --------------------------------------------------------------------------- function stepInstruction(step: OsrmStep): string { if (step.maneuver.instruction) return step.maneuver.instruction; const modifier = step.maneuver.modifier ? ` ${step.maneuver.modifier}` : ""; return `${step.maneuver.type}${modifier}${step.name ? ` onto ${step.name}` : ""}`; } // --------------------------------------------------------------------------- // WalkRoutingClient — Wraps the OSRM foot routing API // // OSRM routes are cached for 5 minutes. Walk routes between the same // coordinates rarely change, and caching significantly reduces API load. // --------------------------------------------------------------------------- export class WalkRoutingClient { private client: ApiClient; private readonly defaultTtlMs: number; constructor( baseUrl: string = OSRM_URL, ttlMs: number = 5 * 60 * 1000, // 5 minutes — routes are very stable ) { this.client = new ApiClient({ baseUrl, defaultTimeoutMs: 10_000, defaultTtlMs: ttlMs, maxRetries: 3, }); this.defaultTtlMs = ttlMs; } async getWalkRoute(fromLat: number, fromLng: number, toLat: number, toLng: number): Promise { const coords = `${fromLng},${fromLat};${toLng},${toLat}`; const path = `/route/v1/foot/${coords}`; const cacheKey = `osrm:walk:${coords}`; const res = await this.client.get( path, { overview: "false", steps: "true" }, { cacheKey, ttl: this.defaultTtlMs, }, ); if (res.code !== "Ok" || !res.routes.length) return null; const route = res.routes[0]; const steps: BikeStep[] = (route.legs[0]?.steps ?? []).map((s) => ({ name: s.name, distance: s.distance, duration: s.duration, instruction: stepInstruction(s), })); return { distance: route.distance, duration: route.duration, steps, }; } /** * Expose cache statistics for debugging/monitoring. */ public cacheStats() { return this.client.cacheStats(); } /** * Clear the cache (useful for testing or forced refresh). */ public clearCache() { this.client.clearCache(); } }