UNPKG

minotor

Version:

A lightweight client-side transit routing library.

204 lines (182 loc) 6.92 kB
import repl from 'node:repl'; import fs from 'fs'; import { Query, Router, StopsIndex, Time, Timetable } from '../router.js'; import { plotGraphToDotFile, prettyPrintRoute } from './utils.js'; export const startRepl = (stopsPath: string, timetablePath: string) => { const stopsIndex = StopsIndex.fromData(fs.readFileSync(stopsPath)); const timetable = Timetable.fromData(fs.readFileSync(timetablePath)); console.log(`Minotor Transit Router CLI`); console.log( 'Enter your stop (.find) or routing (.route) queries. Type ".exit" to quit.', ); const replServer = repl.start({ prompt: 'minotor> ', ignoreUndefined: true, }); replServer.context.stopFinder = stopsIndex; replServer.defineCommand('find', { help: 'Find stops by name using .find <query>', action(query: string) { this.clearBufferedCommand(); const stops = stopsIndex.findStopsByName(query); stops.forEach((stop) => { console.log(`${stop.name} (${stop.id})`); }); this.displayPrompt(); }, }); replServer.defineCommand('route', { help: 'Find a route using .route from <stationIdOrName> to <stationIdOrName> at <HH:mm> [with <N> transfers]', action(routeQuery: string) { this.clearBufferedCommand(); const parts = routeQuery.split(' ').filter(Boolean); const withTransfersIndex = parts.indexOf('with'); const maxTransfers = withTransfersIndex !== -1 && parts[withTransfersIndex + 1] !== undefined ? parseInt(parts[withTransfersIndex + 1] as string) : 4; const atTime = parts .slice( withTransfersIndex === -1 ? parts.indexOf('at') + 1 : parts.indexOf('at') + 1, withTransfersIndex === -1 ? parts.length : withTransfersIndex, ) .join(' '); const fromIndex = parts.indexOf('from'); const toIndex = parts.indexOf('to'); const fromId = parts.slice(fromIndex + 1, toIndex).join(' '); const toId = parts .slice( toIndex + 1, withTransfersIndex === -1 ? parts.indexOf('at') : parts.indexOf('at'), ) .join(' '); if (!fromId || !toId || !atTime) { console.log( 'Usage: .route from <stationIdOrName> to <stationIdOrName> at <HH:mm> [with <N> transfers]', ); this.displayPrompt(); return; } const fromStop = stopsIndex.findStopBySourceStopId(fromId) || stopsIndex.findStopsByName(fromId)[0]; const toStop = stopsIndex.findStopBySourceStopId(toId) || stopsIndex.findStopsByName(toId)[0]; if (!fromStop) { console.log(`No stop found for 'from' ID or name: ${fromId}`); this.displayPrompt(); return; } if (!toStop) { console.log(`No stop found for 'to' ID or name: ${toId}`); this.displayPrompt(); return; } const departureTime = Time.fromString(atTime); try { const query = new Query.Builder() .from(fromStop.sourceStopId) .to([toStop.sourceStopId]) .departureTime(departureTime) .maxTransfers(maxTransfers) .build(); const router = new Router(timetable, stopsIndex); const result = router.route(query); const arrivalTime = result.arrivalAt(toStop.sourceStopId); if (arrivalTime === undefined) { console.log(`Destination not reachable`); } else { console.log( `Arriving to ${toStop.name} at ${arrivalTime.time.toString()} with ${arrivalTime.legNumber - 1} transfers from ${stopsIndex.findStopById(arrivalTime.origin)?.name}.`, ); } const bestRoute = result.bestRoute(toStop.sourceStopId); if (bestRoute) { console.log(`Found route from ${fromStop.name} to ${toStop.name}:`); prettyPrintRoute(bestRoute); } else { console.log('No route found'); } } catch (error) { console.log('Error querying route:', error); } this.displayPrompt(); }, }); replServer.defineCommand('plot', { help: 'Plot a network graph using .plot from <stationId> to <stationId> at <HH:mm> [with <N> transfers] [to <graph.dot>]', action(routeQuery: string) { this.clearBufferedCommand(); const parts = routeQuery.split(' ').filter(Boolean); const withTransfersIndex = parts.indexOf('with'); const maxTransfers = withTransfersIndex !== -1 && parts[withTransfersIndex + 1] !== undefined ? parseInt(parts[withTransfersIndex + 1] as string) : 1; const atTimeIndex = parts.indexOf('at'); const atTime = parts .slice( atTimeIndex + 1, withTransfersIndex === -1 ? parts.indexOf('to', atTimeIndex) >= 0 ? parts.indexOf('to', atTimeIndex) : parts.length : withTransfersIndex, ) .join(' '); const fromIndex = parts.indexOf('from'); const toIndex = parts.indexOf('to'); const toFileIndex = toIndex !== -1 && parts.indexOf('to', toIndex + 1) !== -1 ? parts.indexOf('to', toIndex + 1) : -1; const fromId = parts.slice(fromIndex + 1, toIndex).join(' '); const toId = parts.slice(toIndex + 1, atTimeIndex).join(' '); const outputFile = toFileIndex !== -1 ? parts.slice(toFileIndex + 1).join(' ') : `${fromId.replace(/ /g, '')}-${toId.replace(/ /g, '')}-${atTime.replace(/:/g, '')}.dot`; if (!fromId || !toId || !atTime || isNaN(maxTransfers)) { console.log( 'Usage: .plot from <stationId> to <stationId> at <HH:mm> [with <N> transfers] [to <graph.dot>]', ); this.displayPrompt(); return; } const fromStop = stopsIndex.findStopBySourceStopId(fromId) || stopsIndex.findStopsByName(fromId)[0]; const toStop = stopsIndex.findStopBySourceStopId(toId) || stopsIndex.findStopsByName(toId)[0]; if (!fromStop) { console.log(`No stop found for 'from' ID or name: ${fromId}`); this.displayPrompt(); return; } if (!toStop) { console.log(`No stop found for 'to' ID or name: ${toId}`); this.displayPrompt(); return; } const departureTime = Time.fromString(atTime); try { const query = new Query.Builder() .from(fromStop.sourceStopId) .to([toStop.sourceStopId]) .departureTime(departureTime) .maxTransfers(maxTransfers) .build(); const router = new Router(timetable, stopsIndex); const result = router.route(query); plotGraphToDotFile(result, outputFile); } catch (error) { console.log('Error plotting route:', error); } this.displayPrompt(); }, }); };