Skip to content

Commit

Permalink
add travel time analysis and dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
rakow committed Jul 8, 2023
1 parent 9853bd9 commit 892d59b
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 34 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ $p/berlin-commercialPersonTraffic-$V-25pct.plans.xml.gz:
--nameOutputPopulation $(notdir $@)\
--pathOutput output/commercialPersonTraffic

mv output/businessTraffic/$(notdir $@) $@
mv output/commercialPersonTraffic/$(notdir $@) $@

$p/berlin-goodsTraffic-$V-25pct.plans.xml.gz:
$(sc) prepare generate-small-scale-commercial-traffic\
Expand All @@ -234,7 +234,7 @@ $p/berlin-goodsTraffic-$V-25pct.plans.xml.gz:
--nameOutputPopulation $(notdir $@)\
--pathOutput output/goodsTraffic

mv output/freightTraffic/$(notdir $@) $@
mv output/goodsTraffic/$(notdir $@) $@

# Depends on location choice runs and freight model
$p/berlin-cadyts-input-$V-25pct.plans.xml.gz: $p/berlin-commercialPersonTraffic-$V-25pct.plans.xml.gz
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<artifactId>matsim-all</artifactId>

<!-- PR-labelled release -->
<!-- <version>16.0-PR2658</version>-->
<!-- <version>16.0-PR2680</version>-->

<!-- snapshot == not recommended: rather use PR-labelled release!-->
<version>16.0-SNAPSHOT</version>
Expand Down
9 changes: 9 additions & 0 deletions src/main/R/routes_val.R
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,12 @@ ggplot(aggr, aes(x=hour)) +
geom_line(mapping = aes(y=min)) +
geom_line(mapping = aes(y=mean)) +
geom_line(mapping = aes(y=max))

#########


trips <- read_csv("src/main/python/table-trips.csv") %>%
mutate(speed=gis_length / (duration/60)) %>%
mutate(across(where(is.numeric), ~na_if(., Inf))) %>%
drop_na()

147 changes: 147 additions & 0 deletions src/main/java/org/matsim/analysis/TravelTimeComparison.java
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;
}

}
16 changes: 14 additions & 2 deletions src/main/java/org/matsim/dashboard/BerlinDashboardProvider.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package org.matsim.dashboard;

import org.matsim.core.config.Config;
import org.matsim.core.utils.io.IOUtils;
import org.matsim.simwrapper.Dashboard;
import org.matsim.simwrapper.DashboardProvider;
import org.matsim.simwrapper.SimWrapper;
import org.matsim.simwrapper.dashboard.TripDashboard;

import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;

/**
Expand All @@ -16,9 +20,17 @@ public class BerlinDashboardProvider implements DashboardProvider {
@Override
public List<Dashboard> getDashboards(Config config, SimWrapper simWrapper) {
TripDashboard trips = new TripDashboard("mode_share_ref.csv", "mode_share_per_dist_ref.csv", "mode_users_ref.csv");
trips.setAnalysisArgs("--match-id", "^berlin.+");
trips.setAnalysisArgs("--match-id", "^berlin.+", "--shp-filter", "none");

return List.of(trips);
URL refURL = IOUtils.extendUrl(config.getContext(), "berlin-v6.0-routes-ref.csv.gz");
String refData;
try {
refData = new File(refURL.toURI()).getAbsolutePath();
} catch (URISyntaxException e) {
refData = refURL.toString();
}

return List.of(trips, new DTVComparisonDashboard(), new TravelTimeComparisonDashboard(refData));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
import org.matsim.simwrapper.viz.PieChart;
import org.matsim.simwrapper.viz.Table;

public class GeoDataCountsDashboard implements Dashboard {
public class DTVComparisonDashboard implements Dashboard {

@Override
public double priority() {
return -1;
}

@Override
public void configure(Header header, Layout layout) {
Expand Down
132 changes: 132 additions & 0 deletions src/main/java/org/matsim/dashboard/TravelTimeComparisonDashboard.java
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")
);

}));
}
}
Loading

0 comments on commit 892d59b

Please sign in to comment.