Skip to content

Commit 2204b78

Browse files
Merge pull request #446 from ipeaGIT/arrive-by-search-java
added arrival_travel_time_matrix.R
2 parents 35d41d4 + bdeabac commit 2204b78

24 files changed

+978
-126
lines changed

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,24 +71,30 @@ The package has seven **fundamental functions**:
7171

7272
3. `travel_time_matrix()`
7373
* Fast function that returns a simple `data.table` with travel time estimates
74-
between one or multiple origin destination pairs.
74+
between one or multiple origin destination pairs considering departure time.
7575

76-
4. `expanded_travel_time_matrix()`
76+
4. `arrival_travel_time_matrix()`
77+
* Calculates travel time matrices between origin destination pairs considering
78+
a time of arrival. The output includes additional information such as the
79+
routes used and total time disaggregated by access, waiting, in-vehicle and
80+
transfer times.
81+
82+
5. `expanded_travel_time_matrix()`
7783
* Calculates travel time matrices between origin destination pairs with
7884
additional information such as routes used and total time disaggregated by access,
7985
waiting, in-vehicle and transfer times.
8086

81-
5. `detailed_itineraries()`
87+
6. `detailed_itineraries()`
8288
* Returns a `data.frame sf LINESTRINGs` with one or multiple alternative routes
8389
between one or multiple origin destination pairs. The data output brings
8490
detailed information on transport mode, travel time, walk distance etc for
8591
each trip segment.
8692

87-
6. `pareto_frontier()`
93+
7. `pareto_frontier()`
8894
* Returns a `data.table` with the travel time and monetary cost of multiple
8995
route alternatives for specified origin-destination pairs.
9096

91-
7. `isochrone()`
97+
8. `isochrone()`
9298
* Returns a `A ⁠POLYGON "sf" "data.frame"` showing the area that can be reached from an origin point at a given travel time limit.
9399

94100
obs. Most of these functions also allow users to account for monetary travel costs

java-r5rcore/src/org/ipea/r5r/Process/TravelTimeMatrixComputer.java

Lines changed: 114 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.conveyal.r5.analyst.cluster.PathResult;
77
import com.conveyal.r5.analyst.cluster.RegionalTask;
88
import com.conveyal.r5.analyst.cluster.TravelTimeResult;
9+
import com.conveyal.r5.api.util.SearchType;
910
import com.conveyal.r5.transit.TransportNetwork;
1011
import com.conveyal.r5.transit.path.RouteSequence;
1112
import com.google.common.collect.ArrayListMultimap;
@@ -17,7 +18,6 @@
1718
import org.slf4j.Logger;
1819
import org.slf4j.LoggerFactory;
1920

20-
import java.lang.reflect.Field;
2121
import java.text.ParseException;
2222
import java.util.*;
2323
import java.util.concurrent.ForkJoinPool;
@@ -90,6 +90,8 @@ public void unreachable() {
9090

9191
private final CsvResultOptions csvOptions;
9292

93+
private int monteCarloDrawsPerMinute;
94+
9395
@Override
9496
protected boolean isOneToOne() {
9597
return false;
@@ -150,76 +152,127 @@ private void populateExpandedResults(OneOriginResult travelTimeResults, RDataFra
150152
// extract travel paths, if required
151153
Multimap<Integer, PathBreakdown>[] pathBreakdown = extractPathResults(travelTimeResults.paths, travelTimeResults.travelTimes);
152154

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+
}
156171
}
157172
}
158173

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
203194
}
204195
}
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+
}
213218
}
214219
}
215220
}
221+
}
222+
}
216223

217-
return pathResults;
224+
private Multimap<Integer, PathBreakdown>[] extractPathResults(PathResult paths, TravelTimeResult travelTimes) {
225+
Multimap<Integer, PathBreakdown>[] pathResults = new Multimap[nDestinations];
218226

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+
}
222272
}
273+
274+
return pathResults;
275+
223276
}
224277

225278
private void populateTravelTimesBreakdown(RDataFrame travelTimesTable, Multimap<Integer, PathBreakdown>[] pathBreakdown, int destination) {
@@ -238,13 +291,7 @@ private void populateTravelTimesBreakdown(RDataFrame travelTimesTable, Multimap<
238291
addPathToDataframe(travelTimesTable, destination, monteCarloDrawsForPath, path);
239292
}
240293

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
248295
if (monteCarloDrawsForPath < monteCarloDrawsPerMinute) {
249296
Collection<PathBreakdown> directPaths = pathBreakdown[destination].get(0);
250297

java-r5rcore/src/org/ipea/r5r/R5RCore.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.conveyal.r5.analyst.Grid;
66
import com.conveyal.r5.analyst.cluster.PathResult;
77
import com.conveyal.r5.analyst.decay.*;
8+
import com.conveyal.r5.api.util.SearchType;
89
import com.conveyal.r5.transit.TransportNetwork;
910
import com.fasterxml.jackson.core.JsonProcessingException;
1011

@@ -118,6 +119,10 @@ public void setExpandedTravelTimes(boolean expandedTravelTimes) {
118119
this.routingProperties.expandedTravelTimes = expandedTravelTimes;
119120
}
120121

122+
public void setSearchType(String searchType) {
123+
this.routingProperties.searchType = SearchType.valueOf(searchType);
124+
}
125+
121126
public void setTravelTimesBreakdown(boolean detailedTravelTimes) {
122127
this.routingProperties.travelTimesBreakdown = detailedTravelTimes;
123128
}

java-r5rcore/src/org/ipea/r5r/RoutingProperties.java

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

33
import com.conveyal.r5.analyst.cluster.PathResult;
44
import com.conveyal.r5.analyst.fare.*;
5+
import com.conveyal.r5.api.util.SearchType;
56
import com.conveyal.r5.transit.TransitLayer;
67
import com.fasterxml.jackson.databind.node.ObjectNode;
78
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -40,6 +41,7 @@ public class RoutingProperties {
4041
public float[] fareCutoffs = DEFAULT_FARE_CUTOFFS;
4142
public InRoutingFareCalculator fareCalculator = null;
4243
public TransitLayer transitLayer = null;
44+
public SearchType searchType = SearchType.DEPART_FROM;
4345

4446
public void setFareCalculatorJson(String fareCalculatorJson) {
4547
// first, check to see if this is a built-in R5 fare calculator JSON representation
@@ -80,6 +82,7 @@ public void reset() {
8082

8183
maxFare = DEFAULT_MAX_FARE;
8284
fareCalculator = null;
85+
searchType = SearchType.DEPART_FROM;
8386
// do not reset transitLayer
8487
}
8588
}

r-package/DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Type: Package
22
Package: r5r
33
Title: Rapid Realistic Routing with 'R5'
4-
Version: 2.2.0
4+
Version: 2.2.099999
55
Authors@R: c(
66
person("Marcus", "Saraiva", , "[email protected]", role = "aut",
77
comment = c(ORCID = "0000-0001-6218-2338")),

r-package/NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Generated by roxygen2: do not edit by hand
22

33
export(accessibility)
4+
export(arrival_travel_time_matrix)
45
export(detailed_itineraries)
56
export(download_r5)
67
export(expanded_travel_time_matrix)

r-package/NEWS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
# r5r 2.3.0
1+
# r5r 2.3.0 dev
22

33
**Major changes**
44

5+
- New function `arrival_travel_time_matrix()` to calculate travel time matrix between origin destination pairs considering the time of arrival, instead of a depature time.
56

67
**Minor changes**
78

0 commit comments

Comments
 (0)