Create routable network from GTFS files

Back to Public transport

This page describes how the model constructs a routable public transport network from raw GTFS source files. The relevant scripts are LoadFeeds.dms, RelevantSelection.dms, and GTFS.dms, all under SourceData/Infrastructuur/GTFS.


GTFS data model

GTFS (General Transit Feed Specification) is a widely used open standard for public transport timetables. The model reads the following GTFS files:

File Contents
agency.txt Transit agencies whose services are included in the feed
calendar.txt Weekly service schedules with start and end dates
calendar_dates.txt Exceptions to the calendar, such as added or removed service days
routes.txt Transit routes; a route groups trips that are presented to passengers as a single service
shapes.txt Geographic alignments of vehicle paths
stop_times.txt Scheduled arrival and departure times at each stop for each trip
stops.txt Stop locations with coordinates, names, and identifiers
trips.txt Individual trips, each belonging to a route and operating on a service calendar

For the Netherlands, GTFS feeds are publicly available from the national data portal gtfs.openov.nl. Multiple regional or national feeds may be combined; the model loads them via a consistent file structure defined in LoadFeeds/File_Structure.


Step 1: loading GTFS text files into FSS

GTFS files are plain text CSV files. Reading these repeatedly during analysis would be slow, so the model first converts them to GeoDMS FSS (Feature Storage Server) binary format using LoadFeeds/WriteFSS. This conversion is done once via Templates/LoadCSVThroughGDAL_T and the result is stored per file type in a directory determined by ModelParameters/GTFS_file_date.

All subsequent steps read from FSS via LoadFeeds/ReadFSS, using the same date-based path:

'%NetworkModel_Dir%/Infrastructuur/GTFS/' + GTFS_file_date + '/fss/' + filename + '.fss'

The GTFS_file_date parameter (e.g. '20231003') identifies the feed download date. It should be set to the date of the downloaded GTFS snapshot.


Step 2: date selection and service filtering

The model performs its analysis for a specific calendar date, set via ModelParameters/Analysis_date (e.g. '20231008'). The integrity check on this parameter requires that Analysis_date falls within a window of no more than 100 days after GTFS_file_date.

Because some trips depart before midnight and arrive after midnight, the model includes not just trips from the analysis date but also trips from the day before. This is handled by computing two Boolean flags per trip in protoTrips:

  • sel_today: the trip operates on Analysis_date, determined via Calendar_dates/Today and the Services exception table.
  • sel_yesterday: the trip operates on Analysis_date - 1 AND its departure time is after midnight on that day (i.e. Time/Day > 0), so that only the portion of the trip that falls on or after midnight is included.

The Services unit aggregates the calendar_dates.txt exceptions per service identifier, summing exception_type values for today and yesterday separately. A value of 1 means service is added; a value of 2 means service is removed. The resulting Today_exc and Yesterday_exc per service are used to derive sel_today and sel_yesterday per trip.


Step 3: constructing protoStops and the filtered Stops set

All stop records from stops.txt are first loaded into protoStops. Here, WGS84 coordinates (stop_lat, stop_lon) are converted to Dutch Rijksdriehoek (rdc) coordinates using LatLongWgs842RD. The StopUsed attribute then marks each stop as active if at least one stop time record in protoStopTimes links to it on the analysis day or the day before:

StopUsed := any(protoStopTimes/SelToday, protoStopTimes/protoStop_rel)
         || any(protoStopTimes/SelYesterday, protoStopTimes/protoStop_rel)

The filtered Stops unit in RelevantSelection is derived from protoStops by selecting only those stops where StopUsed is true. For each stop, a mode is assigned based on the most frequent transport mode serving it across all StopTimes (modus(StopTimes/Mode_rel, StopTimes/Stop_rel)). Several additional attributes are computed on this stop set that are later needed in the OV model:

Stop clusters for identifying adjacent platforms of the same station: Find_Halte_Clusters uses join_near_values to find all pairs of stops within DistanceStopClusters metres of each other, producing a c_Stop_Stop_rel set used later to filter out intra-cluster OD pairs.

NS station mapping: Find_Trainstations_Clusters spatially joins each stop against a list of known train stations (within DistanceTrainStationsSelection metres), then looks up the corresponding NS_Stations_rel from the NS pricing data via the station code. The IsNSICStation flag is similarly derived. These attributes are needed for NS through-pricing.

OV-fiets stations: Find_OVFiets_Stops spatially joins stops against known OV-fiets locations (within DistanceOVFietsStops metres), producing the IsOVFietsStation flag. Stops with this flag can be used as origins or destinations for the OV-fiets post-transport connection type.


protoStopTimes enriches the raw stop times with the trip’s service selection flags and computes the NextStopId for each stop-time record: the record in the same trip with the next sequential stop sequence number. Each non-final stop-time record thus forms one link from stop N to stop N+1.

Links are built separately for today and yesterday in protoStopTimes/Today/Links and protoStopTimes/Yesterday/Links. For yesterday’s links, departure and arrival times are shifted back by 24 hours (subtracting 24 * 60 * 60 time steps) so that they fall within the model’s time domain. Link length is taken from shape_dist_traveled where available, falling back to the Euclidean arc length otherwise.

The ScheduledLinks unit in RelevantSelection is the union of yesterday’s and today’s links. Each link has:

  • FromStop_rel and ToStop_rel: references into the filtered Stops set.
  • fromTime_rel and toTime_rel: departure and arrival times as time step indices.
  • Duration: travel time in seconds (with midnight-crossing handled via sub_or_null with wrap-around).
  • Length: route distance in kilometres.
  • Mode_rel, Route_rel, Agency_rel, Trip_rel: references to the relevant GTFS entities.
  • c_FromTime_Stop_rel and c_ToTime_Stop_rel: the c_Time_Stop combined keys used throughout the OV model.

The unique set of all departure and arrival time-stop events across all ScheduledLinks is also computed here as uq_TimeStop (aka Scheduled Space-Time Events, SSTE), and the unique set of stop locations that appear in the link set forms SL_Places, which feeds into the global Places domain.


The scheduled links alone do not yet form a routable network. A traveller sitting in a train at an intermediate stop must wait until the train departs again; this waiting time is not represented by any scheduled link. To make the network routable, waiting-at-stop links are added between consecutive time-stop events on the same trip.

This is done in GTFS/GetWaitingAtStop, using CreateWaitingAtStopSet_T. The construction proceeds as follows:

  1. All scheduled links are doubled into their from-events and to-events, producing a set of (trip, time) pairs called doubledLinks.
  2. The unique set of these pairs, WaitingAtStop, gives one node per moment that a vehicle is present at a stop within a trip.
  3. For each node, NextMoment identifies the next node on the same trip at the same stop, using add_or_null(id(.), 1) where both the trip and stop match.
  4. A waiting link is created from each node to its NextMoment, with Duration equal to the time difference between them. Midnight crossings are handled in the same way as for scheduled links.

Waiting links have Length = 0, Mode_rel = Waiting, and no Agency_rel. For Set_L (the NS network subset), waiting links at NS stations are included alongside the scheduled NS links, so that the Dijkstra traversal can wait for a later NS departure.


Step 6: the complete PublicTransportNet

GTFS/PublicTransportNet is the union of RelevantSelection/ScheduledLinks and GetWaitingAtStop/WaitingAtStop. This is the full routable network. All links carry a LinkType_rel attribute (Scheduled or Waiting_at_Stop) from Classifications/LinkTypes.

Two further attributes are computed on this union for NS pricing:

  • NS_StartPoint_rel and NS_EndPoint_rel: the NS station references for the from-stop and to-stop, via Stops/NS_Stations_rel.
  • c_NS_start_end_rel: the combined station-pair key used to look up NS through-prices in TariefEenhedenMatrix.

This network is the input to Get_Windowed_Agency_Set_T in PublicTransport_Prep, which applies time-window and agency filters to produce the Set_R and Set_L subsets used for the OD matrix calculations. See the Script reference for details on those steps.


Routes and TimeInvariantTypes

The Routes unit in RelevantSelection is not only built from the GTFS routes.txt but also includes a row for each Classifications/TimeInvariantType. This means that pre- and post-transport connection types (walking, cycling) are represented as routes in the same domain as scheduled public transport routes. This unified representation simplifies attribute lookup by mode and route throughout the model.