UNPKG

minotor

Version:

A lightweight client-side transit routing library.

98 lines (76 loc) 4.62 kB
# Virtual transfers Many GTFS feeds only partially populate `transfers.txt`, so nearby stops that belong to different stations (e.g. a subway platform and an adjacent tram or bus terminal) end up with no walking connection and cannot be used as an interchange. A `TransferGenerator` can be passed to the parser to synthesize the missing walking transfers as a pre-processing step, before the timetable is serialized. ## Built-in implementation `StraightLineTransferGenerator` connects nearby stops that aren't already linked in the feed — for every stop it adds walking transfers to the other stops within a radius, estimating the walking time from the straight-line distance: ```ts import { GtfsParser, StraightLineTransferGenerator, extendedGtfsProfile, } from 'minotor/parser'; const parser = new GtfsParser( 'gtfs-feed.zip', extendedGtfsProfile, new StraightLineTransferGenerator({ maxDistanceMeters: 500 }), ); const timetable = await parser.parseTimetable(new Date()); ``` On top of the walking time, some adjustments make the estimate more realistic: - a **detour factor** as no path in the real world is a straight line - a flat **change penalty** added to every transfer, for the general overhead of changing The change penalty is symmetric, so the two directions of a transfer share the same minimum time. Existing transfers from the feed are always preserved: | Option | Default | Description | | ---------------------- | ------- | ----------------------------------------------------------------------- | | `maxDistanceMeters` | `500` | Radius within which two stops are connected | | `walkingSpeedKmh` | `4` | Assumed walking speed | | `detourFactor` | `1.3` | Multiplier from straight-line to real walking distance (`1` to disable) | | `changePenaltyMinutes` | `3` | Flat penalty added to every generated transfer (`0` to disable) | ## Custom implementation The straight-line estimate is relatively basic but works without external dependencies. For realistic walking times, you can implement the `TransferGenerator` interface yourself by e.g. delegating to an external pedestrian routing engine. The parser passes the candidate origin stops (route-served stops with coordinates) and deduplicates the result against the transfers already in the feed, keeping only links between stops a route actually calls at — so you can emit a transfer for every nearby pair without tracking what already exists. Return the directed transfers to add, using `TransferTypes.REQUIRES_MINIMAL_TIME` so the router treats them as walking connections (for both transfers and access/egress). `minTransferTime` is expressed in minutes. ```ts import { GtfsParser, TransferTypes, extendedGtfsProfile } from 'minotor/parser'; import type { GeneratedTransfers, TransferGenerator } from 'minotor/parser'; import type { Stop, StopsIndex } from 'minotor'; // Delegates to a hypothetical external walking-router HTTP API. class WalkingApiTransferGenerator implements TransferGenerator { async generate( originStops: Stop[], stops: StopsIndex, ): Promise<GeneratedTransfers> { const generated: GeneratedTransfers = new Map(); for (const origin of originStops) { // Reuse minotor's geo index to find nearby candidate stops. const neighbors = stops.findStopsByLocation( origin.lat, origin.lon, Infinity, this.maxDistanceKm, ); for (const neighbor of neighbors) { // Ask the external engine for a real door-to-door walking time. const walkTimeInMinutes = await callExternalRoutingEngine( origin.lat, origin.lon, neighbor.lat, neighbor.lon, ); const transfers = generated.get(origin.id) ?? []; transfers.push({ destination: neighbor.id, type: TransferTypes.REQUIRES_MINIMAL_TIME, minTransferTime: walkTimeInMinutes, }); generated.set(origin.id, transfers); } } return generated; } } const parser = new GtfsParser( 'gtfs-feed.zip', extendedGtfsProfile, new WalkingApiTransferGenerator('https://walk.example.com'), ); ``` Transfers are directed. The example above emits one edge per `origin -> neighbor`; the reverse edge is produced when the neighbor is itself visited as an origin (every candidate stop is passed as an origin), so a symmetric walking time yields a usable connection both ways.