GTFS public transport¶
IduEdu can build a static public-transport iduedu.UrbanGraph from a
local GTFS Schedule
feed. The source may be a directory containing *.txt tables, a ZIP archive,
an already-read iduedu.gtfs.GTFSFeed, or a list of those merged into
one feed (see Merging several feeds). The GTFS public transport
notebook walks through every step below on small synthetic feeds.
Building a graph¶
from iduedu import get_gtfs_public_transport_graph
public_transport = get_gtfs_public_transport_graph(
"feed.zip",
service_date="2026-08-04",
start_time="07:00:00",
end_time="10:00:00",
)
service_date applies calendar.txt and calendar_dates.txt. It accepts
a Python date or datetime, YYYY-MM-DD, or YYYYMMDD. start_time
is inclusive and end_time is exclusive; both accept GTFS time strings,
Python time values, or seconds. An end time earlier than the start time
selects an overnight window.
Omitting all three filters intentionally aggregates every trip in the feed. This can combine weekday, weekend, and seasonal service, so pass a date and time window when the graph should represent a particular operating period.
Required and optional tables¶
The builder requires agency.txt, stops.txt, routes.txt,
trips.txt, and stop_times.txt. At least one of calendar.txt and
calendar_dates.txt must also be present.
The builder also reads frequencies.txt, shapes.txt, pathways.txt,
levels.txt, and feed_info.txt when available. transfers.txt and
exact time-dependent transfer routing are not implemented yet.
Base route types are represented as tram (0), subway (1), train (2),
bus (3), ferry (4), cable_tram (5), aerial_lift (6),
funicular (7), trolleybus (11), and monorail (12). Google’s extended
route types are mapped by explicit ranges: 100-117 train, 200-209 coach
(kept apart from city buses), 400-404 and 406-499 subway, 405 monorail,
700-716 bus, 800-899 trolleybus, 900-906 tram, 1000 and 1200
ferry, 1300 aerial_lift, 1400 funicular, and 1500 taxi. Any
other code, including the miscellaneous 300, 500, 600, 1600 and 1700 ranges,
remains public_transport rather than a guess from neighbouring codes.
Static time model¶
Trips with the same route, direction, shape, stop order, and pickup/drop-off
rules form a route pattern. For each pattern segment, time_min is the
median scheduled time from departure at one stop to arrival at the next.
Boarding edges represent expected waiting rather than vehicle motion:
scheduled service uses half the mean gap between consecutive departures at the pattern stop;
frequencies.txtservice usesheadway_secs / 120and duration-weighted averaging when frequency periods overlap the selected window;a pattern stop with only one scheduled departure has no estimable headway, so its boarding edge is omitted by default. Set
single_departure_wait_minto retain it with an explicit fallback.
Boarding and alighting edges always have length_meter = 0. Alighting also
has time_min = 0. GTFS waiting times come from the feed and do not use
iduedu.TransportRegistry defaults.
Geometry and distance¶
When shapes.txt supplies usable route geometry, segment geometry is cut
from the matching shape and length_meter is its measured length. Otherwise
the builder creates a straight line between consecutive route-stop points and
assigns sqrt(2) * straight_line_length as the estimated travel distance.
The square-root-of-two correction applies only to vehicle movement edges such
as bus, tram, or subway; it never applies to boarding or alighting.
The graph CRS must be projected in metres. If crs is omitted, IduEdu
estimates a local UTM CRS from the feed stops.
Merging several feeds¶
A city is often published as several feeds, and every feed numbers its own routes, trips, and stops. Pass a list of sources to merge them before the graph is built:
public_transport = get_gtfs_public_transport_graph(
["subway.zip", "bus.zip"],
service_date="2026-08-04",
start_time="07:00:00",
end_time="10:00:00",
)
A list is merged with iduedu.merge_gtfs_feeds(), which qualifies every
identifier by its source, so route 1 of one feed and route 1 of another
stay distinct. Call it directly to choose the prefixes or to fuse stops that
different agencies publish for the same place:
from iduedu import get_gtfs_public_transport_graph, merge_gtfs_feeds
feed = merge_gtfs_feeds(["subway.zip", "bus.zip"], merge_stops_within=20)
public_transport = get_gtfs_public_transport_graph(feed, service_date="2026-08-04")
Stops are kept distinct by default: fusing them changes the network topology, and an inferred transfer would look like a published one. Distances of about 15-25 m suit feeds from different agencies.
Stations, pathways, and intermodal joining¶
pathways.txt creates station-internal pathway edges. Explicit
traversal_time is converted from seconds to minutes; otherwise time is
estimated from pathway length and walk_speed_m_per_min. Station entrances,
nodes, boarding areas, and platforms are retained when referenced by the
served stops or pathways.
A stop that names a parent_station is linked to it with a zero-cost,
bidirectional station_link edge. Only stops already present in the graph
are linked. Without these edges, a station described down to boarding areas
would leave its platforms in a separate component that keep_largest_subgraph
removes.
To join GTFS public transport to an OSM walking graph, build the walk layer first and use the same CRS:
from iduedu import get_gtfs_public_transport_graph, get_walk_graph, join_pt_walk_graph
walk = get_walk_graph(osm_id=1114252)
public_transport = get_gtfs_public_transport_graph(
"feed.zip",
service_date="2026-08-04",
start_time="07:00:00",
end_time="10:00:00",
crs=walk.crs,
)
intermodal = join_pt_walk_graph(public_transport, walk)
API reference¶
- iduedu.get_gtfs_public_transport_graph(feed, *, service_date=None, start_time=None, end_time=None, crs=None, single_departure_wait_min=None, walk_speed_m_per_min=83.33333333333333)[source]
Build a static public-transport
UrbanGraphfrom a local GTFS feed.Boarding weights use the first-iteration half-headway heuristic. Scheduled services use half the mean gap between consecutive departures of the same route pattern at the same stop.
frequencies.txtusesheadway_secs / 120.Nonedate/time values are intentionally unbounded: with all three filters omitted, every trip in the feed participates. This is an aggregate all-service graph, not the timetable for a particular real-world day.- Parameters:
feed (
Union[str,Path,GTFSFeed,Sequence[str|Path|GTFSFeed]]) – Directory containing GTFS text files, a GTFS ZIP archive, an already-readiduedu.gtfs.GTFSFeed, or a sequence of any of those. A sequence is merged withiduedu.merge_gtfs_feeds()first, which qualifies identifiers by source; pass the merged feed yourself when you need to control prefixes or fuse stops across feeds.service_date (
date|datetime|str|None) – Optional service date used withcalendar.txtandcalendar_dates.txt. Acceptsdate,YYYY-MM-DDorYYYYMMDD.start_time (
time|str|int|float|None) – Inclusive departure-time bound.Nonemeans unbounded.end_time (
time|str|int|float|None) – Exclusive departure-time bound.Nonemeans unbounded.crs (
Any|None) – Projected target CRS. If omitted, a local UTM CRS is estimated.single_departure_wait_min (
float|None) – Explicit wait fallback for a pattern-stop with one scheduled departure. IfNone, its boarding edge is omitted.walk_speed_m_per_min (
float) – Fallback speed for pathways without an explicit traversal time.
- Return type:
- iduedu.merge_gtfs_feeds(sources, *, prefixes=None, merge_stops_within=None, crs=None, validate=True)[source]¶
Combine several GTFS feeds into one that the graph builder can consume.
Identifiers are qualified by their source, so a
route_idof1in two feeds becomes two distinct routes. Definitions and references are rewritten together, and blank optional references stay blank.- Parameters:
sources (
Sequence[str|Path|GTFSFeed]) – Paths to GTFS directories or ZIP archives, or already-read feeds. A single source is returned unchanged unless stop merging is requested, so this function is a no-op where merging is not needed.prefixes (
Optional[Sequence[str]]) – One prefix per source. Defaults to each source’s file name, with a numeric suffix where two sources share one.merge_stops_within (
float|None) – Distance in metres below which stops published by different feeds are treated as the same place and fused into one node.None(the default) keeps every stop distinct: fusing stops changes the topology of the network, and doing it silently would make an inferred transfer indistinguishable from a published one. Values around 15–25 m suit feeds from different agencies; the 1 m used for OSM platforms is far too tight here, because two agencies survey the same kerb independently.crs (
Any|None) – Projected CRS in metres used for the distance test. Estimated from the stops when omitted.validate (
bool) – Runiduedu.gtfs.validate_gtfs_feed()on the result. Leave this on: it is what makes the merge verifiable rather than merely plausible.
- Return type:
- Returns:
A
iduedu.gtfs.GTFSFeedwhosesourcenames the merged inputs.- Raises:
ValueError – If no sources are given, or the number of prefixes does not match the number of sources.
GTFSValidationError – If the merged feed breaks a GTFS contract.
- iduedu.gtfs.read_gtfs_feed(source, *, table_names=None, usecols=None)[source]¶
Read GTFS Schedule text tables from a directory or ZIP archive.
All fields are retained as strings. Numeric, date, time and geometry conversion belongs to the validation and graph-building layers.
- iduedu.gtfs.validate_gtfs_feed(feed)[source]¶
Validate the structural GTFS contracts required by the graph builder.