Skip to content

Commit 9ddf243

Browse files
committed
feat: Add stopsWithRegularTransfers build config parameter
Introduces a new build configuration parameter `stopsWithRegularTransfers` that allows operators to explicitly list stop IDs for which regular transfers are always pre-computed during graph build, even if the stop has no scheduled trips in the static data. This is needed for stops that are normally unused in static GTFS/NeTEx data but may be visited at runtime via real-time updates (e.g. a platform a train can be re-routed to). Without this, such stops are excluded from transfer pre-computation. The parameter is marked experimental and is intended as a temporary workaround until automatic transfer updates based on real-time updates are implemented.
1 parent a3567cf commit 9ddf243

6 files changed

Lines changed: 102 additions & 8 deletions

File tree

application/src/main/java/org/opentripplanner/graph_builder/module/transfer/DirectTransferGenerator.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.Objects;
1212
import java.util.Set;
1313
import java.util.concurrent.atomic.AtomicInteger;
14+
import org.opentripplanner.core.model.id.FeedScopedId;
1415
import org.opentripplanner.framework.application.OTPFeature;
1516
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
1617
import org.opentripplanner.graph_builder.issues.StopNotLinkedForTransfers;
@@ -53,6 +54,7 @@ public class DirectTransferGenerator implements GraphBuilderModule {
5354

5455
private final Duration defaultMaxTransferDuration;
5556

57+
private final Set<FeedScopedId> includeStopsWithRegularTransfers;
5658
private final List<RouteRequest> transferRequests;
5759
private final Map<StreetMode, TransferParametersForMode> transferParametersForMode;
5860
private final Graph graph;
@@ -61,7 +63,8 @@ public class DirectTransferGenerator implements GraphBuilderModule {
6163
private final DataImportIssueStore issueStore;
6264

6365
/**
64-
* Constructor used in tests. This initializes transferParametersForMode as an empty map.
66+
* Constructor used in tests. This initializes transferParametersForMode as an empty map and
67+
* stopsWithRegularTransfers as an empty set.
6568
*/
6669
public DirectTransferGenerator(
6770
Graph graph,
@@ -75,6 +78,7 @@ public DirectTransferGenerator(
7578
this.timetableRepository = timetableRepository;
7679
this.issueStore = issueStore;
7780
this.defaultMaxTransferDuration = defaultMaxTransferDuration;
81+
this.includeStopsWithRegularTransfers = Set.of();
7882
this.transferRequests = transferRequests;
7983
this.transferRepository = transferRepository;
8084
this.transferParametersForMode = Map.of();
@@ -91,6 +95,7 @@ public DirectTransferGenerator(
9195
this.timetableRepository = timetableRepository;
9296
this.issueStore = issueStore;
9397
this.defaultMaxTransferDuration = parameters.maxDuration();
98+
this.includeStopsWithRegularTransfers = Set.copyOf(parameters.includeStops());
9499
this.transferRequests = parameters.requests();
95100
this.transferParametersForMode = parameters.parametersForMode();
96101
this.transferRepository = transferRepository;
@@ -252,7 +257,11 @@ private NearbyStopFinder createNearbyStopFinder(Duration radiusAsDuration) {
252257
}
253258

254259
if (OTPFeature.ConsiderPatternsForDirectTransfers.isOn()) {
255-
return new PatternConsideringNearbyStopFinder(transitService, finder);
260+
return new PatternConsideringNearbyStopFinder(
261+
transitService,
262+
finder,
263+
includeStopsWithRegularTransfers
264+
);
256265
} else {
257266
return finder;
258267
}

application/src/main/java/org/opentripplanner/graph_builder/module/transfer/api/RegularTransferParameters.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,34 @@
55
import java.util.List;
66
import java.util.Map;
77
import java.util.Objects;
8+
import org.opentripplanner.core.model.id.FeedScopedId;
89
import org.opentripplanner.routing.api.request.RouteRequest;
910
import org.opentripplanner.street.model.StreetMode;
1011

1112
public final class RegularTransferParameters {
1213

1314
private final Duration maxDuration;
15+
private final List<FeedScopedId> includeStops;
1416
private final Map<StreetMode, TransferParametersForMode> parametersForMode;
1517
private final List<RouteRequest> requests;
1618

1719
public static RegularTransferParameters DEFAULT = new RegularTransferParameters();
1820

1921
private RegularTransferParameters() {
2022
this.maxDuration = Duration.ofMinutes(30);
23+
this.includeStops = List.of();
2124
this.parametersForMode = Map.of();
2225
this.requests = List.of();
2326
}
2427

2528
public RegularTransferParameters(
2629
Duration maxDuration,
30+
List<FeedScopedId> includeStops,
2731
Map<StreetMode, TransferParametersForMode> parametersForMode,
2832
List<RouteRequest> requests
2933
) {
3034
this.maxDuration = Objects.requireNonNull(maxDuration);
35+
this.includeStops = List.copyOf(includeStops);
3136
this.parametersForMode = Map.copyOf(parametersForMode);
3237
this.requests = List.copyOf(requests);
3338
}
@@ -40,6 +45,10 @@ public Duration maxDuration() {
4045
return maxDuration;
4146
}
4247

48+
public List<FeedScopedId> includeStops() {
49+
return includeStops;
50+
}
51+
4352
public Map<StreetMode, TransferParametersForMode> parametersForMode() {
4453
return parametersForMode;
4554
}
@@ -51,11 +60,13 @@ public List<RouteRequest> requests() {
5160
public static class Builder {
5261

5362
private Duration maxDuration;
63+
private List<FeedScopedId> includeStops;
5464
private Map<StreetMode, TransferParametersForMode> parametersForMode = new HashMap<>();
5565
private List<RouteRequest> requests;
5666

5767
private Builder(RegularTransferParameters original) {
5868
this.maxDuration = original.maxDuration;
69+
this.includeStops = original.includeStops;
5970
this.parametersForMode.putAll(original.parametersForMode);
6071
this.requests = original.requests;
6172
}
@@ -65,6 +76,11 @@ public Builder withMaxDuration(Duration maxDuration) {
6576
return this;
6677
}
6778

79+
public Builder withIncludeStops(List<FeedScopedId> includeStops) {
80+
this.includeStops = includeStops;
81+
return this;
82+
}
83+
6884
/// Note! This method replace/overwrite any existing content
6985
public Builder withParametersForMode(Map<StreetMode, TransferParametersForMode> map) {
7086
this.parametersForMode.clear();
@@ -84,7 +100,7 @@ public Builder withRequests(List<RouteRequest> requests) {
84100
}
85101

86102
public RegularTransferParameters build() {
87-
return new RegularTransferParameters(maxDuration, parametersForMode, requests);
103+
return new RegularTransferParameters(maxDuration, includeStops, parametersForMode, requests);
88104
}
89105
}
90106
}

application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternConsideringNearbyStopFinder.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import java.util.Collection;
44
import java.util.List;
5+
import java.util.Set;
6+
import org.opentripplanner.core.model.id.FeedScopedId;
57
import org.opentripplanner.framework.application.OTPFeature;
68
import org.opentripplanner.graph_builder.module.nearbystops.NearbyStopFinder;
79
import org.opentripplanner.routing.api.request.RouteRequest;
@@ -34,9 +36,12 @@ public class PatternConsideringNearbyStopFinder implements NearbyStopFinder {
3436

3537
public PatternConsideringNearbyStopFinder(
3638
TransitService transitService,
37-
NearbyStopFinder delegateNearbyStopFinder
39+
NearbyStopFinder delegateNearbyStopFinder,
40+
Set<FeedScopedId> stopsWithRegularTransfers
3841
) {
39-
var builder = CompositeNearbyStopFilter.of().add(new PatternNearbyStopFilter(transitService));
42+
var builder = CompositeNearbyStopFilter.of().add(
43+
new PatternNearbyStopFilter(transitService, stopsWithRegularTransfers)
44+
);
4045

4146
if (OTPFeature.FlexRouting.isOn()) {
4247
builder.add(new FlexTripNearbyStopFilter(transitService));

application/src/main/java/org/opentripplanner/graph_builder/module/transfer/filter/PatternNearbyStopFilter.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,20 @@
2020
* or alighting is possible (depending on direction).
2121
* <p>
2222
* Stops without patterns may still be included if they are marked as sometimes-used by real-time
23-
* updates (when the IncludeStopsUsedRealTimeInTransfers feature is enabled).
23+
* updates (when the IncludeStopsUsedRealTimeInTransfers feature is enabled), or if they are
24+
* explicitly listed in the {@code stopsWithRegularTransfers} build configuration.
2425
*/
2526
class PatternNearbyStopFilter implements NearbyStopFilter {
2627

2728
private final TransitService transitService;
29+
private final Set<FeedScopedId> stopsWithRegularTransfers;
2830

29-
PatternNearbyStopFilter(TransitService transitService) {
31+
PatternNearbyStopFilter(
32+
TransitService transitService,
33+
Set<FeedScopedId> stopsWithRegularTransfers
34+
) {
3035
this.transitService = transitService;
36+
this.stopsWithRegularTransfers = stopsWithRegularTransfers;
3137
}
3238

3339
@Override
@@ -82,6 +88,15 @@ private List<FeedScopedId> findPatternsForStop(RegularStop stop, boolean reverse
8288
}
8389

8490
private boolean includeStopUsedRealtime(RegularStop stop) {
85-
return OTPFeature.IncludeStopsUsedRealTimeInTransfers.isOn() && stop.isSometimesUsedRealtime();
91+
// Some stops, like railway and bus-replacement stops, are tagged during graph building/data
92+
// import. These should allways be included.
93+
if (OTPFeature.IncludeStopsUsedRealTimeInTransfers.isOn() && stop.isSometimesUsedRealtime()) {
94+
return true;
95+
}
96+
// Some stops is listed in the build-config, these are regular stops to include.
97+
if (stopsWithRegularTransfers.contains(stop.getId())) {
98+
return true;
99+
}
100+
return false;
86101
}
87102
}

application/src/main/java/org/opentripplanner/standalone/config/buildconfig/RegularTransferConfig.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,32 @@ public static RegularTransferParameters map(NodeAdapter root) {
3030
.asDuration(dft.maxDuration())
3131
);
3232

33+
builder.withIncludeStops(
34+
root
35+
.of("stopsWithRegularTransfers")
36+
.since(V2_9)
37+
.summary(
38+
"Stops that should always have regular transfers computed, even without scheduled trips."
39+
)
40+
.description(
41+
"""
42+
List of stop IDs for which regular transfers are always pre-computed during graph build,
43+
even if the stop has no scheduled trips. Remember to include _feedId_ like this
44+
`"RB:NSR:Quay:102541"`.
45+
46+
This is useful for stops that are unused in static transit data, but may be visited by
47+
real-time updates (e.g. a platform that a train can be re-routed to at runtime). Without
48+
this configuration, stops with no scheduled trips are excluded from transfer pre-computation
49+
and become unreachable islands when a real-time update routes a trip to them.
50+
51+
Note! This parameter should be replaced with an automatic update to regular transfers
52+
based on real-time updates.
53+
"""
54+
)
55+
.experimentalFeature()
56+
.asFeedScopedIds(List.of())
57+
);
58+
3359
builder.withParametersForMode(
3460
root
3561
.of("transferParametersForMode")

doc/user/BuildConfiguration.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Sections follow that describe particular settings in more depth.
9393
|    includeOsmSubwayEntrances | `boolean` | Whether to include subway entrances from the OSM data. | *Optional* | `false` | 2.7 |
9494
|    [osmTagMapping](#od_osmTagMapping) | `enum` | The named set of mapping rules applied when parsing OSM tags. | *Optional* | `"default"` | 2.2 |
9595
|    timeZone | `time-zone` | The timezone used to resolve opening hours in OSM data. | *Optional* | | 2.2 |
96+
| [stopsWithRegularTransfers](#stopsWithRegularTransfers) | `feed-scoped-id[]` | Stops that should always have regular transfers computed, even without scheduled trips. | *Optional* | | 2.9 |
9697
| [transferParametersForMode](#transferParametersForMode) | `enum map of object` | Configures mode-specific properties for transfer calculations. | *Optional* | | 2.7 |
9798
|    BIKE | `object` | NA | *Optional* | | 2.7 |
9899
|       [bikesAllowedStopMaxTransferDuration](#tpfm_BIKE_bikesAllowedStopMaxTransferDuration) | `duration` | This is used for specifying a `maxTransferDuration` value to use with transfers between stops which are visited by trips that allow bikes. | *Optional* | | 2.9 |
@@ -950,6 +951,28 @@ The named set of mapping rules applied when parsing OSM tags. Overrides the valu
950951

951952
The named set of mapping rules applied when parsing OSM tags.
952953

954+
<h3 id="stopsWithRegularTransfers">stopsWithRegularTransfers</h3>
955+
956+
**Since version:** `2.9`**Type:** `feed-scoped-id[]`**Cardinality:** `Optional`
957+
**Path:** /
958+
959+
Stops that should always have regular transfers computed, even without scheduled trips.
960+
961+
List of stop IDs for which regular transfers are always pre-computed during graph build,
962+
even if the stop has no scheduled trips. Remember to include _feedId_ like this
963+
`"RB:NSR:Quay:102541"`.
964+
965+
This is useful for stops that are unused in static transit data, but may be visited by
966+
real-time updates (e.g. a platform that a train can be re-routed to at runtime). Without
967+
this configuration, stops with no scheduled trips are excluded from transfer pre-computation
968+
and become unreachable islands when a real-time update routes a trip to them.
969+
970+
Note! This parameter should be replaced with an automatic update to regular transfers
971+
based on real-time updates.
972+
973+
974+
**THIS IS STILL AN EXPERIMENTAL FEATURE - IT MAY CHANGE WITHOUT ANY NOTICE!**
975+
953976
<h3 id="transferParametersForMode">transferParametersForMode</h3>
954977

955978
**Since version:** `2.7`**Type:** `enum map of object`**Cardinality:** `Optional`

0 commit comments

Comments
 (0)