-
Notifications
You must be signed in to change notification settings - Fork 140
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add travel time analysis and dashboard
- Loading branch information
Showing
12 changed files
with
345 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
147 changes: 147 additions & 0 deletions
147
src/main/java/org/matsim/analysis/TravelTimeComparison.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package org.matsim.analysis; | ||
|
||
import org.matsim.api.core.v01.Id; | ||
import org.matsim.api.core.v01.network.Link; | ||
import org.matsim.api.core.v01.network.Network; | ||
import org.matsim.api.core.v01.network.Node; | ||
import org.matsim.application.CommandSpec; | ||
import org.matsim.application.MATSimAppCommand; | ||
import org.matsim.application.options.InputOptions; | ||
import org.matsim.application.options.OutputOptions; | ||
import org.matsim.core.api.experimental.events.EventsManager; | ||
import org.matsim.core.events.EventsUtils; | ||
import org.matsim.core.router.FastDijkstraFactory; | ||
import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutility; | ||
import org.matsim.core.router.util.LeastCostPathCalculator; | ||
import org.matsim.core.router.util.TravelTime; | ||
import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime; | ||
import org.matsim.core.trafficmonitoring.TravelTimeCalculator; | ||
import org.matsim.core.utils.io.IOUtils; | ||
import picocli.CommandLine; | ||
import tech.tablesaw.api.ColumnType; | ||
import tech.tablesaw.api.DoubleColumn; | ||
import tech.tablesaw.api.Row; | ||
import tech.tablesaw.api.Table; | ||
import tech.tablesaw.columns.Column; | ||
import tech.tablesaw.io.csv.CsvReadOptions; | ||
|
||
import java.io.BufferedReader; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import static tech.tablesaw.aggregate.AggregateFunctions.mean; | ||
|
||
@CommandLine.Command( | ||
name = "travel-time-comparison", | ||
description = "Compare travel time with routes from API" | ||
) | ||
@CommandSpec( | ||
requireEvents = true, | ||
requireNetwork = true, | ||
produces = {"travel_time_comparison_by_hour.csv", "travel_time_comparison_by_route.csv"}, | ||
group = "travelTime" | ||
) | ||
public class TravelTimeComparison implements MATSimAppCommand { | ||
|
||
@CommandLine.Mixin | ||
private InputOptions input = InputOptions.ofCommand(TravelTimeComparison.class); | ||
|
||
@CommandLine.Mixin | ||
private OutputOptions output = OutputOptions.ofCommand(TravelTimeComparison.class); | ||
|
||
@CommandLine.Option(names = "--input-ref", description = "File with reference data", required = true) | ||
private String apiFile; | ||
|
||
public static void main(String[] args) { | ||
new TravelTimeComparison().execute(args); | ||
} | ||
|
||
@Override | ||
public Integer call() throws Exception { | ||
|
||
Table data; | ||
try (BufferedReader reader = IOUtils.getBufferedReader(apiFile)) { | ||
data = Table.read().csv(CsvReadOptions.builder(reader).columnTypesPartial(Map.of( | ||
"from_node", ColumnType.STRING, | ||
"to_node", ColumnType.STRING | ||
)).build()); | ||
} | ||
|
||
Network network = input.getNetwork(); | ||
TravelTime tt = collectTravelTimes(network).getLinkTravelTimes(); | ||
TravelTime fs = new FreeSpeedTravelTime(); | ||
|
||
OnlyTimeDependentTravelDisutility util = new OnlyTimeDependentTravelDisutility(tt); | ||
|
||
LeastCostPathCalculator congestedRouter = new FastDijkstraFactory(false).createPathCalculator(network, util, tt); | ||
LeastCostPathCalculator freeflowRouter = new FastDijkstraFactory(false).createPathCalculator(network, new OnlyTimeDependentTravelDisutility(fs), fs); | ||
|
||
data.addColumns( | ||
DoubleColumn.create("simulated", data.rowCount()), | ||
DoubleColumn.create("free_flow", data.rowCount()) | ||
); | ||
|
||
for (Row row : data) { | ||
LeastCostPathCalculator.Path congested = computePath(network, congestedRouter, row); | ||
double dist = congested.links.stream().mapToDouble(Link::getLength).sum(); | ||
double speed = 3.6 * dist / congested.travelTime; | ||
|
||
row.setDouble("simulated", speed); | ||
|
||
LeastCostPathCalculator.Path freeflow = computePath(network, freeflowRouter, row); | ||
dist = freeflow.links.stream().mapToDouble(Link::getLength).sum(); | ||
speed = 3.6 * dist / freeflow.travelTime; | ||
|
||
row.setDouble("free_flow", speed); | ||
} | ||
|
||
data.addColumns( | ||
data.doubleColumn("simulated").subtract(data.doubleColumn("mean")).setName("bias") | ||
); | ||
|
||
data.addColumns(data.doubleColumn("bias").abs().setName("abs_error")); | ||
|
||
data.write().csv(output.getPath("travel_time_comparison_by_route.csv").toFile()); | ||
|
||
List<String> columns = List.of("min", "max", "mean", "std", "simulated", "free_flow", "bias", "abs_error"); | ||
|
||
Table aggr = data.summarize(columns, mean).by("hour"); | ||
|
||
for (Column<?> column : aggr.columns()) { | ||
String name = column.name(); | ||
if (name.startsWith("Mean")) | ||
column.setName(name.substring(6, name.length() - 1)); | ||
} | ||
|
||
aggr.write().csv(output.getPath("travel_time_comparison_by_hour.csv").toFile()); | ||
|
||
return 0; | ||
} | ||
|
||
private LeastCostPathCalculator.Path computePath(Network network, LeastCostPathCalculator router, Row row) { | ||
Node fromNode = network.getNodes().get(Id.createNodeId(row.getString("from_node"))); | ||
Node toNode = network.getNodes().get(Id.createNodeId(row.getString("to_node"))); | ||
|
||
return router.calcLeastCostPath(fromNode, toNode, row.getInt("hour") * 3600, null, null); | ||
} | ||
|
||
private TravelTimeCalculator collectTravelTimes(Network network) { | ||
TravelTimeCalculator.Builder builder = new TravelTimeCalculator.Builder(network); | ||
builder.setCalculateLinkTravelTimes(true); | ||
builder.setMaxTime(86400); | ||
builder.setTimeslice(900); | ||
|
||
TravelTimeCalculator travelTimes = builder.build(); | ||
|
||
EventsManager manager = EventsUtils.createEventsManager(); | ||
|
||
manager.addHandler(travelTimes); | ||
|
||
manager.initProcessing(); | ||
EventsUtils.readEvents(manager, input.getEventsPath()); | ||
manager.finishProcessing(); | ||
|
||
return travelTimes; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
src/main/java/org/matsim/dashboard/TravelTimeComparisonDashboard.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package org.matsim.dashboard; | ||
|
||
import org.matsim.analysis.TravelTimeComparison; | ||
import org.matsim.simwrapper.Dashboard; | ||
import org.matsim.simwrapper.Header; | ||
import org.matsim.simwrapper.Layout; | ||
import org.matsim.simwrapper.viz.Plotly; | ||
import tech.tablesaw.plotly.components.Axis; | ||
import tech.tablesaw.plotly.components.Line; | ||
import tech.tablesaw.plotly.traces.ScatterTrace; | ||
|
||
/** | ||
* Compares travel time from simulation with reference data. | ||
*/ | ||
public class TravelTimeComparisonDashboard implements Dashboard { | ||
|
||
private final String refData; | ||
|
||
public TravelTimeComparisonDashboard(String refData) { | ||
this.refData = refData; | ||
} | ||
|
||
@Override | ||
public double priority() { | ||
return -1; | ||
} | ||
|
||
@Override | ||
public void configure(Header header, Layout layout) { | ||
|
||
header.title = "Travel time"; | ||
header.description = "Comparison of simulated travel times vs. results from routing services."; | ||
|
||
layout.row("first") | ||
.el(Plotly.class, (viz, data) -> { | ||
|
||
viz.title = "Travel time comparison"; | ||
viz.description = "by hour"; | ||
viz.fixedRatio = true; | ||
viz.height = 8d; | ||
|
||
viz.interactive = Plotly.Interactive.slider; | ||
viz.layout = tech.tablesaw.plotly.components.Layout.builder() | ||
.yAxis(Axis.builder().title("Observed mean travel time [km/h]").build()) | ||
.xAxis(Axis.builder().title("Simulated mean travel time [km/h]").build()) | ||
.build(); | ||
|
||
Plotly.DataSet ds = viz.addDataset(data.compute(TravelTimeComparison.class, "travel_time_comparison_by_route.csv", "--input-ref", refData)); | ||
|
||
viz.addTrace(ScatterTrace.builder(Plotly.INPUT, Plotly.INPUT).build(), ds.mapping() | ||
.y("mean") | ||
.x("simulated") | ||
.text("from_node") | ||
.name("hour") | ||
); | ||
|
||
}).el(Plotly.class, ((viz, data) -> { | ||
|
||
viz.title = "Avg. Speed"; | ||
viz.description = "by hour"; | ||
|
||
Plotly.DataSet ds = viz.addDataset(data.compute(TravelTimeComparison.class, "travel_time_comparison_by_hour.csv", "--input-ref", refData)); | ||
|
||
viz.layout = tech.tablesaw.plotly.components.Layout.builder() | ||
.xAxis(Axis.builder().title("Hour").build()) | ||
.yAxis(Axis.builder().title("Speed [km/h]").build()) | ||
.build(); | ||
|
||
viz.addTrace(ScatterTrace.builder(Plotly.INPUT, Plotly.INPUT) | ||
.name("Mean") | ||
.mode(ScatterTrace.Mode.LINE) | ||
.line(Line.builder().dash(Line.Dash.LONG_DASH_DOT).build()).build(), ds.mapping() | ||
.x("hour").y("mean") | ||
); | ||
|
||
viz.addTrace(ScatterTrace.builder(Plotly.INPUT, Plotly.INPUT) | ||
.name("Min") | ||
.mode(ScatterTrace.Mode.LINE) | ||
.line(Line.builder().dash(Line.Dash.DASH).build()).build(), ds.mapping() | ||
.x("hour").y("min") | ||
); | ||
|
||
viz.addTrace(ScatterTrace.builder(Plotly.INPUT, Plotly.INPUT) | ||
.name("Max") | ||
.mode(ScatterTrace.Mode.LINE) | ||
.line(Line.builder().dash(Line.Dash.DASH).build()).build(), ds.mapping() | ||
.x("hour").y("max") | ||
); | ||
|
||
viz.addTrace(ScatterTrace.builder(Plotly.INPUT, Plotly.INPUT) | ||
.name("Simulated") | ||
.mode(ScatterTrace.Mode.LINE) | ||
.line(Line.builder().dash(Line.Dash.SOLID).build()).build(), ds.mapping() | ||
.x("hour").y("simulated") | ||
); | ||
|
||
})).el(Plotly.class, ((viz, data) -> { | ||
|
||
viz.title = "Error and bias"; | ||
viz.description = "by hour"; | ||
|
||
Plotly.DataSet ds = viz.addDataset(data.compute(TravelTimeComparison.class, "travel_time_comparison_by_hour.csv", "--input-ref", refData)); | ||
|
||
viz.layout = tech.tablesaw.plotly.components.Layout.builder() | ||
.xAxis(Axis.builder().title("Hour").build()) | ||
.yAxis(Axis.builder().title("Speed [km/h]").build()) | ||
.build(); | ||
|
||
viz.addTrace(ScatterTrace.builder(Plotly.INPUT, Plotly.INPUT) | ||
.name("Mean abs. error") | ||
.mode(ScatterTrace.Mode.LINE) | ||
.line(Line.builder().dash(Line.Dash.SOLID).build()).build(), ds.mapping() | ||
.x("hour").y("abs_error") | ||
); | ||
|
||
viz.addTrace(ScatterTrace.builder(Plotly.INPUT, Plotly.INPUT) | ||
.name("Ref. std.") | ||
.mode(ScatterTrace.Mode.LINE) | ||
.line(Line.builder().dash(Line.Dash.LONG_DASH_DOT).build()).build(), ds.mapping() | ||
.x("hour").y("std") | ||
); | ||
|
||
viz.addTrace(ScatterTrace.builder(Plotly.INPUT, Plotly.INPUT) | ||
.name("Bias") | ||
.mode(ScatterTrace.Mode.LINE) | ||
.line(Line.builder().dash(Line.Dash.SOLID).build()).build(), ds.mapping() | ||
.x("hour").y("bias") | ||
); | ||
|
||
})); | ||
} | ||
} |
Oops, something went wrong.