22
33import com .conveyal .r5 .analyst .StreetTimesAndModes ;
44import com .conveyal .r5 .transit .TransitLayer ;
5- import com .conveyal .r5 .transit .TripPattern ;
65import com .conveyal .r5 .transit .path .Path ;
76import com .conveyal .r5 .transit .path .PatternSequence ;
87import com .conveyal .r5 .transit .path .RouteSequence ;
1615import org .slf4j .Logger ;
1716import org .slf4j .LoggerFactory ;
1817
19- import java .awt .*;
2018import java .lang .invoke .MethodHandles ;
2119import java .util .ArrayList ;
2220import java .util .Arrays ;
@@ -59,6 +57,13 @@ public class PathResult {
5957 */
6058 public final Multimap <RouteSequence , Iteration >[] iterationsForPathTemplates ;
6159
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+
6267 private final TransitLayer transitLayer ;
6368
6469 public static final String [] DATA_COLUMNS = new String []{
@@ -86,7 +91,9 @@ public PathResult(AnalysisWorkerTask task, TransitLayer transitLayer) {
8691 throw new UnsupportedOperationException ("Number of detailed path destinations exceeds limit of " + MAX_PATH_DESTINATIONS );
8792 }
8893 }
94+ // FIXME should we be allocating these large arrays when not recording paths?
8995 iterationsForPathTemplates = new Multimap [nDestinations ];
96+ nUnfilteredIterationsReachingDestination = new int [nDestinations ];
9097 this .transitLayer = transitLayer ;
9198 }
9299
@@ -95,10 +102,13 @@ public PathResult(AnalysisWorkerTask task, TransitLayer transitLayer) {
95102 * pattern-based keys
96103 */
97104 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 ();
98108
99109 // When selected link analysis is enabled, filter down the PatternSequence-Iteration Multimap to retain only
100110 // 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 .
102112 if (transitLayer .parentNetwork .selectedLink != null ) {
103113 patterns = transitLayer .parentNetwork .selectedLink .filterPatterns (patterns );
104114 }
@@ -125,6 +135,7 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
125135 Multimap <RouteSequence , Iteration > iterationMap = iterationsForPathTemplates [d ];
126136 if (iterationMap != null ) {
127137 // 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.
128139 // This could also be done by merging all Iterations under a single RouteSequence with all route IDs.
129140 if (transitLayer .parentNetwork .selectedLink != null ) {
130141 int nIterations = 0 ;
@@ -137,20 +148,27 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
137148 summedTotalTime += iterations .stream ().mapToInt (i -> i .totalTime ).sum ();
138149 }
139150 // 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 ;
152154 }
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.
154172 }
155173 // Standard (non SelectedLink) case.
156174 for (RouteSequence routeSequence : iterationMap .keySet ()) {
@@ -185,7 +203,11 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
185203 score = thisScore ;
186204 }
187205 }
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 );
189211 checkState (row .length == DATA_COLUMNS .length );
190212 summary [d ].add (row );
191213 }
0 commit comments