Skip to content

Commit

Permalink
Merge pull request #930 from conveyal/path-route-names
Browse files Browse the repository at this point in the history
Include names in paths in CSV
  • Loading branch information
abyrd authored Feb 29, 2024
2 parents d12f763 + 4f4985b commit 5aa45f5
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 21 deletions.
11 changes: 11 additions & 0 deletions src/main/java/com/conveyal/analysis/models/AnalysisRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -175,6 +176,14 @@ public class AnalysisRequest {
*/
public int dualAccessibilityThreshold = 0;

/**
* Freeform (untyped) flags for enabling experimental, undocumented, or arcane behavior in backend or workers.
* This should be used to replace all previous special behavior flags that were embedded inside analysis names etc.
*/
public Set<String> flags;

/** Control the details of CSV regional analysis output, including whether to output IDs, names, or both. */
public CsvResultOptions csvResultOptions = new CsvResultOptions();

/**
* Create the R5 `Scenario` from this request.
Expand Down Expand Up @@ -281,6 +290,8 @@ public void populateTask (AnalysisWorkerTask task, UserPermissions userPermissio

task.includeTemporalDensity = includeTemporalDensity;
task.dualAccessibilityThreshold = dualAccessibilityThreshold;
task.flags = flags;
task.csvResultOptions = csvResultOptions;
}

private EnumSet<LegMode> getEnumSetFromString (String s) {
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/conveyal/analysis/models/CsvResultOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.conveyal.analysis.models;

import com.conveyal.r5.transit.TransitLayer.EntityRepresentation;

import static com.conveyal.r5.transit.TransitLayer.EntityRepresentation.ID_ONLY;

/**
* API model type included in analysis requests to control details of CSV regional analysis output.
* This type is shared between AnalysisRequest (Frontend -> Broker) and AnalysisWorkerTask (Broker -> Workers).
* There is precedent for nested compound types shared across those top level request types (see DecayFunction).
*/
public class CsvResultOptions {
public EntityRepresentation routeRepresentation = ID_ONLY;
public EntityRepresentation stopRepresentation = ID_ONLY;
// Only feed ID representation is allowed to be null (no feed IDs at all, the default).
public EntityRepresentation feedRepresentation = null;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.conveyal.r5.analyst.cluster;

import com.conveyal.analysis.models.CsvResultOptions;
import com.conveyal.r5.analyst.FreeFormPointSet;
import com.conveyal.r5.analyst.Grid;
import com.conveyal.r5.analyst.GridTransformWrapper;
Expand All @@ -15,6 +16,7 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo;

import java.util.Arrays;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
Expand Down Expand Up @@ -177,6 +179,15 @@ public abstract class AnalysisWorkerTask extends ProfileRequest {
*/
public ChaosParameters injectFault;

/**
* Freeform (untyped) flags for enabling experimental, undocumented, or arcane worker behavior.
* This should be used to replace all previous special behavior flags that were embedded inside analysis names etc.
*/
public Set<String> flags;

/** Control the details of CSV regional analysis output, including whether to output IDs, names, or both. */
public CsvResultOptions csvResultOptions;

/**
* Is this a single point or regional request? Needed to encode types in JSON serialization. Can that type field be
* added automatically with a serializer annotation instead of by defining a getter method and two dummy methods?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.conveyal.r5.analyst.cluster;

import com.conveyal.analysis.models.CsvResultOptions;
import com.conveyal.r5.analyst.StreetTimesAndModes;
import com.conveyal.r5.transit.TransitLayer;
import com.conveyal.r5.transit.path.Path;
Expand Down Expand Up @@ -47,8 +48,11 @@ public class PathResult {
* With additional changes, patterns could be collapsed further to route combinations or modes.
*/
public final Multimap<RouteSequence, Iteration>[] iterationsForPathTemplates;

private final TransitLayer transitLayer;

private final CsvResultOptions csvOptions;

public static final String[] DATA_COLUMNS = new String[]{
"routes",
"boardStops",
Expand Down Expand Up @@ -76,6 +80,7 @@ public PathResult(AnalysisWorkerTask task, TransitLayer transitLayer) {
}
iterationsForPathTemplates = new Multimap[nDestinations];
this.transitLayer = transitLayer;
this.csvOptions = task.csvResultOptions;
}

/**
Expand Down Expand Up @@ -108,7 +113,7 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
int nIterations = iterations.size();
checkState(nIterations > 0, "A path was stored without any iterations");
String waits = null, transfer = null, totalTime = null;
String[] path = routeSequence.detailsWithGtfsIds(transitLayer);
String[] path = routeSequence.detailsWithGtfsIds(transitLayer, csvOptions);
double targetValue;
IntStream totalWaits = iterations.stream().mapToInt(i -> i.waitTimes.sum());
if (stat == Stat.MINIMUM) {
Expand Down
57 changes: 44 additions & 13 deletions src/main/java/com/conveyal/r5/transit/TransitLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;

import static com.conveyal.r5.transit.TransitLayer.EntityRepresentation.ID_ONLY;
import static com.conveyal.r5.transit.TransitLayer.EntityRepresentation.NAME_ONLY;


/**
* A key simplifying factor is that we don't handle overnight trips. This is fine for analysis at usual times of day.
Expand Down Expand Up @@ -815,31 +818,59 @@ public TIntSet findStopsInGeometry (Geometry geometry) {
return stops;
}

public enum EntityRepresentation {
ID_ONLY, NAME_ONLY, NAME_AND_ID
}

/**
* For the given pattern index, returns the GTFS routeId. If includeName is true, the returned string will
* also include a route_short_name or route_long_name (if they are not null).
*/
public String routeString(int routeIndex, boolean includeName) {
public String routeString (int routeIndex, EntityRepresentation nameOrId) {
RouteInfo routeInfo = routes.get(routeIndex);
String route = routeInfo.route_id;
if (includeName) {
if (routeInfo.route_short_name != null) {
route += " (" + routeInfo.route_short_name + ")";
} else if (routeInfo.route_long_name != null){
route += " (" + routeInfo.route_long_name + ")";
String name = routeInfo.route_short_name;
String id = routeInfo.route_id;
// If we might actually use the name, check some fallbacks.
if (nameOrId != ID_ONLY) {
if (name == null) {
name = routeInfo.route_long_name;
}
if (name == null) {
name = routeInfo.route_id;
}
}
return route;
return switch (nameOrId) {
case NAME_ONLY -> name;
case NAME_AND_ID -> name + " (" + id + ")";
default -> id;
};
}

/**
* For the given stop index, returns the GTFS stopId (stripped of R5's feedId prefix) and, if includeName is true,
* stopName.
*/
public String stopString(int stopIndex, boolean includeName) {
// TODO use a compact feed index, instead of splitting to remove feedIds
String stop = stopIdForIndex.get(stopIndex) == null ? "[new]" : stopIdForIndex.get(stopIndex).split(":")[1];
if (includeName) stop += " (" + stopNames.get(stopIndex) + ")";
return stop;
public String stopString(int stopIndex, EntityRepresentation nameOrId) {
String stopId = stopIdForIndex.get(stopIndex);
String stopName = stopNames.get(stopIndex);
// I'd trust the JVM JIT to optimize out these assignments on different code paths, but not the split call.
if (nameOrId != NAME_ONLY) {
if (stopId == null) {
stopId = "[new]";
} else {
// TODO use a compact feed ID instead of splitting to remove feedIds (or put feedId into another CSV field)
stopId = stopId.split(":")[1];
}
}
if (nameOrId != ID_ONLY) {
if (stopName == null) {
stopName = "[new]";
}
}
return switch (nameOrId) {
case NAME_ONLY -> stopName;
case NAME_AND_ID -> stopName + " (" + stopId + ")";
default -> stopId;
};
}
}
18 changes: 11 additions & 7 deletions src/main/java/com/conveyal/r5/transit/path/RouteSequence.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.conveyal.r5.transit.path;

import com.conveyal.analysis.models.CsvResultOptions;
import com.conveyal.r5.transit.TransitLayer;
import com.conveyal.r5.transit.TransitLayer.EntityRepresentation;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TIntArrayList;

Expand All @@ -9,6 +11,8 @@
import java.util.Objects;
import java.util.StringJoiner;

import static com.conveyal.r5.transit.TransitLayer.EntityRepresentation.NAME_AND_ID;

/** A door-to-door path that includes the routes ridden between stops */
public class RouteSequence {

Expand All @@ -28,15 +32,15 @@ public RouteSequence(PatternSequence patternSequence, TransitLayer transitLayer)
}

/** Returns details summarizing this route sequence, using GTFS ids stored in the supplied transitLayer. */
public String[] detailsWithGtfsIds(TransitLayer transitLayer){
public String[] detailsWithGtfsIds (TransitLayer transitLayer, CsvResultOptions csvOptions){
StringJoiner routeIds = new StringJoiner("|");
StringJoiner boardStopIds = new StringJoiner("|");
StringJoiner alightStopIds = new StringJoiner("|");
StringJoiner rideTimes = new StringJoiner("|");
for (int i = 0; i < routes.size(); i++) {
routeIds.add(transitLayer.routeString(routes.get(i), false));
boardStopIds.add(transitLayer.stopString(stopSequence.boardStops.get(i), false));
alightStopIds.add(transitLayer.stopString(stopSequence.alightStops.get(i), false));
routeIds.add(transitLayer.routeString(routes.get(i), csvOptions.routeRepresentation));
boardStopIds.add(transitLayer.stopString(stopSequence.boardStops.get(i), csvOptions.stopRepresentation));
alightStopIds.add(transitLayer.stopString(stopSequence.alightStops.get(i), csvOptions.stopRepresentation));
rideTimes.add(String.format("%.1f", stopSequence.rideTimesSeconds.get(i) / 60f));
}
String accessTime = stopSequence.access == null ? null : String.format("%.1f", stopSequence.access.time / 60f);
Expand All @@ -55,9 +59,9 @@ public String[] detailsWithGtfsIds(TransitLayer transitLayer){
public Collection<TransitLeg> transitLegs(TransitLayer transitLayer) {
Collection<TransitLeg> transitLegs = new ArrayList<>();
for (int i = 0; i < routes.size(); i++) {
String routeString = transitLayer.routeString(routes.get(i), true);
String boardStop = transitLayer.stopString(stopSequence.boardStops.get(i), true);
String alightStop = transitLayer.stopString(stopSequence.alightStops.get(i), true);
String routeString = transitLayer.routeString(routes.get(i), NAME_AND_ID);
String boardStop = transitLayer.stopString(stopSequence.boardStops.get(i), NAME_AND_ID);
String alightStop = transitLayer.stopString(stopSequence.alightStops.get(i), NAME_AND_ID);
transitLegs.add(new TransitLeg(routeString, stopSequence.rideTimesSeconds.get(i), boardStop, alightStop));
}
return transitLegs;
Expand Down

0 comments on commit 5aa45f5

Please sign in to comment.