Problem
When building footpaths, connect_components() in build_footpaths.cc clears all preprocessing footpaths (line 209) and recomputes them via Dijkstra. This erases the GTFS transfers.txt min_transfer_time values that were correctly parsed and stored in read_transfers() (stop.cc).
As a result, all inter-stop transfer times are computed solely from walking distance, ignoring operator-provided minimum transfer times. For example, at Paris Gare de Lyon, IDFM specifies 6-8 minutes between metro platforms and mainline platforms in their transfers.txt (206,187 entries), but MOTIS returns 0 minutes.
Root Cause Analysis
In src/loader/gtfs/stop.cc, read_transfers() correctly parses transfer_type=2 entries and stores them as footpaths (lines 192-200):
auto& footpaths = from_stop_it->second->footpaths_;
footpaths.emplace_back(to_stop_it->second.get(), duration_t{*t.min_transfer_time_ / 60});
These are written to preprocessing_footpaths_out_ (lines 347-352).
However, connect_components() in build_footpaths.cc then:
- Clears all preprocessing footpaths (line 209):
tt.locations_.preprocessing_footpaths_out_.clear()
- Recomputes everything via Dijkstra using only walking distances
- The GTFS min_transfer_time values are lost
Proposed Fix
In connect_components(), before clearing the preprocessing footpaths, save the GTFS-provided footpath durations. Then after the Dijkstra computation, ensure the final duration is at least the GTFS minimum:
// Before clear: save GTFS footpath durations
hash_map<std::pair<location_idx_t, location_idx_t>, duration_t> gtfs_min_durations;
for (auto l = location_idx_t{0U}; l != tt.locations_.preprocessing_footpaths_out_.size(); ++l) {
for (auto const& fp : tt.locations_.preprocessing_footpaths_out_[l]) {
auto const key = std::make_pair(l, fp.target());
auto it = gtfs_min_durations.find(key);
if (it == gtfs_min_durations.end() || fp.duration() > it->second) {
gtfs_min_durations[key] = fp.duration();
}
}
}
// ... existing clear + Dijkstra code ...
// When writing footpaths (around line 294), add:
auto const gtfs_it = gtfs_min_durations.find({from_l, to_l});
auto const gtfs_min = (gtfs_it != gtfs_min_durations.end())
? gtfs_it->second : duration_t{0};
auto const duration = std::max({
std::chrono::duration_cast<u8_minutes>(fp.duration()),
tt.locations_.transfer_time_[from_l],
tt.locations_.transfer_time_[to_l],
gtfs_min // NEW: respect GTFS min_transfer_time
});
This ensures the computed footpath is at least as long as the GTFS-specified minimum, while still allowing the Dijkstra-computed time to be longer if the walking distance warrants it.
Additionally: respect transfer_type=3
Currently, transfer_type=3 (not possible) entries are simply skipped (line 150-152). They should also prevent footpath creation between those stop pairs, even if they are geographically close.
Impact
This would fix:
Operators like IDFM (Ile-de-France Mobilites) publish detailed transfer times in their GTFS that account for platform layout, elevators, corridors, and fare gates - information that pure walking distance cannot capture.
Test Case
A minimal GTFS with two stops at 50m distance and a transfers.txt entry with min_transfer_time=600 (10 min) should produce a footpath of 10 min, not the ~1 min computed from walking distance.
Problem
When building footpaths,
connect_components()inbuild_footpaths.ccclears all preprocessing footpaths (line 209) and recomputes them via Dijkstra. This erases the GTFStransfers.txtmin_transfer_timevalues that were correctly parsed and stored inread_transfers()(stop.cc).As a result, all inter-stop transfer times are computed solely from walking distance, ignoring operator-provided minimum transfer times. For example, at Paris Gare de Lyon, IDFM specifies 6-8 minutes between metro platforms and mainline platforms in their transfers.txt (206,187 entries), but MOTIS returns 0 minutes.
Root Cause Analysis
In
src/loader/gtfs/stop.cc,read_transfers()correctly parses transfer_type=2 entries and stores them as footpaths (lines 192-200):These are written to
preprocessing_footpaths_out_(lines 347-352).However,
connect_components()inbuild_footpaths.ccthen:tt.locations_.preprocessing_footpaths_out_.clear()Proposed Fix
In
connect_components(), before clearing the preprocessing footpaths, save the GTFS-provided footpath durations. Then after the Dijkstra computation, ensure the final duration is at least the GTFS minimum:This ensures the computed footpath is at least as long as the GTFS-specified minimum, while still allowing the Dijkstra-computed time to be longer if the walking distance warrants it.
Additionally: respect transfer_type=3
Currently,
transfer_type=3(not possible) entries are simply skipped (line 150-152). They should also prevent footpath creation between those stop pairs, even if they are geographically close.Impact
This would fix:
Operators like IDFM (Ile-de-France Mobilites) publish detailed transfer times in their GTFS that account for platform layout, elevators, corridors, and fare gates - information that pure walking distance cannot capture.
Test Case
A minimal GTFS with two stops at 50m distance and a
transfers.txtentry withmin_transfer_time=600(10 min) should produce a footpath of 10 min, not the ~1 min computed from walking distance.