Skip to content

Commit

Permalink
SimWrapper Dashboard features (matsim-org#3381)
Browse files Browse the repository at this point in the history
* add explanations to the traffic dashboard, add pt viewer dashboard

* add additional description

* add avg beeline speed to trip dashboard

* added mode share distance distribution

* added plot for detailed distance distribution

* upscale emission dashboard with sample size
  • Loading branch information
rakow authored Jul 30, 2024
1 parent e1783db commit 33d857b
Show file tree
Hide file tree
Showing 13 changed files with 533 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,11 @@ private void writeOutput(Network network, EmissionsOnLinkEventHandler emissionsE
if (link2pollutants.get(linkId).get(pollutant) != null) {
emissionValue = link2pollutants.get(linkId).get(pollutant);
}
absolute.print(nf.format(emissionValue));
absolute.print(nf.format(emissionValue * sample.getUpscaleFactor()));

Link link = network.getLinks().get(linkId);
double emissionPerM = emissionValue / link.getLength();
perMeter.print(nf.format(emissionPerM));
perMeter.print(nf.format(emissionPerM * sample.getUpscaleFactor()));
}

absolute.println();
Expand Down Expand Up @@ -246,7 +246,7 @@ private void writeTotal(Network network, EmissionsOnLinkEventHandler emissionsEv

total.printRecord("Pollutant", "kg");
for (Pollutant p : Pollutant.values()) {
double val = (sum.getDouble(p) / sample.getSample()) / 1000;
double val = (sum.getDouble(p) * sample.getUpscaleFactor()) / 1000;
total.printRecord(p, val < 100_000 && val > 100 ? simple.format(val) : scientific.format(val));
}

Expand Down Expand Up @@ -286,7 +286,7 @@ private void writeAvroRaster(Network network, Config config, EmissionsOnLinkEven
for (int xi = 0; xi < xLength.get(0); xi++) {
for (int yi = 0; yi < yLength.get(0); yi++) {
Coord coord = raster.getCoordForIndex(xi, yi);
double value = rasterMap.get(Pollutant.CO2_TOTAL).getValueByIndex(xi, yi);
double value = rasterMap.get(Pollutant.CO2_TOTAL).getValueByIndex(xi, yi) * sample.getUpscaleFactor();
if (xi == 0) yCoords.add((float) coord.getY());
if (yi == 0) xCoords.add((float) coord.getX());
valuesList.add((float) value);
Expand Down Expand Up @@ -349,7 +349,7 @@ private void writeRaster(Network network, Config config, EmissionsOnLinkEventHan
for (int yi = 0; yi < yLength.get(0); yi++) {

Coord coord = raster.getCoordForIndex(xi, yi);
double value = rasterMap.get(Pollutant.CO2_TOTAL).getValueByIndex(xi, yi);
double value = rasterMap.get(Pollutant.CO2_TOTAL).getValueByIndex(xi, yi) * sample.getUpscaleFactor();

if (value == 0)
continue;
Expand Down Expand Up @@ -406,7 +406,7 @@ private void writeTimeDependentRaster(Network network, Config config, EmissionsO
for (TimeBinMap.TimeBin<Map<Pollutant, Raster>> timeBin : timeBinMap.getTimeBins()) {

Coord coord = raster.getCoordForIndex(xi, yi);
double value = timeBin.getValue().get(Pollutant.CO2_TOTAL).getValueByIndex(xi, yi);
double value = timeBin.getValue().get(Pollutant.CO2_TOTAL).getValueByIndex(xi, yi) * sample.getUpscaleFactor();

if (value == 0)
continue;
Expand Down Expand Up @@ -467,7 +467,8 @@ private void writeTimeDependentAvroRaster(Network network, Config config, Emissi
if (yi == 0 && isFirst)
xCoords.add((float) coord.getX());

valuesList.add((float) timeBin.getValue().get(Pollutant.CO2_TOTAL).getValueByIndex(xi, yi));
double value = timeBin.getValue().get(Pollutant.CO2_TOTAL).getValueByIndex(xi, yi) * sample.getUpscaleFactor();
valuesList.add((float) value);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.math3.analysis.interpolation.LoessInterpolator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.locationtech.jts.geom.Coordinate;
Expand All @@ -32,6 +33,7 @@
import java.math.RoundingMode;
import java.nio.file.Files;
import java.util.*;
import java.util.stream.IntStream;

import static tech.tablesaw.aggregate.AggregateFunctions.count;

Expand All @@ -41,6 +43,7 @@
produces = {
"mode_share.csv", "mode_share_per_dist.csv", "mode_users.csv", "trip_stats.csv",
"mode_share_per_%s.csv", "population_trip_stats.csv", "trip_purposes_by_hour.csv",
"mode_share_distance_distribution.csv",
"mode_choices.csv", "mode_choice_evaluation.csv", "mode_choice_evaluation_per_mode.csv",
"mode_confusion_matrix.csv", "mode_prediction_error.csv"
}
Expand Down Expand Up @@ -109,6 +112,25 @@ private static int durationToSeconds(String d) {
return (Integer.parseInt(split[0]) * 60 * 60) + (Integer.parseInt(split[1]) * 60) + Integer.parseInt(split[2]);
}

private static double[] calcHistogram(double[] data, double[] bins) {

double[] hist = new double[bins.length - 1];

for (int i = 0; i < bins.length - 1; i++) {

double binStart = bins[i];
double binEnd = bins[i + 1];

// The last right bin edge is inclusive, which is consistent with the numpy implementation
if (i == bins.length - 2)
hist[i] = Arrays.stream(data).filter(d -> d >= binStart && d <= binEnd).count();
else
hist[i] = Arrays.stream(data).filter(d -> d >= binStart && d < binEnd).count();
}

return hist;
}

@Override
public Integer call() throws Exception {

Expand Down Expand Up @@ -247,6 +269,8 @@ public Integer call() throws Exception {

writeTripPurposes(joined);

writeTripDistribution(joined);

return 0;
}

Expand Down Expand Up @@ -293,13 +317,15 @@ private void writeTripStats(Table trips) throws IOException {
Object2IntMap<String> n = new Object2IntLinkedOpenHashMap<>();
Object2LongMap<String> travelTime = new Object2LongOpenHashMap<>();
Object2LongMap<String> travelDistance = new Object2LongOpenHashMap<>();
Object2LongMap<String> beelineDistance = new Object2LongOpenHashMap<>();

for (Row trip : trips) {
String mainMode = trip.getString("main_mode");

n.mergeInt(mainMode, 1, Integer::sum);
travelTime.mergeLong(mainMode, durationToSeconds(trip.getString("trav_time")), Long::sum);
travelDistance.mergeLong(mainMode, trip.getLong("traveled_distance"), Long::sum);
beelineDistance.mergeLong(mainMode, trip.getLong("euclidean_distance"), Long::sum);
}

try (CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(output.getPath("trip_stats.csv")), CSVFormat.DEFAULT)) {
Expand Down Expand Up @@ -338,6 +364,13 @@ private void writeTripStats(Table trips) throws IOException {
}
printer.println();

printer.print("Avg. beeline speed [km/h]");
for (String m : modeOrder) {
double speed = (beelineDistance.getLong(m) / 1000d) / (travelTime.getLong(m) / (60d * 60d));
printer.print(new BigDecimal(speed).setScale(2, RoundingMode.HALF_UP));
}
printer.println();

printer.print("Avg. distance per trip [km]");
for (String m : modeOrder) {
double avg = (travelDistance.getLong(m) / 1000d) / (n.getInt(m));
Expand Down Expand Up @@ -458,6 +491,55 @@ private void writeTripPurposes(Table trips) {

}

private void writeTripDistribution(Table trips) throws IOException {

Map<String, double[]> dists = new LinkedHashMap<>();

// Note that the results of this interpolator are consistent with the one performed in matsim-python-tools
// This makes the results comparable with reference data, changes here will also require changes in the python package
LoessInterpolator inp = new LoessInterpolator(0.05, 0);

long max = distGroups.get(distGroups.size() - 3) + distGroups.get(distGroups.size() - 2);

double[] bins = IntStream.range(0, (int) (max / 100)).mapToDouble(i -> i * 100).toArray();
double[] x = Arrays.copyOf(bins, bins.length - 1);

for (String mode : modeOrder) {
double[] distances = trips.where(
trips.stringColumn("main_mode").equalsIgnoreCase(mode))
.numberColumn("traveled_distance").asDoubleArray();

double[] hist = calcHistogram(distances, bins);

double[] y = inp.smooth(x, hist);
dists.put(mode, y);
}

try (CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(output.getPath("mode_share_distance_distribution.csv")), CSVFormat.DEFAULT)) {

printer.print("dist");
for (String s : modeOrder) {
printer.print(s);
}
printer.println();

for (int i = 0; i < x.length; i++) {

double sum = 0;
for (String s : modeOrder) {
sum += Math.max(0, dists.get(s)[i]);
}

printer.print(x[i]);
for (String s : modeOrder) {
double value = Math.max(0, dists.get(s)[i]) / sum;
printer.print(value);
}
printer.println();
}
}
}

/**
* How shape file filtering should be applied.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

/**
* Class to calculate the traffic congestion index based on the paper
* "A Traffic Congestion Assessment Method for Urban Road Networks Based on Speed Performance Index" by Feifei He, Xuedong Yan*, Yang Liu, Lu Ma.
* "A Traffic Congestion Assessment Method for Urban Road Networks Based on Speed Performance Index" by Feifei He, Xuedong Yan, Yang Liu, Lu Ma.
*/
public final class TrafficStatsCalculator {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ public double getSample() {
return sample;
}

/**
* Return factor that is used to upscale the sample size.
*/
public double getUpscaleFactor() {
return 1.0 / sample;
}

private void setSize(double sample) {
this.set = true;
this.sample = sample;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ public List<Dashboard> getDashboards(Config config, SimWrapper simWrapper) {
List<Dashboard> result = new ArrayList<>(List.of(
new OverviewDashboard(),
new TripDashboard(),
new TrafficDashboard(),
new StuckAgentDashboard()
new TrafficDashboard()
));

if (config.transit().isUseTransit()) {
result.add(new PublicTransitDashboard());
}

if (config.counts().getCountsFileName() != null) {
result.add(new TrafficCountsDashboard());
}
Expand All @@ -35,6 +38,8 @@ public List<Dashboard> getDashboards(Config config, SimWrapper simWrapper) {
result.add(new NoiseDashboard());
}

result.add(new StuckAgentDashboard());

return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class EmissionsDashboard implements Dashboard {
public void configure(Header header, Layout layout) {

header.title = "Emissions";
header.description = "Shows the emissions footprint and spatial distribution.";
header.description = "Shows the emissions footprint and spatial distribution. Shown values are already upscaled from simulated sample size.";


layout.row("links")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.matsim.simwrapper.dashboard;

import org.matsim.simwrapper.Dashboard;
import org.matsim.simwrapper.Header;
import org.matsim.simwrapper.Layout;
import org.matsim.simwrapper.viz.TransitViewer;

/**
* Standard dashboard for public transit.
*/
public class PublicTransitDashboard implements Dashboard {

@Override
public void configure(Header header, Layout layout) {

header.title = "Public Transit";
header.tab = "PT";
header.triggerPattern = "*output_transitSchedule*xml*";

layout.row("viewer").el(TransitViewer.class, (viz, data) -> {
viz.title = "Transit Viewer";
viz.height = 12d;
viz.description = "Visualize the transit schedule.";
viz.network = "*output_network.xml.gz";
viz.transitSchedule = data.output("*output_transitSchedule.xml.gz");
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public TrafficCountsDashboard withQualityLabels(List<Double> limits, List<String
public void configure(Header header, Layout layout) {

header.title = "Traffic Counts";
header.description = "Comparison of observed and simulated daily traffic volumes.\nError metrics: ";
header.description = "Comparison of observed and simulated daily traffic volumes. Reported volumes are scaled-up according to simulated sample size. \nError metrics based on relative error: ";

for (int i = 0; i < labels.size(); i++) {
if (i == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
import org.matsim.simwrapper.Dashboard;
import org.matsim.simwrapper.Header;
import org.matsim.simwrapper.Layout;
import org.matsim.simwrapper.viz.ColorScheme;
import org.matsim.simwrapper.viz.MapPlot;
import org.matsim.simwrapper.viz.Plotly;
import org.matsim.simwrapper.viz.Table;
import org.matsim.simwrapper.viz.*;
import tech.tablesaw.plotly.components.Axis;
import tech.tablesaw.plotly.traces.ScatterTrace;

Expand Down Expand Up @@ -93,5 +90,18 @@ public void configure(Header header, Layout layout) {
viz.height = 12d;
});


layout.row("info").el(TextBlock.class, (viz, data) -> {
viz.backgroundColor = "transparent";
viz.content = """
### Notes
- The speed performance index is the ratio of average travel speed and the maximum permissible road speed.
A performance index of 0.5, means that the average speed is half of the maximum permissible speed. A road with a performance index below 0.5 is considered to be in a congested state.
- The congestion index is the ratio of time a road is in an uncongested state. 0.5 means that a road is congested half of the time. A road with 1.0 is always uncongested.
cf. *A Traffic Congestion Assessment Method for Urban Road Networks Based on Speed Performance Index* by Feifei He, Xuedong Yan*, Yang Liu, Lu Ma.
""";
});

}
}
Loading

0 comments on commit 33d857b

Please sign in to comment.