UNPKG

minotor

Version:

A lightweight client-side transit routing library.

148 lines (139 loc) 3.84 kB
import { Latitude, LocationType, Longitude, Platform, SourceStopId, Stop, StopId, StopsMap, } from '../stops/stops.js'; import { Maybe, parseCsv } from './utils.js'; export type GtfsLocationType = | 0 // simple stop or platform (can also be empty) | 1 // station | 2 // entrance / exit | 3 // generic node | 4; // boarding area export type StopEntry = { stop_id: SourceStopId; stop_name: string; stop_lat?: Latitude; stop_lon?: Longitude; location_type?: GtfsLocationType; parent_station?: SourceStopId; platform_code?: Platform; }; type ParsedStop = Stop & { parentSourceId?: SourceStopId; }; export type ParsedStopsMap = Map<SourceStopId, ParsedStop>; /** * Parses the stops.txt file from a GTFS feed. * * @param stopsStream The readable stream containing the stops data. * @return A mapping of stop IDs to corresponding stop details. */ export const parseStops = async ( stopsStream: NodeJS.ReadableStream, platformParser?: (stopEntry: StopEntry) => Maybe<Platform>, ): Promise<ParsedStopsMap> => { const parsedStops = new Map<SourceStopId, ParsedStop>(); let i = 0; for await (const rawLine of parseCsv(stopsStream)) { const line = rawLine as StopEntry; const stop: ParsedStop = { id: i, sourceStopId: line.stop_id + '', name: line.stop_name, lat: line.stop_lat, lon: line.stop_lon, locationType: line.location_type ? parseGtfsLocationType(line.location_type) : 'SIMPLE_STOP_OR_PLATFORM', children: [], ...(line.parent_station && { parentSourceId: line.parent_station }), }; if (platformParser) { try { const platform = platformParser(line); if (platform) { stop.platform = platform; } } catch { console.info(`Could not parse platform for stop ${line.stop_id}.`); } } parsedStops.set(line.stop_id + '', stop); i = i + 1; } for (const [sourceStopId, stop] of parsedStops) { if (stop.parentSourceId) { const parentStop = parsedStops.get(stop.parentSourceId); if (!parentStop) { console.warn( `Cannot find parent stop ${stop.parentSourceId} of ${sourceStopId}`, ); continue; } stop.parent = parentStop.id; parentStop.children.push(stop.id); } } return parsedStops; }; /** * Builds the final stop map indexed by internal IDs. * Excludes all stops that do not have at least one valid stopId * as a child, a parent, or being valid itself. * * @param parsedStops - The map of parsed stops. * @param validStops - A set of valid stop IDs. * @returns A map of stops indexed by internal IDs. */ export const indexStops = ( parsedStops: ParsedStopsMap, validStops?: Set<StopId>, ): StopsMap => { const stops = new Map<StopId, Stop>(); for (const [, stop] of parsedStops) { if ( !validStops || validStops.has(stop.id) || (stop.parent && validStops.has(stop.parent)) || stop.children.some((childId) => validStops.has(childId)) ) { stops.set(stop.id, { id: stop.id, sourceStopId: stop.sourceStopId, name: stop.name, lat: stop.lat, lon: stop.lon, locationType: stop.locationType, platform: stop.platform, children: stop.children.filter( (childId) => !validStops || validStops.has(childId), ), parent: stop.parent, }); } } return stops; }; const parseGtfsLocationType = ( gtfsLocationType: GtfsLocationType, ): LocationType => { switch (gtfsLocationType) { case 0: default: return 'SIMPLE_STOP_OR_PLATFORM'; case 1: return 'STATION'; case 2: return 'ENTRANCE_EXIT'; case 3: return 'GENERIC_NODE'; case 4: return 'BOARDING_AREA'; } };