6
6
import com .conveyal .r5 .analyst .cluster .PathResult ;
7
7
import com .conveyal .r5 .analyst .cluster .RegionalTask ;
8
8
import com .conveyal .r5 .analyst .cluster .TravelTimeResult ;
9
+ import com .conveyal .r5 .api .util .SearchType ;
9
10
import com .conveyal .r5 .transit .TransportNetwork ;
10
11
import com .conveyal .r5 .transit .path .RouteSequence ;
11
12
import com .google .common .collect .ArrayListMultimap ;
17
18
import org .slf4j .Logger ;
18
19
import org .slf4j .LoggerFactory ;
19
20
20
- import java .lang .reflect .Field ;
21
21
import java .text .ParseException ;
22
22
import java .util .*;
23
23
import java .util .concurrent .ForkJoinPool ;
@@ -90,6 +90,8 @@ public void unreachable() {
90
90
91
91
private final CsvResultOptions csvOptions ;
92
92
93
+ private int monteCarloDrawsPerMinute ;
94
+
93
95
@ Override
94
96
protected boolean isOneToOne () {
95
97
return false ;
@@ -150,76 +152,127 @@ private void populateExpandedResults(OneOriginResult travelTimeResults, RDataFra
150
152
// extract travel paths, if required
151
153
Multimap <Integer , PathBreakdown >[] pathBreakdown = extractPathResults (travelTimeResults .paths , travelTimeResults .travelTimes );
152
154
153
- for (int destination = 0 ; destination < travelTimeResults .travelTimes .nPoints ; destination ++) {
154
- // fill travel details for destination
155
- populateTravelTimesBreakdown (travelTimesTable , pathBreakdown , destination );
155
+ // set expected monteCarloDrawsPerMinute to later use to count if every path was accounted for in routing
156
+ monteCarloDrawsPerMinute = transportNetwork .transitLayer .hasFrequencies
157
+ ? routingProperties .numberOfMonteCarloDraws / routingProperties .timeWindowSize
158
+ : 1 ;
159
+
160
+
161
+ if (routingProperties .searchType == SearchType .ARRIVE_BY ) {
162
+ for (int destination = 0 ; destination < travelTimeResults .travelTimes .nPoints ; destination ++) {
163
+ // fill travel details for destination
164
+ filterLatestBeforeArrivalTime (travelTimesTable , pathBreakdown , destination );
165
+ }
166
+ } else {
167
+ for (int destination = 0 ; destination < travelTimeResults .travelTimes .nPoints ; destination ++) {
168
+ // fill travel details for destination
169
+ populateTravelTimesBreakdown (travelTimesTable , pathBreakdown , destination );
170
+ }
156
171
}
157
172
}
158
173
159
- private Multimap <Integer , PathBreakdown >[] extractPathResults (PathResult paths , TravelTimeResult travelTimes ) {
160
- try {
161
- // Create Field object
162
- Field privateField = PathResult .class .getDeclaredField ("iterationsForPathTemplates" );
163
- // Set the accessibility as true
164
- privateField .setAccessible (true );
165
- // Store the value of private field in variable
166
- Multimap <RouteSequence , PathResult .Iteration >[] iterationMaps = (Multimap <RouteSequence , PathResult .Iteration >[])privateField .get (paths );
167
-
168
- Multimap <Integer , PathBreakdown >[] pathResults = new Multimap [nDestinations ];
169
-
170
- for (int d = 0 ; d < nDestinations ; d ++) {
171
- pathResults [d ] = ArrayListMultimap .create ();
172
-
173
- Multimap <RouteSequence , PathResult .Iteration > iterationMap = iterationMaps [d ];
174
- if (iterationMap != null ) {
175
-
176
- for (RouteSequence routeSequence : iterationMap .keySet ()) {
177
- Collection <PathResult .Iteration > iterations = iterationMap .get (routeSequence );
178
- int nIterations = iterations .size ();
179
- checkState (nIterations > 0 , "A path was stored without any iterations" );
180
-
181
- // extract the route id's
182
- String [] path = routeSequence .detailsWithGtfsIds (this .transportNetwork .transitLayer , csvOptions );
183
-
184
- for (PathResult .Iteration iteration : iterations ) {
185
- PathBreakdown breakdown = new PathBreakdown ();
186
- breakdown .departureTime = Utils .getTimeFromSeconds (iteration .departureTime );
187
- breakdown .accessTime = routeSequence .stopSequence .access == null ? 0 : routeSequence .stopSequence .access .time / 60.0f ;
188
- breakdown .waitTime = iteration .waitTimes .sum () / 60.0f ;
189
- breakdown .rideTime = routeSequence .stopSequence .rideTimesSeconds == null ? 0 : routeSequence .stopSequence .rideTimesSeconds .sum () / 60.0f ;
190
- breakdown .transferTime = routeSequence .stopSequence .transferTime (iteration ) / 60f ;
191
- breakdown .egressTime = routeSequence .stopSequence .egress == null ? 0 : routeSequence .stopSequence .egress .time / 60.0f ;
192
- breakdown .totalTime = iteration .totalTime / 60.0f ;
193
-
194
- breakdown .routes = path [0 ];
195
- breakdown .nRides = routeSequence .stopSequence .rideTimesSeconds == null ? 0 : routeSequence .stopSequence .rideTimesSeconds .size ();
196
-
197
- if (iteration .departureTime == 0 ) {
198
- breakdown .departureTime = "" ;
199
- breakdown .routes = this .directModes .toString ();
200
- }
201
-
202
- pathResults [d ].put (iteration .departureTime , breakdown );
174
+ private void filterLatestBeforeArrivalTime (RDataFrame travelTimesTable , Multimap <Integer , PathBreakdown >[] pathBreakdown , int destination ) {
175
+ if (this .routingProperties .expandedTravelTimes & pathBreakdown != null ) {
176
+ if (!pathBreakdown [destination ].isEmpty ()) {
177
+ // for this destination return the latest departing trip that still arrives before the arrival time
178
+ int desiredArrivalTime = secondsFromMidnight + maxTripDuration * 60 ; // convert trip duration to seconds
179
+
180
+ for (int departureTime = secondsFromMidnight + (routingProperties .timeWindowSize - 1 ) * 60 ;
181
+ departureTime >= secondsFromMidnight ;
182
+ departureTime -= 60 ) {
183
+ Collection <PathBreakdown > pbs = pathBreakdown [destination ].get (departureTime );
184
+
185
+ int monteCarloDrawsForPath = 0 ;
186
+ for (PathBreakdown path : pbs ) {
187
+ int arrivalTime = departureTime + (int ) (path .getTotalTime () * 60 );
188
+ monteCarloDrawsForPath ++;
189
+ if (arrivalTime <= desiredArrivalTime ) {
190
+ addPathToDataframe (travelTimesTable , destination , monteCarloDrawsForPath , path );
191
+ return ; // only return the first trip per destination that arrives before the desiredArrivalTime cutoff
192
+ // since we are searching in descending order of departure times it will be the lastest
193
+ // departure arriving before our desired time
203
194
}
204
195
}
205
- } else {
206
- // if iteration map for this destination is null, add possible direct route if shorter than max trip duration
207
- if (travelTimes .getValues ()[0 ][d ] <= this .maxTripDuration ) {
208
- PathBreakdown breakdown = new PathBreakdown ();
209
- breakdown .departureTime = "" ;
210
- breakdown .routes = this .directModes .toString ();
211
- breakdown .totalTime = travelTimes .getValues ()[0 ][d ];
212
- pathResults [d ].put (0 , breakdown );
196
+
197
+ // if there are less routes than expected check direct paths
198
+ if (monteCarloDrawsForPath < monteCarloDrawsPerMinute ) {
199
+ Collection <PathBreakdown > directPaths = pathBreakdown [destination ].get (0 );
200
+
201
+ PathBreakdown directPath ;
202
+ if (!directPaths .isEmpty ()) {
203
+ directPath = directPaths .iterator ().next ();
204
+ } else {
205
+ return ; // if path is unreachable there is no point seeing if it arrives in time
206
+ }
207
+
208
+ monteCarloDrawsForPath = 1 ; // artificial "first draw" for direct path
209
+ int arrivalTime = departureTime + (int ) (directPath .getTotalTime () * 60 );
210
+
211
+ if (arrivalTime <= desiredArrivalTime ) {
212
+ directPath .departureTime = Utils .getTimeFromSeconds (departureTime );
213
+ addPathToDataframe (travelTimesTable , destination , monteCarloDrawsForPath , directPath );
214
+ return ; // only return the first trip per destination that arrives before the desiredArrivalTime cutoff
215
+ // since we are searching in descending order of departure times it will be the lastest
216
+ // departure arriving before our desired time
217
+ }
213
218
}
214
219
}
215
220
}
221
+ }
222
+ }
216
223
217
- return pathResults ;
224
+ private Multimap <Integer , PathBreakdown >[] extractPathResults (PathResult paths , TravelTimeResult travelTimes ) {
225
+ Multimap <Integer , PathBreakdown >[] pathResults = new Multimap [nDestinations ];
218
226
219
- } catch (NoSuchFieldException | IllegalAccessException e ) {
220
- e .printStackTrace ();
221
- return null ;
227
+ for (int d = 0 ; d < nDestinations ; d ++) {
228
+ pathResults [d ] = ArrayListMultimap .create ();
229
+
230
+ Multimap <RouteSequence , PathResult .Iteration > iterationMap = paths .iterationsForPathTemplates [d ];
231
+ if (iterationMap != null ) {
232
+
233
+ for (RouteSequence routeSequence : iterationMap .keySet ()) {
234
+ Collection <PathResult .Iteration > iterations = iterationMap .get (routeSequence );
235
+ int nIterations = iterations .size ();
236
+ checkState (nIterations > 0 , "A path was stored without any iterations" );
237
+
238
+ // extract the route id's
239
+ String [] path = routeSequence .detailsWithGtfsIds (this .transportNetwork .transitLayer , csvOptions );
240
+
241
+ for (PathResult .Iteration iteration : iterations ) {
242
+ PathBreakdown breakdown = new PathBreakdown ();
243
+ breakdown .departureTime = Utils .getTimeFromSeconds (iteration .departureTime );
244
+ breakdown .accessTime = routeSequence .stopSequence .access == null ? 0 : routeSequence .stopSequence .access .time / 60.0f ;
245
+ breakdown .waitTime = iteration .waitTimes .sum () / 60.0f ;
246
+ breakdown .rideTime = routeSequence .stopSequence .rideTimesSeconds == null ? 0 : routeSequence .stopSequence .rideTimesSeconds .sum () / 60.0f ;
247
+ breakdown .transferTime = routeSequence .stopSequence .transferTime (iteration ) / 60f ;
248
+ breakdown .egressTime = routeSequence .stopSequence .egress == null ? 0 : routeSequence .stopSequence .egress .time / 60.0f ;
249
+ breakdown .totalTime = iteration .totalTime / 60.0f ;
250
+
251
+ breakdown .routes = path [0 ];
252
+ breakdown .nRides = routeSequence .stopSequence .rideTimesSeconds == null ? 0 : routeSequence .stopSequence .rideTimesSeconds .size ();
253
+
254
+ if (iteration .departureTime == 0 ) {
255
+ breakdown .departureTime = "" ;
256
+ breakdown .routes = this .directModes .toString ();
257
+ }
258
+
259
+ pathResults [d ].put (iteration .departureTime , breakdown );
260
+ }
261
+ }
262
+ } else {
263
+ // if iteration map for this destination is null, add possible direct route if shorter than max trip duration
264
+ if (travelTimes .getValues ()[0 ][d ] <= this .maxTripDuration ) {
265
+ PathBreakdown breakdown = new PathBreakdown ();
266
+ breakdown .departureTime = "" ;
267
+ breakdown .routes = this .directModes .toString ();
268
+ breakdown .totalTime = travelTimes .getValues ()[0 ][d ];
269
+ pathResults [d ].put (0 , breakdown );
270
+ }
271
+ }
222
272
}
273
+
274
+ return pathResults ;
275
+
223
276
}
224
277
225
278
private void populateTravelTimesBreakdown (RDataFrame travelTimesTable , Multimap <Integer , PathBreakdown >[] pathBreakdown , int destination ) {
@@ -238,13 +291,7 @@ private void populateTravelTimesBreakdown(RDataFrame travelTimesTable, Multimap<
238
291
addPathToDataframe (travelTimesTable , destination , monteCarloDrawsForPath , path );
239
292
}
240
293
241
- int monteCarloDrawsPerMinute ;
242
- if (this .transportNetwork .transitLayer .hasFrequencies ) {
243
- monteCarloDrawsPerMinute = routingProperties .numberOfMonteCarloDraws / routingProperties .timeWindowSize ;
244
- } else {
245
- monteCarloDrawsPerMinute = 1 ;
246
- }
247
-
294
+ // if there are less routes than expected check direct paths
248
295
if (monteCarloDrawsForPath < monteCarloDrawsPerMinute ) {
249
296
Collection <PathBreakdown > directPaths = pathBreakdown [destination ].get (0 );
250
297
0 commit comments