Skip to content

Commit 5810262

Browse files
authored
Merge pull request opentripplanner#6792 from Aubin-MaaS/incremental-transfer-calculation
Make Raptor transfer cost calculation done on demand only when it is accessed for caches generated per request
2 parents 3fe07f9 + a52edcf commit 5810262

11 files changed

Lines changed: 375 additions & 120 deletions

File tree

application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ public enum OTPFeature {
7070
"from OSM data."
7171
),
7272

73+
OnDemandRaptorTransfer(
74+
false,
75+
false,
76+
"""
77+
Calculate transfers only when accessed by Raptor, instead of calculating and caching all transfers for the whole graph,
78+
for runtime requests which are not pre-cached in `transferCacheRequests` in router-config.json.
79+
This may help performance when doing local journey planning in a large graph.
80+
81+
Requests which are specified in `transferCacheRequests` in router-config.json are not affected and are always pre-cached for the whole graph.
82+
"""
83+
),
84+
7385
OptimizeTransfers(
7486
true,
7587
false,

application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultRaptorTransfer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import org.opentripplanner.utils.lang.OtpNumberFormat;
66
import org.opentripplanner.utils.time.DurationUtils;
77

8+
/**
9+
* A Raptor transfer which goes directly to the target stop
10+
*/
811
public final class DefaultRaptorTransfer implements RaptorTransfer {
912

1013
private final int stop;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package org.opentripplanner.routing.algorithm.raptoradapter.transit;
2+
3+
import static java.util.stream.Collectors.toMap;
4+
5+
import java.util.ArrayList;
6+
import java.util.Collection;
7+
import java.util.List;
8+
import java.util.function.Function;
9+
import org.opentripplanner.raptor.api.model.RaptorTransfer;
10+
import org.opentripplanner.street.search.request.StreetSearchRequest;
11+
12+
class OnDemandRaptorTransferIndex implements RaptorTransferIndex {
13+
14+
private final List<List<Transfer>> forwardTransfers;
15+
private List<List<TransferWithSource>> reversedTransfers;
16+
private final Collection<DefaultRaptorTransfer>[] forwardRaptorTransfers;
17+
18+
private final Collection<DefaultRaptorTransfer>[] reversedRaptorTransfers;
19+
20+
private final StreetSearchRequest request;
21+
22+
private record TransferWithSource(Transfer transfer, int fromStopIndex) {}
23+
24+
OnDemandRaptorTransferIndex(
25+
List<List<Transfer>> transfersByStopIndex,
26+
StreetSearchRequest request
27+
) {
28+
this.request = request;
29+
forwardTransfers = transfersByStopIndex;
30+
31+
//noinspection unchecked
32+
forwardRaptorTransfers = new Collection[transfersByStopIndex.size()];
33+
//noinspection unchecked
34+
reversedRaptorTransfers = new Collection[transfersByStopIndex.size()];
35+
}
36+
37+
private synchronized void initializeReversedTransfers() {
38+
if (reversedTransfers == null) {
39+
reversedTransfers = new ArrayList<>(forwardTransfers.size());
40+
for (int i = 0; i < forwardTransfers.size(); i++) {
41+
reversedTransfers.add(new ArrayList<>());
42+
}
43+
44+
for (var i = 0; i < forwardTransfers.size(); ++i) {
45+
var transfers = forwardTransfers.get(i);
46+
for (var transfer : transfers) {
47+
reversedTransfers.get(transfer.getToStop()).add(new TransferWithSource(transfer, i));
48+
}
49+
}
50+
}
51+
}
52+
53+
@Override
54+
public Collection<DefaultRaptorTransfer> getForwardTransfers(int stopIndex) {
55+
// This block is not fully thread safe as there may be a race condition between the check
56+
// and the assignment. However, the assignment is an atomic operation and the assigned value
57+
// should always be the same, so we don't think that it will be a major problem.
58+
// We don't think that the overhead of locking is worthwhile for the occasional chance of two
59+
// threads generating the transfers at the same time.
60+
if (forwardRaptorTransfers[stopIndex] == null) {
61+
forwardRaptorTransfers[stopIndex] = RaptorTransferIndex.getRaptorTransfers(
62+
request,
63+
forwardTransfers.get(stopIndex)
64+
);
65+
}
66+
67+
return forwardRaptorTransfers[stopIndex];
68+
}
69+
70+
@Override
71+
public Collection<DefaultRaptorTransfer> getReversedTransfers(int stopIndex) {
72+
initializeReversedTransfers();
73+
74+
if (reversedRaptorTransfers[stopIndex] == null) {
75+
reversedRaptorTransfers[stopIndex] = getReversedRaptorTransfers(
76+
reversedTransfers.get(stopIndex)
77+
);
78+
}
79+
80+
return reversedRaptorTransfers[stopIndex];
81+
}
82+
83+
private Collection<DefaultRaptorTransfer> getReversedRaptorTransfers(
84+
List<TransferWithSource> transferWithSources
85+
) {
86+
var mode = request.mode();
87+
// The transfers are filtered so that there is only one possible directional transfer
88+
// for a stop pair.
89+
return transferWithSources
90+
.stream()
91+
.filter(s -> s.transfer.allowsMode(mode))
92+
.flatMap(s ->
93+
s.transfer.asRaptorTransfer(request).stream().map(rt -> rt.reverseOf(s.fromStopIndex))
94+
)
95+
.collect(toMap(RaptorTransfer::stop, Function.identity(), (a, b) -> a.c1() < b.c1() ? a : b))
96+
.values();
97+
}
98+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.opentripplanner.routing.algorithm.raptoradapter.transit;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collection;
5+
import java.util.List;
6+
import java.util.stream.IntStream;
7+
import org.opentripplanner.street.search.request.StreetSearchRequest;
8+
9+
class PreCachedRaptorTransferIndex implements RaptorTransferIndex {
10+
11+
private final List<DefaultRaptorTransfer>[] forwardTransfers;
12+
13+
private final List<DefaultRaptorTransfer>[] reversedTransfers;
14+
15+
PreCachedRaptorTransferIndex(
16+
List<List<Transfer>> transfersByStopIndex,
17+
StreetSearchRequest request,
18+
boolean parallel
19+
) {
20+
var forwardTransfers = new ArrayList<List<DefaultRaptorTransfer>>(transfersByStopIndex.size());
21+
var reversedTransfers = new ArrayList<List<DefaultRaptorTransfer>>(transfersByStopIndex.size());
22+
23+
for (int i = 0; i < transfersByStopIndex.size(); i++) {
24+
forwardTransfers.add(new ArrayList<>());
25+
reversedTransfers.add(new ArrayList<>());
26+
}
27+
28+
var stopIndices = IntStream.range(0, transfersByStopIndex.size());
29+
if (parallel) {
30+
stopIndices = stopIndices.parallel();
31+
}
32+
stopIndices.forEach(fromStop -> {
33+
var transfers = transfersByStopIndex.get(fromStop);
34+
var raptorTransfers = RaptorTransferIndex.getRaptorTransfers(request, transfers);
35+
36+
// forwardTransfers is not modified here, and no two threads will access the same element
37+
// in it, so this is still thread safe.
38+
forwardTransfers.get(fromStop).addAll(raptorTransfers);
39+
});
40+
41+
for (int fromStop = 0; fromStop < transfersByStopIndex.size(); fromStop++) {
42+
for (var forwardTransfer : forwardTransfers.get(fromStop)) {
43+
reversedTransfers.get(forwardTransfer.stop()).add(forwardTransfer.reverseOf(fromStop));
44+
}
45+
}
46+
47+
// Create immutable copies of the lists for each stop to make them immutable and faster to iterate
48+
//noinspection unchecked
49+
this.forwardTransfers = forwardTransfers.stream().map(List::copyOf).toArray(List[]::new);
50+
//noinspection unchecked
51+
this.reversedTransfers = reversedTransfers.stream().map(List::copyOf).toArray(List[]::new);
52+
}
53+
54+
public Collection<DefaultRaptorTransfer> getForwardTransfers(int stopIndex) {
55+
return forwardTransfers[stopIndex];
56+
}
57+
58+
public Collection<DefaultRaptorTransfer> getReversedTransfers(int stopIndex) {
59+
return reversedTransfers[stopIndex];
60+
}
61+
}

application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RaptorTransferIndex.java

Lines changed: 26 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2,108 +2,57 @@
22

33
import static java.util.stream.Collectors.toMap;
44

5-
import java.util.ArrayList;
5+
import java.util.Collection;
66
import java.util.List;
77
import java.util.function.Function;
8-
import java.util.stream.IntStream;
98
import org.opentripplanner.framework.application.OTPFeature;
109
import org.opentripplanner.raptor.api.model.RaptorTransfer;
11-
import org.opentripplanner.routing.api.request.StreetMode;
1210
import org.opentripplanner.street.search.request.StreetSearchRequest;
1311

14-
public class RaptorTransferIndex {
15-
16-
private enum RequestSource {
17-
SETUP,
18-
REQUEST_SCOPE,
19-
}
20-
21-
private final List<RaptorTransfer>[] forwardTransfers;
22-
23-
private final List<RaptorTransfer>[] reversedTransfers;
24-
25-
public RaptorTransferIndex(
26-
List<List<RaptorTransfer>> forwardTransfers,
27-
List<List<RaptorTransfer>> reversedTransfers
28-
) {
29-
// Create immutable copies of the lists for each stop to make them immutable and faster to iterate
30-
this.forwardTransfers = forwardTransfers.stream().map(List::copyOf).toArray(List[]::new);
31-
this.reversedTransfers = reversedTransfers.stream().map(List::copyOf).toArray(List[]::new);
32-
}
33-
12+
public interface RaptorTransferIndex {
3413
/**
3514
* Create an index for a route request configured in router-config.json
3615
*/
37-
public static RaptorTransferIndex createInitialSetup(
16+
static RaptorTransferIndex createInitialSetup(
3817
List<List<Transfer>> transfersByStopIndex,
3918
StreetSearchRequest request
4019
) {
41-
return create(transfersByStopIndex, request, RequestSource.SETUP);
20+
// We always want to parallelize the generation when OTP is starting up.
21+
return new PreCachedRaptorTransferIndex(transfersByStopIndex, request, true);
4222
}
4323

4424
/**
4525
* Create an index for a route request originated from the client
4626
*/
47-
public static RaptorTransferIndex createRequestScope(
27+
static RaptorTransferIndex createRequestScope(
4828
List<List<Transfer>> transfersByStopIndex,
4929
StreetSearchRequest request
5030
) {
51-
return create(transfersByStopIndex, request, RequestSource.REQUEST_SCOPE);
31+
return OTPFeature.OnDemandRaptorTransfer.isOn()
32+
? new OnDemandRaptorTransferIndex(transfersByStopIndex, request)
33+
: new PreCachedRaptorTransferIndex(
34+
transfersByStopIndex,
35+
request,
36+
OTPFeature.ParallelRouting.isOn()
37+
);
5238
}
5339

54-
private static RaptorTransferIndex create(
55-
List<List<Transfer>> transfersByStopIndex,
40+
static Collection<DefaultRaptorTransfer> getRaptorTransfers(
5641
StreetSearchRequest request,
57-
RequestSource requestSource
42+
List<Transfer> transfers
5843
) {
59-
var forwardTransfers = new ArrayList<List<RaptorTransfer>>(transfersByStopIndex.size());
60-
var reversedTransfers = new ArrayList<List<RaptorTransfer>>(transfersByStopIndex.size());
61-
StreetMode mode = request.mode();
62-
63-
for (int i = 0; i < transfersByStopIndex.size(); i++) {
64-
forwardTransfers.add(new ArrayList<>());
65-
reversedTransfers.add(new ArrayList<>());
66-
}
67-
68-
var stopIndices = IntStream.range(0, transfersByStopIndex.size());
69-
// we want to always parallelize the cache building during the startup
70-
// and only parallelize during runtime requests if the feature flag is on
71-
if (requestSource == RequestSource.SETUP || OTPFeature.ParallelRouting.isOn()) {
72-
stopIndices = stopIndices.parallel();
73-
}
74-
stopIndices.forEach(fromStop -> {
75-
// The transfers are filtered so that there is only one possible directional transfer
76-
// for a stop pair.
77-
var transfers = transfersByStopIndex
78-
.get(fromStop)
79-
.stream()
80-
.filter(transfer -> transfer.allowsMode(mode))
81-
.flatMap(s -> s.asRaptorTransfer(request).stream())
82-
.collect(
83-
toMap(RaptorTransfer::stop, Function.identity(), (a, b) -> a.c1() < b.c1() ? a : b)
84-
)
85-
.values();
86-
87-
// forwardTransfers is not modified here, and no two threads will access the same element
88-
// in it, so this is still thread safe.
89-
forwardTransfers.get(fromStop).addAll(transfers);
90-
});
91-
92-
for (int fromStop = 0; fromStop < transfersByStopIndex.size(); fromStop++) {
93-
for (var forwardTransfer : forwardTransfers.get(fromStop)) {
94-
reversedTransfers
95-
.get(forwardTransfer.stop())
96-
.add(((DefaultRaptorTransfer) forwardTransfer).reverseOf(fromStop));
97-
}
98-
}
99-
return new RaptorTransferIndex(forwardTransfers, reversedTransfers);
44+
var mode = request.mode();
45+
// The transfers are filtered so that there is only one possible directional transfer
46+
// for a stop pair.
47+
return transfers
48+
.stream()
49+
.filter(transfer -> transfer.allowsMode(mode))
50+
.flatMap(s -> s.asRaptorTransfer(request).stream())
51+
.collect(toMap(RaptorTransfer::stop, Function.identity(), (a, b) -> a.c1() < b.c1() ? a : b))
52+
.values();
10053
}
10154

102-
public List<RaptorTransfer> getForwardTransfers(int stopIndex) {
103-
return forwardTransfers[stopIndex];
104-
}
55+
Collection<DefaultRaptorTransfer> getForwardTransfers(int stopIndex);
10556

106-
public List<RaptorTransfer> getReversedTransfers(int stopIndex) {
107-
return reversedTransfers[stopIndex];
108-
}
57+
Collection<DefaultRaptorTransfer> getReversedTransfers(int stopIndex);
10958
}

application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/Transfer.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import java.util.Optional;
88
import org.locationtech.jts.geom.Coordinate;
99
import org.opentripplanner.raptor.api.model.RaptorCostConverter;
10-
import org.opentripplanner.raptor.api.model.RaptorTransfer;
1110
import org.opentripplanner.routing.api.request.StreetMode;
1211
import org.opentripplanner.routing.api.request.preference.WalkPreferences;
1312
import org.opentripplanner.street.model.edge.Edge;
@@ -80,7 +79,7 @@ public boolean allowsMode(StreetMode mode) {
8079
return modes.contains(mode);
8180
}
8281

83-
public Optional<RaptorTransfer> asRaptorTransfer(StreetSearchRequest request) {
82+
public Optional<DefaultRaptorTransfer> asRaptorTransfer(StreetSearchRequest request) {
8483
WalkPreferences walkPreferences = request.preferences().walk();
8584
if (edges == null || edges.isEmpty()) {
8685
double durationSeconds = distanceMeters / walkPreferences.speed();

application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/request/RaptorRoutingRequestTransitData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public RaptorRoutingRequestTransitData(
142142
}
143143

144144
@Override
145-
public Iterator<RaptorTransfer> getTransfersFromStop(int stopIndex) {
145+
public Iterator<? extends RaptorTransfer> getTransfersFromStop(int stopIndex) {
146146
return transferIndex.getForwardTransfers(stopIndex).iterator();
147147
}
148148

application/src/test/java/org/opentripplanner/raptorlegacy/_data/transit/TestTransitData.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,7 @@ public TestTransitData withRoutes(TestRoute... routes) {
215215
public TestTransitData withTransfer(int fromStop, DefaultRaptorTransfer transfer) {
216216
expandNumOfStops(Math.max(fromStop, transfer.stop()));
217217
transfersFromStop.get(fromStop).add(transfer);
218-
transfersToStop
219-
.get(transfer.stop())
220-
.add(((DefaultRaptorTransfer) transfer).reverseOf(fromStop));
218+
transfersToStop.get(transfer.stop()).add(transfer.reverseOf(fromStop));
221219
return this;
222220
}
223221

0 commit comments

Comments
 (0)