Skip to content

Commit

Permalink
Merge pull request #7 from matsim-scenarios/1.0
Browse files Browse the repository at this point in the history
1.0
  • Loading branch information
simei94 committed Apr 2, 2024
2 parents 7c5a228 + b61a23c commit 90d7284
Show file tree
Hide file tree
Showing 14 changed files with 580 additions and 20 deletions.
7 changes: 3 additions & 4 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ authors:
affiliation: Technische Universität Berlin
orcid: 'https://orcid.org/0000-0002-6354-4529'

url: "https://github.com/matsim-scenarios/matsim-mexico-city"
TODO: adapt release date
date-released: 2023-01-01
year: 2023
url: "https://github.com/matsim-scenarios/matsim-mexico-city/tree/1.0"
date-released: 2024-03-26
year: 2024
version: 1.0
license: AGPL-3.0
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.matsim-scenarios</groupId>
<artifactId>matsim-mexico-city</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.0</version>

<name>MATSim Open Mexico-City Model</name>
<description>MATSim Open Mexico-City Model</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*;

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

@CommandLine.Command(name = "roadPricing", description = "Calculates various road pricing related metrics.")
@CommandSpec(
requires = {"personMoneyEvents.tsv", "persons.csv", "config.xml"},
produces = {"roadPricing_income_groups.csv", "roadPricing_tolled_agents.csv", "roadPricing_daytime_groups.csv", "roadPricing_tolled_agents_home_locations.csv", "roadPricing_area.shp"}
produces = {"roadPricing_income_groups.csv", "roadPricing_avg_toll_income_groups.csv", "roadPricing_tolled_agents.csv", "roadPricing_daytime_groups.csv", "roadPricing_tolled_agents_home_locations.csv", "roadPricing_area.shp"}
)
public class RoadPricingAnalysis implements MATSimAppCommand {

Expand All @@ -58,6 +60,7 @@ public class RoadPricingAnalysis implements MATSimAppCommand {
String share = "share";
String person = "person";
String incomeGroup = "incomeGroup";
String amount = "amount";

public static void main(String[] args) {
new RoadPricingAnalysis().execute(args);
Expand All @@ -75,25 +78,31 @@ public Integer call() throws Exception {
.separator(csv.detectDelimiter(input.getPath("persons.csv"))).build());

Table moneyEvents = Table.read().csv(CsvReadOptions.builder(IOUtils.getBufferedReader(input.getPath("personMoneyEvents.tsv")))
.columnTypesPartial(Map.of(person, ColumnType.TEXT, "time", ColumnType.DOUBLE, "purpose", ColumnType.TEXT, "amount", ColumnType.DOUBLE))
.columnTypesPartial(Map.of(person, ColumnType.TEXT, "time", ColumnType.DOUBLE, "purpose", ColumnType.TEXT, amount, ColumnType.DOUBLE))
.sample(false)
.separator(csv.detectDelimiter(input.getPath("personMoneyEvents.tsv"))).build());

// filter person money events for toll events only
IntList idx = new IntArrayList();
for (int i = 0; i < moneyEvents.rowCount(); i++) {
Row row = moneyEvents.row(i);
if (row.getString("purpose").contains("toll")) {
if (row.getString("purpose").toLowerCase().contains("toll")) {
idx.add(i);
}
}
Table filtered = moneyEvents.where(Selection.with(idx.toIntArray()));

double totalToll = (double) filtered.summarize("amount", AggregateFunctions.sum).apply().column("Sum [amount]").get(0);
double totalToll = (double) filtered.summarize(amount, AggregateFunctions.sum).apply().column("Sum [amount]").get(0);
double medianTollPaid = MexicoCityUtils.calcMedian(filtered.doubleColumn(amount).asList());
double meanTollPaid = totalToll / filtered.rowCount();

DecimalFormat f = new DecimalFormat("0.00", new DecimalFormatSymbols(Locale.ENGLISH));

try (CSVPrinter printer = new CSVPrinter(new FileWriter(output.getPath("roadPricing_tolled_agents.csv").toString()), CSVFormat.DEFAULT)) {
printer.printRecord("total toll paid [MXN]", totalToll, "paid_FILL1_wght400_GRAD0_opsz48.png", "https://svn.vsp.tu-berlin.de/repos/public-svn/matsim/scenarios/countries/mx/mexico-city/mexico-city-v1.0/input/roadPricing/");
printer.printRecord("number of tolled agents", filtered.rowCount(), "tag_FILL1_wght400_GRAD0_opsz48.png", "https://svn.vsp.tu-berlin.de/repos/public-svn/matsim/scenarios/countries/mx/mexico-city/mexico-city-v1.0/input/roadPricing/");
printer.printRecord("\"total toll paid [MXN]\"", f.format(totalToll));
printer.printRecord("\"number of tolled agents\"", filtered.rowCount());
printer.printRecord("\"mean toll paid [MXN]\"", f.format(meanTollPaid));
printer.printRecord("\"median toll paid [MXN]\"", f.format(medianTollPaid));
}

Table joined = new DataFrameJoiner(moneyEvents, person).inner(persons);
Expand Down Expand Up @@ -198,24 +207,31 @@ private void writeIncomeDistr(Table joined) {

Table aggr = joined.summarize(person, count).by(incomeGroup);

// how to sort rows here? this does not work! Using workaround instead. -sme0324
aggr = new DataFrameJoiner(new DataFrameJoiner(aggr, incomeGroup)
.inner(joined.summarize(amount, mean).by(incomeGroup)), incomeGroup)
.inner(joined.summarize(amount, median).by(incomeGroup));

// how to sort rows here? agg.sortOn does not work! Using workaround instead. -sme0324
DoubleColumn shareCol = aggr.numberColumn(1).divide(aggr.numberColumn(1).sum()).setName(this.share);
aggr.addColumns(shareCol);
aggr.sortOn(this.share);

List<String> incomeDistr = new ArrayList<>();
List<String> avgTolls = new ArrayList<>();

for (String k : labels.keySet()) {
for (int i = 0; i < aggr.rowCount() - 1; i++) {
Row row = aggr.row(i);
if (row.getString(incomeGroup).equals(k)) {
incomeDistr.add(k + "," + row.getDouble("Count [person]") + "," + row.getDouble("share"));
avgTolls.add(k + "," + Math.abs(row.getDouble("Mean [amount]")) + "," + Math.abs(row.getDouble("Median [amount]")));
break;
}
}
}

incomeDistr.sort(Comparator.comparingInt(RoadPricingAnalysis::getLowerBound));
avgTolls.sort(Comparator.comparingInt(RoadPricingAnalysis::getLowerBound));

CSVFormat format = CSVFormat.DEFAULT.builder()
.setQuote(null)
Expand All @@ -224,13 +240,25 @@ private void writeIncomeDistr(Table joined) {
.build();


// print income distr
try (CSVPrinter printer = new CSVPrinter(new FileWriter(output.getPath("roadPricing_income_groups.csv").toString()), format)) {
printer.printRecord("incomeGroup,Count [person],share");
for (String s : incomeDistr) {
printer.printRecord(s);
}
} catch (IOException e) {
throw new IllegalArgumentException();
}

// print avg toll paid per income group
try (CSVPrinter printer = new CSVPrinter(new FileWriter(output.getPath("roadPricing_avg_toll_income_groups.csv").toString()), format)) {
printer.printRecord("incomeGroup,Mean [amount],Median [amount]");
for (String s : avgTolls) {
printer.printRecord(s);
}
} catch (IOException e) {
throw new IllegalArgumentException();
}
}

private static int getLowerBound(String s) {
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/org/matsim/dashboard/RoadPricingDashboard.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,22 @@ public void configure(Header header, Layout layout) {
viz.width = 2.0;
viz.setShape(data.compute(RoadPricingAnalysis.class, "roadPricing_area.shp"), "id");
});

layout.row("fourth")
.el(Bar.class, (viz, data) -> {
viz.title = "Paid toll per income group";
viz.description = "mean and median values";
viz.stacked = false;
viz.dataset = data.compute(RoadPricingAnalysis.class, "roadPricing_avg_toll_income_groups.csv");
viz.x = "incomeGroup";
viz.xAxisName = "income group";
viz.yAxisName = "Toll paid [MXN]";
viz.columns = List.of("Mean [amount]", "Median [amount]");
});
}

@Override
public double priority() {
return -2;
}
}
22 changes: 22 additions & 0 deletions src/main/java/org/matsim/prepare/MexicoCityUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
Expand Down Expand Up @@ -88,5 +90,25 @@ public static Coord roundCoord(Coord coord) {
return new Coord(roundNumber(coord.getX()), roundNumber(coord.getY()));
}

/**
* helper method to calc a median of a list of doubles.
*/
public static Double calcMedian(List<Double> values) {

Collections.sort(values);

int length = values.size();
// Check if the length of the array is odd or even
if (length % 2 != 0) {
// If odd, return the middle element
return values.get(length / 2);
} else {
// If even, return the average of the two middle elements
int midIndex1 = length / 2 - 1;
int midIndex2 = length / 2;
return (values.get(midIndex1) + values.get(midIndex2)) / 2.0;
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* *
* *********************************************************************** */

package org.matsim.run;
package org.matsim.run.MexicoCityRoadPricing;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* *
* *********************************************************************** */

package org.matsim.run;
package org.matsim.run.MexicoCityRoadPricing;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* *********************************************************************** *
* * project: org.matsim.*
* * PlansCalcRouteWithTollOrNot.java
* * *
* * *********************************************************************** *
* * *
* * copyright : (C) 2015 by the members listed in the COPYING, *
* * LICENSE and WARRANTY file. *
* * email : info at matsim dot org *
* * *
* * *********************************************************************** *
* * *
* * This program is free software; you can redistribute it and/or modify *
* * it under the terms of the GNU General Public License as published by *
* * the Free Software Foundation; either version 2 of the License, or *
* * (at your option) any later version. *
* * See also COPYING, LICENSE and WARRANTY file *
* * *
* * ***********************************************************************
*/

package org.matsim.run.MexicoCityRoadPricing;

import jakarta.inject.Inject;
import jakarta.inject.Provider;
import org.matsim.api.core.v01.TransportMode;
import org.matsim.api.core.v01.population.Leg;
import org.matsim.api.core.v01.population.Plan;
import org.matsim.api.core.v01.population.PlanElement;
import org.matsim.contrib.roadpricing.RoadPricingScheme;
import org.matsim.core.population.algorithms.PlanAlgorithm;
import org.matsim.core.population.routes.NetworkRoute;
import org.matsim.core.router.PlanRouter;
import org.matsim.core.router.TripRouter;
import org.matsim.core.router.TripStructureUtils;
import org.matsim.core.utils.timing.TimeInterpretation;


/**
* copied from org.matsim.contrib.roadpricing.
*/
class MexicoCityPlansCalcRouteWithTollOrNot implements PlanAlgorithm {

static final String CAR_WITH_PAYED_AREA_TOLL = "car_with_payed_area_toll";
private RoadPricingScheme roadPricingScheme;
private final PlanRouter planRouter ;

@Inject
MexicoCityPlansCalcRouteWithTollOrNot(RoadPricingScheme roadPricingScheme, Provider<TripRouter> tripRouterProvider, TimeInterpretation timeInterpretation) {
this.roadPricingScheme = roadPricingScheme;
this.planRouter = new PlanRouter( tripRouterProvider.get(), timeInterpretation ) ;
}

@Override
public void run(final Plan plan) {
handlePlan(plan);
}

private void handlePlan(Plan plan) {
// This calculates a best-response plan from the two options, paying area toll or not.
// From what I understand, it may be simpler/better to just throw a coin and produce
// one of the two options.
replaceCarModeWithTolledCarMode(plan);
planRouter.run( plan );
double areaToll = roadPricingScheme.getTypicalCosts().iterator().next().amount;
double routeCostWithAreaToll = sumNetworkModeCosts(plan) + areaToll;
replaceTolledCarModeWithCarMode(plan);
planRouter.run( plan );
double routeCostWithoutAreaToll = sumNetworkModeCosts(plan);
if (routeCostWithAreaToll < routeCostWithoutAreaToll) {
replaceCarModeWithTolledCarMode(plan);
planRouter.run( plan );
}
}

// This most likely will not work for intermodal setups with car e.g. as access mode to pt and routing mode of the trip
// something else than car.
// However, it did not work before the switch to routing mode either. - gl-nov'19
private void replaceCarModeWithTolledCarMode(Plan plan) {
for (PlanElement planElement : plan.getPlanElements()) {
if (planElement instanceof Leg) {
if (TripStructureUtils.getRoutingMode((Leg) planElement).equals(TransportMode.car)) {
TripStructureUtils.setRoutingMode( (Leg) planElement , CAR_WITH_PAYED_AREA_TOLL );
}
}
}
}

private void replaceTolledCarModeWithCarMode(Plan plan) {
for (PlanElement planElement : plan.getPlanElements()) {
if (planElement instanceof Leg) {
if (TripStructureUtils.getRoutingMode((Leg) planElement).equals(CAR_WITH_PAYED_AREA_TOLL)) {
TripStructureUtils.setRoutingMode( (Leg) planElement , TransportMode.car );
}
}
}
}

private double sumNetworkModeCosts(Plan plan) {
double sum = 0.0;
for (PlanElement planElement : plan.getPlanElements()) {
if (planElement instanceof Leg) {
Leg leg = (Leg) planElement;
if (leg.getRoute() instanceof NetworkRoute) {
NetworkRoute networkRoute = (NetworkRoute) leg.getRoute();
sum += networkRoute.getTravelCost();
}
}
}
return sum;
}

}

Loading

0 comments on commit 90d7284

Please sign in to comment.