2
2
3
3
import com .conveyal .r5 .analyst .StreetTimesAndModes ;
4
4
import com .conveyal .r5 .transit .TransitLayer ;
5
- import com .conveyal .r5 .transit .TripPattern ;
6
5
import com .conveyal .r5 .transit .path .Path ;
7
6
import com .conveyal .r5 .transit .path .PatternSequence ;
8
7
import com .conveyal .r5 .transit .path .RouteSequence ;
16
15
import org .slf4j .Logger ;
17
16
import org .slf4j .LoggerFactory ;
18
17
19
- import java .awt .*;
20
18
import java .lang .invoke .MethodHandles ;
21
19
import java .util .ArrayList ;
22
20
import java .util .Arrays ;
@@ -59,6 +57,13 @@ public class PathResult {
59
57
*/
60
58
public final Multimap <RouteSequence , Iteration >[] iterationsForPathTemplates ;
61
59
60
+ /**
61
+ * The total number of iterations that reached each destination can be derived from iterationsForPathTemplates
62
+ * as long as every path is being retained. When filtering down to a subset of paths, such as only those passing
63
+ * through a selected link, we need this additional array to retain the information.
64
+ */
65
+ private final int [] nUnfilteredIterationsReachingDestination ;
66
+
62
67
private final TransitLayer transitLayer ;
63
68
64
69
public static final String [] DATA_COLUMNS = new String []{
@@ -86,7 +91,9 @@ public PathResult(AnalysisWorkerTask task, TransitLayer transitLayer) {
86
91
throw new UnsupportedOperationException ("Number of detailed path destinations exceeds limit of " + MAX_PATH_DESTINATIONS );
87
92
}
88
93
}
94
+ // FIXME should we be allocating these large arrays when not recording paths?
89
95
iterationsForPathTemplates = new Multimap [nDestinations ];
96
+ nUnfilteredIterationsReachingDestination = new int [nDestinations ];
90
97
this .transitLayer = transitLayer ;
91
98
}
92
99
@@ -95,10 +102,13 @@ public PathResult(AnalysisWorkerTask task, TransitLayer transitLayer) {
95
102
* pattern-based keys
96
103
*/
97
104
public void setTarget (int targetIndex , Multimap <PatternSequence , Iteration > patterns ) {
105
+ // The size of a multimap is the number of mappings (number of values), not number of unique keys.
106
+ // This size method appears to be O(1), see: com.google.common.collect.AbstractMapBasedMultimap.size
107
+ nUnfilteredIterationsReachingDestination [targetIndex ] = patterns .size ();
98
108
99
109
// When selected link analysis is enabled, filter down the PatternSequence-Iteration Multimap to retain only
100
110
// those keys passing through the selected links.
101
- // TODO Maybe selectedLink should be on TransitLayer, and somehow indicate the number of removed iterations .
111
+ // Maybe selectedLink instance should be on TransitLayer not TransportNetwork .
102
112
if (transitLayer .parentNetwork .selectedLink != null ) {
103
113
patterns = transitLayer .parentNetwork .selectedLink .filterPatterns (patterns );
104
114
}
@@ -125,6 +135,7 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
125
135
Multimap <RouteSequence , Iteration > iterationMap = iterationsForPathTemplates [d ];
126
136
if (iterationMap != null ) {
127
137
// SelectedLink case: collapse all RouteSequences and Iterations for this OD pair into one to simplify.
138
+ // iterationMap is empty (not null) for destinations that were reached without using the selected link.
128
139
// This could also be done by merging all Iterations under a single RouteSequence with all route IDs.
129
140
if (transitLayer .parentNetwork .selectedLink != null ) {
130
141
int nIterations = 0 ;
@@ -137,20 +148,27 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
137
148
summedTotalTime += iterations .stream ().mapToInt (i -> i .totalTime ).sum ();
138
149
}
139
150
// Many destinations will have no iterations at all passing through the SelectedLink area.
140
- // Skip those to keep the CSV output short.
141
- if (nIterations > 0 ) {
142
- String [] row = new String [DATA_COLUMNS .length ];
143
- Arrays .fill (row , "ALL" );
144
- String allRouteIdsPipeSeparated = Arrays .stream (allRouteIds .toArray ())
145
- .mapToObj (transitLayer .routes ::get )
146
- .map (routeInfo -> routeInfo .route_id )
147
- .collect (Collectors .joining ("|" ));
148
- row [0 ] = allRouteIdsPipeSeparated ;
149
- row [row .length - 1 ] = Integer .toString (nIterations );
150
- row [row .length - 2 ] = String .format ("%.1f" , summedTotalTime / nIterations / 60d ); // Average total time
151
- summary [d ].add (row );
151
+ // Skip those to keep the CSV output short (and to avoid division by zero below).
152
+ if (nIterations == 0 ) {
153
+ continue ;
152
154
}
153
- continue ;
155
+ String [] row = new String [DATA_COLUMNS .length ];
156
+ Arrays .fill (row , "ALL" );
157
+ transitLayer .routeString (1 , true );
158
+ String allRouteIdsPipeSeparated = Arrays .stream (allRouteIds .toArray ())
159
+ // If includeName is set to false we record only the ID without the name.
160
+ // Name works better than ID for routes added by modifications, which have random IDs.
161
+ .mapToObj (ri -> transitLayer .routeString (ri , true ))
162
+ .collect (Collectors .joining ("|" ));
163
+ String iterationProportion = "%.3f" .formatted (
164
+ nIterations / (double )(nUnfilteredIterationsReachingDestination [d ]));
165
+ row [0 ] = allRouteIdsPipeSeparated ;
166
+ row [row .length - 1 ] = iterationProportion ;
167
+ // Report average of total time over all retained iterations, different than mean/min approach below.
168
+ row [row .length - 2 ] = String .format ("%.1f" , summedTotalTime / nIterations / 60d );
169
+ summary [d ].add (row );
170
+ // Fall through to the standard case below, so the summary row is followed by its component parts.
171
+ // We could optionally continue to the next loop iteration here, to return only the summary row.
154
172
}
155
173
// Standard (non SelectedLink) case.
156
174
for (RouteSequence routeSequence : iterationMap .keySet ()) {
@@ -185,7 +203,11 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
185
203
score = thisScore ;
186
204
}
187
205
}
188
- String [] row = ArrayUtils .addAll (path , transfer , waits , totalTime , String .valueOf (nIterations ));
206
+ // Check above guarantees that nIterations is nonzero, so total iterations must be nonzero,
207
+ // avoiding divide by zero.
208
+ String iterationProportion = "%.3f" .formatted (
209
+ nIterations / (double )(nUnfilteredIterationsReachingDestination [d ]));
210
+ String [] row = ArrayUtils .addAll (path , transfer , waits , totalTime , iterationProportion );
189
211
checkState (row .length == DATA_COLUMNS .length );
190
212
summary [d ].add (row );
191
213
}
0 commit comments