Skip to content

Walking and cycling legs

MaartenHilferink edited this page Nov 9, 2026 · 25 revisions

Walking and cycling appear in the model in two roles:

  • as legs of a public transport journey: the pre-transport leg from the origin to the first stop and the post-transport leg from the last stop to the destination;
  • as direct trips from origin to destination: the components WW (walking) and CC (cycling).

This page describes step by step how the model computes both. The car is described on Private transport. How the legs are joined to the public transport chains is on From chains to journeys.

Contents


Step 1: the network

Walking

Walking uses the OSM network: the road segments that the OSM classification marks as pedestrian (IsPedestrian). The extract follows the analysis moment: AnalysisMoment chooses a row of ModelParameters/AnalysisMomentPresets, and its OSM_file_date is the date of the extract. The model reads it from the network cache %LocalDataProjDir%/OSM/Final_Network_<date>_r3_<hash>.mmd, which it makes from the Geofabrik shapefiles (see Private transport, OSM). Every link can be walked in both directions.

Cycling

ModelParameters/Advanced/CyclingNetwork chooses the network for all cycling: cycling as pre- and post-transport, the OV-fiets, and the direct cycling trip.

With 'OSM', the default, the network consists of the OSM segments of the road types in the bike network (IsPartOfBikeNetwork, a BikeSpeed above 1 km/h), from the same extract as walking. Every link can be ridden in both directions, because the oneway of the Geofabrik shapefiles does not say whether cyclists are exempt.

With 'Fietsersbond', the network is the route network of the Fietsersbond: the network of 2024, delivered by PBL on 25 May 2025 (Fietsersbond_file_date), in %NetworkModel_Dir%/Infrastructuur/Fietsersbond_20250525. One delivery serves all analysis moments. SourceData/Infrastructure/Fietsersbond/Network_Cycling turns it into a network with the attributes of the OSM bike network:

  • It keeps the links with bicycle access (toegang not 'geen') and two known nodes: 1,449,915 of the 1,771,242 links, 160,500 km.
  • A link with toegang 'heen' or 'terug' (10% of the links) is a one-way cycle path: it can only be ridden in its direction of travel (IsOneDirection, with the geometry in that direction).
  • The 217 river ferries up to Fietsersbond_MaxFerryLength (2 km) take part, with Fietsersbond_FerryExtraTime (5 min) for waiting and crossing on top of the cycling time. The 62 longer crossings (2 to 39 km) do not. No origin, destination or stop is connected to a ferry.
  • The road type, used for the junction penalties, is ferry, tertiary (cars above 30 km/h), residential (cars up to 30 km/h) or cycleway (cycle paths and pedestrian areas).
  • The topology is built from the end points of the segments, as for OSM, not from van_id and naar_id.
  • The network covers the Netherlands only, so there are no cycling legs to stops across the border.

With the Fietsersbond network the signature of the output names the network (Impl/CyclingNetwork_FileTag, _fb20250525-veer2000m5min_r2, with the revision Impl/Fietsersbond_Revision at the end). The output folder of a component that cycles gets _fb after its name (Impl/CyclingNetwork_DirTag), for example 20251014_b2025_CC_fb_<hash>. So the output of both networks can stand side by side. WW, W_PT_W and Car do not depend on the cycling network. The pre- and post-transport caches have the network in the hash of their name (see The cache).

The two networks compare as follows (#124):

  • 95% of the Fietsersbond network lies within 25 m of the OSM bike network. Most of the rest is in OSM under a road type outside the bike network (footways, pedestrian areas, tracks without a grade).
  • 72% of the OSM bike network in the Netherlands lies near a Fietsersbond link; the rest is mainly path and service.
  • At 14 km/h on every link, without junction penalties and one-way cycle paths, trips between neighbourhoods of up to 15 minutes take as long over both networks: the median ratio of the travel times is 1.000.
  • In the model, with the junction penalties and the one-way cycle paths, the direct cycling trip of Y2025 in the morning rush hour reaches 7.2% fewer OD pairs within 90 minutes over OSM than over the Fietsersbond network. For the pairs that both reach, it takes 2.4% longer in the median. With cycling in the pre- or post-transport, public transport reaches 1.6 to 5.6% fewer pairs over OSM. Which of the differences between the two measurements causes this is not measured.

Speed

Every link has a constant speed: WalkingSpeed_kmhr (4.5 km/h) for walking and CyclingSpeed_kmhr (14 km/h) for cycling. An e-bike is a run with other cycling values: 20 km/h and a cost per km of its own (Cycling_CostPerKm). Its output goes to folders of its own; see An e-bike.

Step 2: preparing the network

PublicTransport_Prep/Create_Optimised_Network_Walking and Create_Optimised_Network_Cycling (Templates/CreateNetwork_Efficient_T) prepare one network per mode, which all pre- and post-transport types of that mode share:

  1. connect_ne connects the origins, the destinations and the stops to the network at the nearest connectable point.
  2. Every junction that a route passes costs time, a junction penalty. The penalty depends on the number of road links at the node: 0 s for 2 or fewer, 2 s for 3, 5 s for 4 and 6 s for more (Advanced/Junction_Penalties, the same for walking and cycling). A junction where every road link is a slow-traffic road costs nothing, because the with_*_lowerSpeedLinks_* penalties are 0 s. A slow-traffic road is a road type with a car speed of at most 30 km/h, such as a residential street, a service road or a footway (Classifications/OSM/roadType/IsSlowTrafficRoad). In a residential area a walker or cyclist does not stop at every side street. Each link carries half of the penalty of each of its end nodes, so a route pays the penalty of a junction once. Only road links count, not the connection links of origins, destinations and stops.
  3. The model removes the nodes that are not junctions and merges their links into longer links. Routes and travel times do not change (see Network optimisation).

Step 3: the pre- and post-transport caches

The base sets

All pre- and post-transport types are selections from four base sets. The model computes each base set once per analysis date, origin set and destination set:

Base set From To Network Cache
Org2Stops_Walking every origin every stop walking Legs_<Set>toStops_Walking_<hash>.parquet
Org2Stops_Cycling every origin every stop cycling Legs_<Set>toStops_Cycling_<hash>.parquet
Stops2Dest_Cycling every stop every destination cycling Legs_Stops2<Set>_Cycling_<hash>.parquet
Stops2Dest_Walking every stop every destination walking Legs_Stops2<Set>_Walking_<hash>.parquet, only when the origin and destination sets differ

When the origin set and the destination set are the same (PublicTransport_Prep/WalkingMirror), the model reads walking from a stop to a destination as the mirror of Org2Stops_Walking. This works because the walking network has no one-way links. Cycling has a cache per direction, because the Fietsersbond network has one-way cycle paths. On the OSM network both directions give the same legs.

The computation

Per base set the model runs one Dijkstra over the whole prepared network (PublicTransport_Prep/TimeInvariantConnections/<base set>/Create_Routes, Templates/CreateNetwork_Pedestrian_Bike_T, with impedance_matrix_od64):

  1. from every origin (pre-transport) or every stop (post-transport);
  2. to every stop or every destination that is reachable;
  3. cut at the maximum time of the base set: MaxWalkingTime_Org2Stops, MaxCyclingTime_Org2Stops, MaxCyclingTime_Stops2Dest and MaxWalkingTime_Stops2Dest, 15 minutes each.

The time of a leg is its network time: the length of each link divided by the speed, plus the junction penalties.

The cache

Each base set is a parquet file (zstd) in %LocalDataProjDir%/IntermediateResults, so that it can also be used outside the model:

  • The columns are BuurtCode (the CBS code of the neighbourhood), stop_id and stop_name (from the GTFS feed), Traveltime (the network time in whole seconds, uint16) and TravelDist (whole metres, uint32). The model rounds them when it computes them, so that a run computes the same with and without the cache.
  • In the name, <Set> is the origin or destination set (Impl/OrgSet_NameTag, DestSet_NameTag, for example B2025), <hash> a hash of the signature.
  • The signature is in <name>.signature.txt next to the cache. It names the base set, the network and speed (with the OSM network cache by its hash, the junction penalties and the clean-up of the network), and the maximum time of the base set. It also names the origin and destination sets with the settings that place them, the analysis date and feed, the numbers of places and stops, the format and the revision. The model reads a cache only when its signature matches the settings of the run. Otherwise it computes the base set in the run. The next write then makes a new cache, which has another hash and so stands next to the old one.

For 2025 (Buurt2025 to Buurt2025) the walking cache is 5 MB and the two cycling caches 23 and 22 MB.

Generating the caches

With Advanced/PrePostTransportDecoupled (default TRUE) a run reads the cache of a base set when it exists, and computes the base set itself when it does not. With FALSE every run computes the base sets, and the model writes no cache. The caches are written:

  • with batch\UpdateAll.cmd legs: all base sets without a cache with the current signature, in one GeoDmsRun, once per analysis date and neighbourhood set. batch\UpdateAll.cmd all does the same before the output of each public transport component, so that the portions of the output read the caches instead of each computing the base sets;
  • in GeoDmsGui, under /MakeUnlinkedData/Step2_PrePostTransport: right-click Generate_All (all base sets without a cache), or Org2Stops_Walking, Org2Stops_Cycling or Stops2Dest_Cycling (one base set), and choose Update TreeItem; then reload the configuration (Alt+R). See Running the steps in GeoDmsGui.

The caches do not depend on the part of the day or the component. They must be computed again after a change in the network (the OSM extract or CyclingNetwork), the walking or cycling speed, the maximum time of a base set, the origins, the destinations, or the stops (the feed and the analysis date). The maximum time of another pre- or post-transport type applies when the model reads the legs (see Step 4), so it does not change these caches.

Step 4: the pre- and post-transport types of the components

Selection from the base sets

The pre- and post-transport type of a component (see Components and merging) takes the connections of its base set in PublicTransport_Prep/Legs/<type> (template Legs_T). These are PublicTransport_Prep/PrePostTransport/<base set>/Connections: the cache, or the base set computed in the run. The type keeps the legs to or from the stops of its stop set, within its own maximum time:

Type Code Base set Stops
Org2Stops_Walking W Org2Stops_Walking all
Org2Stops_Cycling C Org2Stops_Cycling all
Org2RM_Stops_Cycling Crm Org2Stops_Cycling rail and metro
Org2RMT_Stops_Cycling Crmt Org2Stops_Cycling rail, metro and tram
Org2R_Stops_Cycling Cr Org2Stops_Cycling train stations, of any rail operator
Stops2Dest_Walking W Stops2Dest_Walking, or the mirror of Org2Stops_Walking all
Stops2Dest_Cycling C Stops2Dest_Cycling all
R_Stops2Dest_Cycling rC Stops2Dest_Cycling train stations, of any rail operator
RM_Stops2Dest_Cycling rmC Stops2Dest_Cycling rail and metro
RMT_Stops2Dest_Cycling rmtC Stops2Dest_Cycling rail, metro and tram
OVF_Stops2Dest_Cycling ovfC Stops2Dest_Cycling stops near an OV-fiets location

The stop sets follow the GTFS feed. A stop is a rail, metro or tram stop by the route type of most of its stop times (Stops/Mode_rel). The feed has no route type for light rail: RandstadRail E is metro in the feed, and the light rail lines to Zoetermeer, Nieuwegein and IJsselstein are tram, so none of them is a train station. Cr and rC take every train station, also one that only a regional or foreign operator serves, such as Arriva, Blauwnet (Keolis), Breng or DB. The model takes the modes as the feed gives them and has no stop set of its own for light rail (#152). An intercity station, where a cycling pre-transport leg gets an extra parking time (see Extra time below), is every stop within Advanced/DistanceTrainStationsSelection (350 m) of an intercity station of NS, bus stops included.

A selection gives the same legs as a Dijkstra of its own, because the route to a stop does not depend on which other stops are targets. The maximum time of a type may not exceed that of its base set (an integrity check of Legs_T).

Extra time

When the model reads a leg, it adds fixed times to its network time:

  • every cycling leg: Cycling_StartTime (1 min);
  • a cycling pre-transport leg: also Advanced/Cycling_PreTransport_Penalty (0 min), and Cycling_ParkingTime_IC_Station (3 min) when it ends at an intercity station.

The maximum times apply to the network time, as the cut of the Dijkstra does.

Price

  • An OV-fiets leg (ovfC) costs the rental price OVFiets_RentalPrice, the NS tariff of the fare year (4.65 euro in 2025).
  • Any other leg costs its time, including the extra time, times an amount per minute of the mode. For walking that amount is a valuation of the time: PreTransport_Walking_Time_Costs and PostTransport_Walking_Time_Costs (0.19 euro per minute). So the cheapest journey does not simply walk as far as possible. For cycling the amount stands for the costs per kilometre: PreTransport_Cycling_Time_Costs and PostTransport_Cycling_Time_Costs follow from Cycling_CostPerKm (0.09 euro per km) as an amount in euro per minute at CyclingSpeed_kmhr. Because they apply to the extra time as well, they also charge the start and parking time.

The price of a leg always counts in Price_Augm. In Price it counts for cycling with Advanced/IncludeCyclingCostsInPrice (default TRUE) and for walking with Advanced/IncludeWalkingCostsInPrice (default FALSE). The rent of the OV-fiets is paid and always counts in Price, also with IncludeCyclingCostsInPrice off (#108).

Step 5: the direct walking and cycling trips

The components WW and CC are the direct trips (PublicTransport_Prep/Direct/Walking and Direct/Cycling):

  1. The route is a Dijkstra from every origin to every destination over the same network as the legs (NetworkSetup/PrivateTransport/Walking and Cycling), up to MaxWalkingTime_Org2Dest and MaxCyclingTime_Org2Dest (90 min).
  2. The duration is the network time; for cycling plus Cycling_StartTime.
  3. The price is the duration times Direct_Walking_Time_Costs (a valuation of the time) or Direct_Cycling_Time_Costs (the costs per kilometre as an amount per minute) when IncludeWalkingCostsInPrice or IncludeCyclingCostsInPrice is set, else 0.
  4. The direct trips do not depend on the departure moment or on the part of the day (Classifications/Components/TimeInvariant). So WW and CC write one file per origin block, tt_alle_<n>of<N>, which holds for every departure moment of every part of the day. Their output folder carries the origin set instead of OutputLabel (Impl/OrgSet_PathTag, for example 20251014_b2025_WW_<hash>), so the runs of every part of the day and every label share it. The output step of batch\UpdateAll.cmd computes them for the components WW and CC. There is no intermediate cache.

Parameters

Parameter Default Description
WalkingSpeed_kmhr, CyclingSpeed_kmhr 4.5, 14 km/h Speed on every link; in the signature of the pre- and post-transport caches.
MaxWalkingTime_Org2Stops, MaxWalkingTime_Stops2Dest 15 min Maximum walking pre- and post-transport time; in the signature of the pre- and post-transport caches.
MaxCyclingTime_Org2Stops, MaxCyclingTime_Stops2Dest 15 min Maximum cycling pre- and post-transport time of the base sets; in the signature of the pre- and post-transport caches.
MaxCyclingTime_Org2RM_Stops, _Org2RMT_Stops, _Org2R_Stops, _RM_Stops2Dest, _RMT_Stops2Dest, _R_Stops2Dest, _OVF_Stops2Dest 15 min Maximum time of the derived cycling types; at most that of the base set. They apply when the legs are read, so they are in the signature of the output, not in that of the pre- and post-transport caches.
MaxWalkingTime_Org2Dest, MaxCyclingTime_Org2Dest 90 min Maximum duration of a direct walking or cycling trip.
Cycling_StartTime 1 min Added to every cycling leg and direct cycling trip.
Cycling_ParkingTime_IC_Station, Advanced/Cycling_PreTransport_Penalty 3, 0 min Extra time at the end of a cycling pre-transport leg at an intercity station, and at the end of every cycling pre-transport leg.
OVFiets_RentalPrice NS tariff of the fare year (OVFiets_Tariffs), 4.65 euro in 2025 Price of an OV-fiets leg; it always counts in Price.
Cycling_CostPerKm 0.09 euro/km Costs per kilometre of cycling on an own bike, from which the three *_Cycling_Time_Costs follow.
PreTransport_Walking_Time_Costs, PostTransport_Walking_Time_Costs, Direct_Walking_Time_Costs 0.19 euro/min Amount per minute of a walking leg or the direct walking trip: a valuation of the time.
PreTransport_Cycling_Time_Costs, PostTransport_Cycling_Time_Costs, Direct_Cycling_Time_Costs Cycling_CostPerKm at CyclingSpeed_kmhr, in euro/min Amount per minute of a cycling leg on an own bike or the direct cycling trip: the costs per kilometre as an amount per minute.
Advanced/IncludeWalkingCostsInPrice, Advanced/IncludeCyclingCostsInPrice FALSE, TRUE Do the costs of walking (a valuation of the time) or of cycling on an own bike (the costs per kilometre) count in Price? They always count in Price_Augm. The rent of the OV-fiets always counts in Price.
Advanced/CyclingNetwork 'OSM' 'OSM' or 'Fietsersbond'.
Fietsersbond_file_date, Advanced/Fietsersbond_MaxFerryLength, Advanced/Fietsersbond_FerryExtraTime '20250525', 2 km, 5 min The Fietsersbond delivery and its ferries.

Clone this wiki locally