diff --git a/src/main/java/org/matsim/dashboards/LausitzDashboardProvider.java b/src/main/java/org/matsim/dashboards/LausitzDashboardProvider.java index e4bd385..4bcf8c2 100644 --- a/src/main/java/org/matsim/dashboards/LausitzDashboardProvider.java +++ b/src/main/java/org/matsim/dashboards/LausitzDashboardProvider.java @@ -25,7 +25,8 @@ public List getDashboards(Config config, SimWrapper simWrapper) { .setAnalysisArgs("--person-filter", "subpopulation=person"); return List.of(trips, - new EmissionsDashboard(config.global().getCoordinateSystem()) + new EmissionsDashboard(config.global().getCoordinateSystem()), + new PtLineDashboard("https://svn.vsp.tu-berlin.de/repos/public-svn/matsim/scenarios/countries/de/lausitz/output/v2024.2/") // the NoiseAnalysis is not run here because it needs more RAM than the entire simulation, // which leads to VM crashes and prevents other analysis to run. We have to run it separately (e.g. with LausitzSimWrapperRunner) ); diff --git a/src/main/java/org/matsim/dashboards/LausitzSimWrapperRunner.java b/src/main/java/org/matsim/dashboards/LausitzSimWrapperRunner.java index 4c0df2b..370946e 100644 --- a/src/main/java/org/matsim/dashboards/LausitzSimWrapperRunner.java +++ b/src/main/java/org/matsim/dashboards/LausitzSimWrapperRunner.java @@ -66,6 +66,8 @@ public final class LausitzSimWrapperRunner implements MATSimAppCommand { private boolean trips; @CommandLine.Option(names = "--emissions", defaultValue = "false", description = "create emission dashboard") private boolean emissions; + @CommandLine.Option(names = "--pt-line-base-dir", description = "create pt line dashboard with base run dir as input") + private String baseDir; public LausitzSimWrapperRunner(){ @@ -75,7 +77,7 @@ public LausitzSimWrapperRunner(){ @Override public Integer call() throws Exception { - if (!noise && !trips && !emissions){ + if (!noise && !trips && !emissions && baseDir == null){ throw new IllegalArgumentException("you have not configured any dashboard to be created! Please use command line parameters!"); } @@ -136,6 +138,9 @@ public Integer call() throws Exception { new MatsimVehicleWriter(scenario.getTransitVehicles()).writeFile(transitVehiclesPath); } + if (baseDir != null) { + sw.addDashboard(new PtLineDashboard(baseDir)); + } try { sw.generate(runDirectory, true); diff --git a/src/main/java/org/matsim/dashboards/PtLineDashboard.java b/src/main/java/org/matsim/dashboards/PtLineDashboard.java new file mode 100644 index 0000000..c2a6fc7 --- /dev/null +++ b/src/main/java/org/matsim/dashboards/PtLineDashboard.java @@ -0,0 +1,126 @@ +package org.matsim.dashboards; + +import org.matsim.run.analysis.PtLineAnalysis; +import org.matsim.run.scenarios.LausitzScenario; +import org.matsim.simwrapper.Dashboard; +import org.matsim.simwrapper.Header; +import org.matsim.simwrapper.Layout; +import org.matsim.simwrapper.viz.*; +import tech.tablesaw.plotly.traces.BarTrace; + +import java.util.ArrayList; +import java.util.List; + +/** + * Shows information about an optional policy case, which implements a pt line between Cottbus and Hoyerswerda. + * It also compares the agents and their trips using the new pt line with their respective trips in the base case. + */ +public class PtLineDashboard implements Dashboard { + private final String basePath; + private static final String SHARE = "share"; + + PtLineDashboard(String basePath) { + if (!basePath.endsWith("/")) { + basePath += "/"; + } + this.basePath = basePath; + } + + @Override + public void configure(Header header, Layout layout) { + header.title = "Pt Line Dashboard"; + header.description = "Shows statistics about agents, who used the newly implemented pt line between Cottbus and Hoyerswerda " + + "and compares to the trips of those agents in the base case."; + + String[] args = new ArrayList<>(List.of("--base-path", basePath)).toArray(new String[0]); + + layout.row("first") + .el(Tile.class, (viz, data) -> { + viz.dataset = data.compute(PtLineAnalysis.class, "mean_travel_stats.csv", args); + viz.height = 0.1; + }); + + layout.row("second") + .el(Bar.class, (viz, data) -> { + viz.title = "Agents per income group"; + viz.stacked = false; + viz.dataset = data.compute(PtLineAnalysis.class, "pt_persons_income_groups.csv", args); + viz.x = "incomeGroup"; + viz.xAxisName = "income group"; + viz.yAxisName = SHARE; + viz.columns = List.of(SHARE); + }) + .el(Bar.class, (viz, data) -> { + viz.title = "Mean score per income group"; + viz.stacked = false; + viz.dataset = data.compute(PtLineAnalysis.class, "pt_persons_mean_score_per_income_group.csv", args); + viz.x = "incomeGroup"; + viz.xAxisName = "income group"; + viz.yAxisName = "mean score"; + viz.columns = List.of("mean_score_base", "mean_score_policy"); + }) + .el(Bar.class, (viz, data) -> { + viz.title = "Agents per age group"; + viz.stacked = false; + viz.dataset = data.compute(PtLineAnalysis.class, "pt_persons_age_groups.csv", args); + viz.x = "ageGroup"; + viz.xAxisName = "age group"; + viz.yAxisName = SHARE; + viz.columns = List.of(SHARE); + }); + + layout.row("third") + .el(Plotly.class, (viz, data) -> { + viz.title = "Modal split (base case)"; + viz.description = "Shows mode of agents in base case, which used the new pt line in the policy case."; + + viz.layout = tech.tablesaw.plotly.components.Layout.builder() + .barMode(tech.tablesaw.plotly.components.Layout.BarMode.STACK) + .build(); + + Plotly.DataSet ds = viz.addDataset(data.compute(PtLineAnalysis.class, "pt_persons_base_modal_share.csv", args)) + .constant("source", "Base Case Mode") + .aggregate(List.of("main_mode"), SHARE, Plotly.AggrFunc.SUM); + + viz.mergeDatasets = true; + viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT).orientation(BarTrace.Orientation.HORIZONTAL).build(), + ds.mapping() + .name("main_mode") + .y("source") + .x(SHARE) + ); + }) + .el(Hexagons.class, (viz, data) -> { + + viz.title = "Pt line agents home locations"; + viz.center = data.context().getCenter(); + viz.zoom = data.context().mapZoomLevel; + viz.height = 7.5; + viz.width = 2.0; + + viz.file = data.compute(PtLineAnalysis.class, "pt_persons_home_locations.csv"); + viz.projection = LausitzScenario.CRS; + viz.addAggregation("home locations", "person", "home_x", "home_y"); + }); + + layout.row("fourth") + .el(Table.class, (viz, data) -> { + viz.title = "Executed scores"; + viz.description = "... in base and policy case"; + viz.dataset = data.compute(PtLineAnalysis.class, "pt_persons_executed_score.csv"); + viz.showAllRows = true; + }) + .el(Table.class, (viz, data) -> { + viz.title = "Travel times"; + viz.description = "... in base and policy case"; + viz.dataset = data.compute(PtLineAnalysis.class, "pt_persons_trav_time.csv"); + viz.showAllRows = true; + }) + .el(Table.class, (viz, data) -> { + viz.title = "Travel distances"; + viz.description = "... in base and policy case"; + viz.dataset = data.compute(PtLineAnalysis.class, "pt_persons_traveled_distance.csv"); + viz.showAllRows = true; + }); + } +} diff --git a/src/main/java/org/matsim/run/scenarios/LausitzScenario.java b/src/main/java/org/matsim/run/scenarios/LausitzScenario.java index 69a1853..41f9022 100644 --- a/src/main/java/org/matsim/run/scenarios/LausitzScenario.java +++ b/src/main/java/org/matsim/run/scenarios/LausitzScenario.java @@ -70,6 +70,7 @@ public class LausitzScenario extends MATSimApplication { public static final String HEAVY_MODE = "truck40t"; public static final String MEDIUM_MODE = "truck18t"; public static final String LIGHT_MODE = "truck8t"; + public static final String CRS = "EPSG:25832"; // To decrypt hbefa input files set MATSIM_DECRYPTION_PASSWORD as environment variable. ask VSP for access. private static final String HBEFA_2020_PATH = "https://svn.vsp.tu-berlin.de/repos/public-svn/3507bb3997e5657ab9da76dbedbb13c9b5991d3e/0e73947443d68f95202b71a156b337f7f71604ae/";