Expand description
An implementation of RAPTOR (Delling, Pajor, Werneck): given a public transit network, find all Pareto-optimal journeys between two stops, trading fewer transfers against earlier arrival.
§Quick start
The gtfs adapter implements Timetable over a parsed GTFS feed.
use gtfs_structures::Gtfs;
use jiff::civil::date;
use vulture::{SecondOfDay, Timetable};
use vulture::gtfs::GtfsTimetable;
let gtfs = Gtfs::new("path/to/gtfs.zip")?;
// Pin the timetable to one service date; inactive trips are filtered out.
let tt = GtfsTimetable::new(>fs, date(2026, 5, 4))?;
// The algorithm takes dense u32 indices, not GTFS string IDs – resolve first.
let start = tt.stop_idx("dilshad_garden").expect("unknown stop");
let target = tt.stop_idx("vishwavidyalaya").expect("unknown stop");
let journeys = tt
.query()
.from(start)
.to(target)
.max_transfers(10)
.depart_at(SecondOfDay::hms(9, 0, 0))
.run();
for j in &journeys {
println!("{} trip(s), arrives {}", j.plan.len(), j.arrival());
}§Concepts
RAPTOR proceeds in rounds. Round k holds the earliest known arrival time
at every stop reachable using at most k trips. Each round scans the routes
through stops improved in the previous round and then relaxes walking
footpaths to a fixed point. The output is a Pareto front: one Journey
per trip count between zero and max_transfers, with strictly earlier
arrivals as trip count rises.
The default routing is single-criterion: minimise arrival time, one
journey per trip count. The criterion is abstracted by the Label trait;
the default ArrivalTime carries a SecondOfDay. Multi-criterion
routing plugs in via Timetable::query_with_label with one of the
labels impls or a custom Label.
§Common patterns
- Single query.
Timetable::queryreturns a typestate builder. Chain.from(...).to(...).max_transfers(...).depart_at(...).run(). - Multi-platform stations.
gtfs::GtfsTimetable::station_stopsexpands a parent station to its child platforms for.from(...)/.to(...). - Range queries.
Query::depart_in_windowreturns aVec<RangeJourney>Pareto profile across departure times. Serial.run()is rRAPTOR;.run_par()/.run_with_pool(&pool)fan per-departure work across Rayon (default-onparallelfeature). - Server reuse. Allocate a
RaptorCacheonce viaRaptorCache::for_timetable; finish each chain with.run_with_cache(&mut cache). - Multi-threaded reuse.
RaptorCachePoolis theSyncvariant. Each worker callsRaptorCachePool::checkoutfor one query; the cache returns to the pool on drop. - Multi-criterion routing.
Timetable::query_with_labeltakes a customLabel.labels::ArrivalAndWalktrades arrival against accumulated walking time;labels::ArrivalAndFaretrades arrival against accumulated fare via a context supplied throughQuery::with_context. - Per-leg trip IDs and timing.
Journey::with_timingreconstructs per-leg(boarding stop, trip, depart, alight stop, arrive)tuples.Journey.plancarries the topology only. - Wheelchair-accessibility filter.
Query::require_wheelchair_accessibleskips trips and stops flaggedNotAvailablein GTFSwheelchair_accessible/wheelchair_boarding. - Multi-day search.
gtfs::GtfsTimetable::new_with_overnight_daysloadsgtfs::OvernightDaysadditional service days after the base date, shifting day-dtrips byd × 86 400seconds onto one time axis. - Sparse
transfers.txt.gtfs::GtfsTimetable::with_walking_footpathsbuilds bidirectional R-tree walking edges from stop coordinates. - Feed introspection.
gtfs::GtfsTimetable::featuresreturns agtfs::FeedFeaturessnapshot (counts, transfer-type breakdown, parent stations, shaped trips, wheelchair flags, footpath state).gtfs::FeedFeatures::suggestionsmaps it to advisory strings.
§Custom backends
Implement the Timetable trait directly for non-GTFS data. Identifiers
are dense u32 newtypes (StopIdx, RouteIdx, TripIdx); the
adapter interns external IDs at construction. The trait carries one
mandatory soundness contract, no-overtaking within a route, documented
on Timetable. Footpaths returned by Timetable::get_footpaths_from
are direct walks only; the algorithm chains them within a round, so
transitive closure is not required. Adapters whose relation is closed
can opt into single-pass relaxation via
Timetable::footpaths_are_transitively_closed.
manual::SimpleTimetable is a hand-rolled in-memory adapter for tests
and small fixtures.
Issue-by-issue audit against the paper:
docs/soundness.md.
§Errors
Four patterns, picked per call:
Resultfor construction.gtfs::GtfsTimetablereturnsResult<_, gtfs::GtfsError>. Custom adapters should follow the same pattern with their own error enum.Optionfor lookups.gtfs::GtfsTimetable::stop_idx,gtfs::GtfsTimetable::route_idx, and similar “find anXmatchingY” accessors returnOption;Nonemeans “not in this timetable”.Resultfor plan reconstruction.Journey::with_timingreturnsResult<_, TimingError>; failure modes (no boarding stop reachable, no catchable trip, alighting stop not on route) are typed variants for debug context.- Panics for programmer-violated invariants. A
RaptorCachesized for one timetable, used against a differently-sized one, panics with an assertion message. CustomTimetableadapters that violate the no-overtaking contract typically produce wrong answers rather than panic.
§Cargo features
parallel(default-on) – enablesQuery::run_par/Query::run_with_poolviarayon.default-features = falsedrops the dependency;RaptorCachePoolstays available.gtfs-bench– thegtfscriterion benchmark.internal– theraptorcriterion benchmark overmanual.
Modules§
- ffi
- Concrete-typed entry points for FFI bindings (PyO3, wasm-bindgen,
and similar) that can’t easily monomorphise the generic
Query<'tt, T, L, M>builder across the language boundary. - gtfs
Timetableimplementation backed by a GTFS feed.- labels
- Canned
Labelimplementations. - manual
- In-memory
Timetableadapter you build by hand with.route(...)/.footpath(...)calls. Useful when your data isn’t from a parsed GTFS feed – for tests, custom data sources, and toy examples. Hand-rolled in-memoryTimetablefor tests, fixtures, and small synthetic networks.
Structs§
- Arrival
Time - Single-criterion label = arrival time at a stop. Default
Lthroughout the algorithm. Constructing from aSecondOfDayis direct; extracting back isarrival(). - Duration
- A length of time, in seconds. Distinct from
SecondOfDay(a point in time) so the algorithm signatures can express which kind they expect: a walk-time offset, a transfer time, and a dwell time are allDuration; an arrival time and a departure time are bothSecondOfDay. - Endpoints
- A bag of
(stop, walk-time-offset)pairs used as the origin or target of a query. - Journey
- A journey found by the RAPTOR algorithm.
- Needs
Departure - Marker type: a
Querythat hasn’t yet had a departure configured..run()is not callable in this state; callQuery::depart_at(single departure) orQuery::depart_in_window(range query) first. - Pooled
Cache - Scoped handle from
RaptorCachePool::checkout. Owns aRaptorCachefor its lifetime and returns it to the pool on drop. Derefs toRaptorCache; pass&mut pooled_cachewhere&mut RaptorCacheis wanted. - Query
- Builder for a RAPTOR query. Constructed via
Timetable::query. - Range
Departure - Marker type: a range-query
Queryover a window of departure times. The supplied iterator is collected eagerly at builder time and normalised to a descending, deduplicatedVec<SecondOfDay>(the order rRAPTOR scans them in)..run()returnsVec<RangeJourney<L>>. - Range
Journey - One entry in a range-query profile: a departure time paired with the
Journeyit produces. Returned byQuery::run/Query::run_with_cacheon builders configured withQuery::depart_in_window. - Raptor
Cache - Reusable scratch buffers for
Query::run_with_cache. - Raptor
Cache Pool - A
Syncpool ofRaptorCaches sized for one timetable. Each worker callscheckout()for the duration of one query. - Route
Idx - Dense index of a route within a
Timetable. Indices are in0..tt.n_routes(). - Second
OfDay - A point in time, in seconds since midnight on the timetable’s
service date. Wraps a
u32– the day is 86,400 seconds;u32covers feed quirks like trips encoded past 24h with room to spare. - Single
Departure - Marker type: a single-departure
Query..run()returnsVec<Journey<L>>. - StopIdx
- Dense index of a stop within a
Timetable. Indices are in0..tt.n_stops(). - Timed
Leg - One transit leg of a
Journeywith reconstructed timing. Produced byJourney::with_timing. - Transfers
- User-facing transfer cap. The algorithm explores rounds 0
through
transfersinclusive, soTransfers(10)lets a journey involve up to 10 boardings.u8is plenty – practical journey queries cap at single digits. - Trip
Context - Per-trip context passed to
Label::extend_by_trip. Single-criterion labels read onlyarrival; multi-criterion impls (fare-aware, route-preference, transfer-penalty) draw on the remaining fields. - TripIdx
- Dense index of a trip within a
Timetable. Indices are in0..n_trips.
Enums§
- Parse
Second OfDay Error - Errors produced when parsing a string into a
SecondOfDayviaFromStr. - Timing
Error - Failure modes for
Journey::with_timing. - TryFrom
Signed Duration Error - Errors produced when narrowing a
jiff::SignedDurationinto a vultureDuration.
Traits§
- Into
Endpoints - Convert any of the natural query-endpoint inputs into an
Endpointsvalue the algorithm can consume. - Label
- A label attached to a
(round, stop)cell during the RAPTOR scan. - Timetable
- Models a route-based transit network for the RAPTOR algorithm.