diff --git a/src/main/java/com/conveyal/analysis/models/AnalysisRequest.java b/src/main/java/com/conveyal/analysis/models/AnalysisRequest.java index f2249c7fe..3bff888e8 100644 --- a/src/main/java/com/conveyal/analysis/models/AnalysisRequest.java +++ b/src/main/java/com/conveyal/analysis/models/AnalysisRequest.java @@ -12,8 +12,10 @@ import com.conveyal.r5.analyst.scenario.Scenario; import com.conveyal.r5.api.util.LegMode; import com.conveyal.r5.api.util.TransitModes; +import com.conveyal.r5.transit.TransitLayer; import com.mongodb.QueryBuilder; import org.apache.commons.codec.digest.DigestUtils; +import org.checkerframework.checker.units.qual.C; import java.time.LocalDate; import java.util.ArrayList; @@ -182,6 +184,9 @@ public class AnalysisRequest { */ public Set 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. */ @@ -288,6 +293,7 @@ public void populateTask (AnalysisWorkerTask task, UserPermissions userPermissio task.includeTemporalDensity = includeTemporalDensity; task.dualAccessibilityThreshold = dualAccessibilityThreshold; task.flags = flags; + task.csvResultOptions = csvResultOptions; } private EnumSet getEnumSetFromString (String s) { diff --git a/src/main/java/com/conveyal/analysis/models/CsvResultOptions.java b/src/main/java/com/conveyal/analysis/models/CsvResultOptions.java new file mode 100644 index 000000000..c21b52aa3 --- /dev/null +++ b/src/main/java/com/conveyal/analysis/models/CsvResultOptions.java @@ -0,0 +1,18 @@ +package com.conveyal.analysis.models; + +import com.conveyal.r5.transit.TransitLayer; +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; +} diff --git a/src/main/java/com/conveyal/r5/analyst/cluster/AnalysisWorkerTask.java b/src/main/java/com/conveyal/r5/analyst/cluster/AnalysisWorkerTask.java index d64996a15..2698905fa 100644 --- a/src/main/java/com/conveyal/r5/analyst/cluster/AnalysisWorkerTask.java +++ b/src/main/java/com/conveyal/r5/analyst/cluster/AnalysisWorkerTask.java @@ -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; @@ -184,6 +185,9 @@ public abstract class AnalysisWorkerTask extends ProfileRequest { */ public Set 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? diff --git a/src/main/java/com/conveyal/r5/analyst/cluster/PathResult.java b/src/main/java/com/conveyal/r5/analyst/cluster/PathResult.java index 36e71699f..dd42972b1 100644 --- a/src/main/java/com/conveyal/r5/analyst/cluster/PathResult.java +++ b/src/main/java/com/conveyal/r5/analyst/cluster/PathResult.java @@ -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; @@ -19,9 +20,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import static com.conveyal.r5.transit.TransitLayer.EntityRepresentation.ID_ONLY; -import static com.conveyal.r5.transit.TransitLayer.EntityRepresentation.ID_AND_NAME; -import static com.conveyal.r5.transit.TransitLayer.EntityRepresentation.NAME_ONLY; import static com.google.common.base.Preconditions.checkState; /** @@ -53,7 +51,7 @@ public class PathResult { private final TransitLayer transitLayer; - private TransitLayer.EntityRepresentation nameOrId; + private final CsvResultOptions csvOptions; public static final String[] DATA_COLUMNS = new String[]{ "routes", @@ -82,17 +80,7 @@ public PathResult(AnalysisWorkerTask task, TransitLayer transitLayer) { } iterationsForPathTemplates = new Multimap[nDestinations]; this.transitLayer = transitLayer; - if (task.flags == null) { - nameOrId = ID_ONLY; - } else { - boolean includeEntityNames = task.flags.contains("csv_names"); - boolean includeEntityIds = !task.flags.contains("csv_no_ids"); - if (includeEntityIds && includeEntityNames) { - this.nameOrId = ID_AND_NAME; - } else if (includeEntityNames) { - this.nameOrId = NAME_ONLY; - } - } + this.csvOptions = task.csvResultOptions; } /** @@ -125,7 +113,7 @@ public ArrayList[] 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, nameOrId); + String[] path = routeSequence.detailsWithGtfsIds(transitLayer, csvOptions); double targetValue; IntStream totalWaits = iterations.stream().mapToInt(i -> i.waitTimes.sum()); if (stat == Stat.MINIMUM) { diff --git a/src/main/java/com/conveyal/r5/transit/TransitLayer.java b/src/main/java/com/conveyal/r5/transit/TransitLayer.java index a025ba4d0..7ec85e6fa 100644 --- a/src/main/java/com/conveyal/r5/transit/TransitLayer.java +++ b/src/main/java/com/conveyal/r5/transit/TransitLayer.java @@ -819,7 +819,7 @@ public TIntSet findStopsInGeometry (Geometry geometry) { } public enum EntityRepresentation { - ID_ONLY, NAME_ONLY, ID_AND_NAME + ID_ONLY, NAME_ONLY, NAME_AND_ID } /** @@ -841,7 +841,7 @@ public String routeString (int routeIndex, EntityRepresentation nameOrId) { } return switch (nameOrId) { case NAME_ONLY -> name; - case ID_AND_NAME -> id + " (" + name + ")"; + case NAME_AND_ID -> name + " (" + id + ")"; default -> id; }; } @@ -869,7 +869,7 @@ public String stopString(int stopIndex, EntityRepresentation nameOrId) { } return switch (nameOrId) { case NAME_ONLY -> stopName; - case ID_AND_NAME -> stopId + " (" + stopName + ")"; + case NAME_AND_ID -> stopName + " (" + stopId + ")"; default -> stopId; }; } diff --git a/src/main/java/com/conveyal/r5/transit/path/RouteSequence.java b/src/main/java/com/conveyal/r5/transit/path/RouteSequence.java index 3bb4f512c..1e60478f3 100644 --- a/src/main/java/com/conveyal/r5/transit/path/RouteSequence.java +++ b/src/main/java/com/conveyal/r5/transit/path/RouteSequence.java @@ -1,5 +1,6 @@ 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; @@ -10,7 +11,7 @@ import java.util.Objects; import java.util.StringJoiner; -import static com.conveyal.r5.transit.TransitLayer.EntityRepresentation.ID_AND_NAME; +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 { @@ -31,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, EntityRepresentation nameOrId){ + 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), nameOrId)); - boardStopIds.add(transitLayer.stopString(stopSequence.boardStops.get(i), nameOrId)); - alightStopIds.add(transitLayer.stopString(stopSequence.alightStops.get(i), nameOrId)); + 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); @@ -58,9 +59,9 @@ public String[] detailsWithGtfsIds (TransitLayer transitLayer, EntityRepresentat public Collection transitLegs(TransitLayer transitLayer) { Collection transitLegs = new ArrayList<>(); for (int i = 0; i < routes.size(); i++) { - String routeString = transitLayer.routeString(routes.get(i), ID_AND_NAME); - String boardStop = transitLayer.stopString(stopSequence.boardStops.get(i), ID_AND_NAME); - String alightStop = transitLayer.stopString(stopSequence.alightStops.get(i), ID_AND_NAME); + 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;