From 12e947b204dc100b935cb5a0eb9c471641ada994 Mon Sep 17 00:00:00 2001 From: tschlenther Date: Tue, 30 Apr 2024 13:45:15 +0200 Subject: [PATCH 1/2] create Kelheim-specific emission dashboard (#70) * kelheim-specific emissions dashboard * bug-fixes: use allvehicles.xml and output only for filtered network * emission xyt plots * plot descriptions * some cleanup * update emission dashboard + post-process runner * space --- .../analysis/CreateEmissionDashboard.java | 143 +++++++ .../KelheimDashboardProvider.java | 6 +- .../emissions/KelheimEmissionsDashboard.java | 105 +++++ ...rPollutionAnalysisByEngineInformation.java | 359 +++++++++++++++--- .../org.matsim.simwrapper.DashboardProvider | 2 +- 5 files changed, 551 insertions(+), 64 deletions(-) create mode 100644 src/main/java/org/matsim/analysis/CreateEmissionDashboard.java rename src/main/java/org/matsim/{dashboards => analysis}/KelheimDashboardProvider.java (83%) create mode 100644 src/main/java/org/matsim/analysis/emissions/KelheimEmissionsDashboard.java diff --git a/src/main/java/org/matsim/analysis/CreateEmissionDashboard.java b/src/main/java/org/matsim/analysis/CreateEmissionDashboard.java new file mode 100644 index 00000000..f3aee046 --- /dev/null +++ b/src/main/java/org/matsim/analysis/CreateEmissionDashboard.java @@ -0,0 +1,143 @@ +/* *********************************************************************** * + * project: org.matsim.* + * Controler.java + * * + * *********************************************************************** * + * * + * copyright : (C) 2007 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.analysis; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.analysis.emissions.KelheimEmissionsDashboard; +import org.matsim.application.ApplicationUtils; +import org.matsim.application.MATSimAppCommand; +import org.matsim.application.options.ShpOptions; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.simwrapper.SimWrapper; +import org.matsim.simwrapper.SimWrapperConfigGroup; +import picocli.CommandLine; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +@CommandLine.Command( + name = "emissions", + description = "Run emission analysis and create SimWrapper dashboard for existing run output." +) +final class CreateEmissionDashboard implements MATSimAppCommand { + + private static final Logger log = LogManager.getLogger(CreateEmissionDashboard.class); + + @CommandLine.Parameters(arity = "1..*", description = "Path to run output directories for which emission dashboards are to be generated.") + private List inputPaths; + + @CommandLine.Mixin + private final ShpOptions shp = new ShpOptions(); + + @CommandLine.Option(names = "--base", description = "Optional. " + + "Relative path (from each! output directory provided) to main output folder for the base MATSim run. " + + "Will be used to compare emissions per link.", required = false) + private String baseRun; + + private CreateEmissionDashboard(){ + } + + @Override + public Integer call() throws Exception { + + for (Path runDirectory : inputPaths) { + log.info("Running on {}", runDirectory); + + //this is to avoid overriding + renameExistingDashboardYAMLs(runDirectory); + + Path configPath = ApplicationUtils.matchInput("config.xml", runDirectory); + Config config = ConfigUtils.loadConfig(configPath.toString()); + SimWrapper sw = SimWrapper.create(config); + + SimWrapperConfigGroup simwrapperCfg = ConfigUtils.addOrGetModule(config, SimWrapperConfigGroup.class); + if (shp.isDefined()){ + //not sure if this is the best way to go, might be that the shape file would be automatically read by providing the --shp command line option + simwrapperCfg.defaultParams().shp = shp.getShapeFile().toString(); + } + //skip default dashboards + simwrapperCfg.defaultDashboards = SimWrapperConfigGroup.Mode.disabled; + simwrapperCfg.defaultParams().mapCenter = "48.91265,11.89223"; + + if (baseRun != null){ + sw.addDashboard(new KelheimEmissionsDashboard(baseRun)); + } else { + sw.addDashboard(new KelheimEmissionsDashboard()); + } + + try { + sw.generate(runDirectory); + sw.run(runDirectory); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + return 0; + } + + public static void main(String[] args) { + new CreateEmissionDashboard().execute(args); + + } + + private static void renameExistingDashboardYAMLs(Path runDirectory) { + // List of files in the folder + File folder = new File(runDirectory.toString()); + File[] files = folder.listFiles(); + + // Loop through all files in the folder + if (files != null) { + for (File file : files) { + if (file.isFile()) { + // Check if the file name starts with "dashboard-" and ends with ".yaml" + if (file.getName().startsWith("dashboard-") && file.getName().endsWith(".yaml")) { + // Get the current file name + String oldName = file.getName(); + + // Extract the number from the file name + String numberPart = oldName.substring(oldName.indexOf('-') + 1, oldName.lastIndexOf('.')); + + // Increment the number by ten + int number = Integer.parseInt(numberPart) + 10; + + // Create the new file name + String newName = "dashboard-" + number + ".yaml"; + + // Create the new File object with the new file name + File newFile = new File(file.getParent(), newName); + + // Rename the file + if (file.renameTo(newFile)) { + log.info("File successfully renamed: " + newName); + } else { + log.info("Error renaming file: " + file.getName()); + } + } + } + } + } + } +} diff --git a/src/main/java/org/matsim/dashboards/KelheimDashboardProvider.java b/src/main/java/org/matsim/analysis/KelheimDashboardProvider.java similarity index 83% rename from src/main/java/org/matsim/dashboards/KelheimDashboardProvider.java rename to src/main/java/org/matsim/analysis/KelheimDashboardProvider.java index ed286f05..90382d21 100644 --- a/src/main/java/org/matsim/dashboards/KelheimDashboardProvider.java +++ b/src/main/java/org/matsim/analysis/KelheimDashboardProvider.java @@ -1,5 +1,6 @@ -package org.matsim.dashboards; +package org.matsim.analysis; +import org.matsim.analysis.emissions.KelheimEmissionsDashboard; import org.matsim.core.config.Config; import org.matsim.core.utils.io.IOUtils; import org.matsim.simwrapper.Dashboard; @@ -24,7 +25,8 @@ public List getDashboards(Config config, SimWrapper simWrapper) { trips.setAnalysisArgs("--dist-groups", "0,1000,2000,5000,10000,20000"); return List.of( trips, - new TravelTimeComparisonDashboard(IOUtils.resolveFileOrResource( "kelheim-v3.0-routes-ref.csv.gz").toString()) + new TravelTimeComparisonDashboard(IOUtils.resolveFileOrResource( "kelheim-v3.0-routes-ref.csv.gz").toString()), + new KelheimEmissionsDashboard() ); } diff --git a/src/main/java/org/matsim/analysis/emissions/KelheimEmissionsDashboard.java b/src/main/java/org/matsim/analysis/emissions/KelheimEmissionsDashboard.java new file mode 100644 index 00000000..fbd239de --- /dev/null +++ b/src/main/java/org/matsim/analysis/emissions/KelheimEmissionsDashboard.java @@ -0,0 +1,105 @@ +/* *********************************************************************** * + * project: org.matsim.* + * Controler.java + * * + * *********************************************************************** * + * * + * copyright : (C) 2007 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.analysis.emissions; + +import org.matsim.application.prepare.network.CreateGeoJsonNetwork; +import org.matsim.simwrapper.Dashboard; +import org.matsim.simwrapper.Header; +import org.matsim.simwrapper.Layout; +import org.matsim.simwrapper.viz.Links; +import org.matsim.simwrapper.viz.Table; +import org.matsim.simwrapper.viz.XYTime; + +/** + * this is basically equivalent to the standard emissions dashboard + * but calls the matsim-kelheim-specific emissions analysis class + * {@code KelheimOfflineAirPollutionAnalysisByEngineInformation} + * which has specific network and vehicle type attributes. + */ +public class KelheimEmissionsDashboard implements Dashboard{ + private final String pathToCsvBase; + + public KelheimEmissionsDashboard() { + this.pathToCsvBase = null; + } + + public KelheimEmissionsDashboard(String pathToBaseRun) { + if (!pathToBaseRun.endsWith("/")) { + pathToBaseRun += "/"; + } + this.pathToCsvBase = pathToBaseRun + "analysis/emissions/emissions_per_link_per_m.csv"; + } + + /** + * Produces the dashboard. + */ + public void configure(Header header, Layout layout) { + + header.title = "Emissions"; + header.description = "Shows the emissions footprint and spatial distribution."; + + String linkDescription = "Displays the emissions for each link per meter. Be aware that emission values are provided in the simulation sample size!"; + if (pathToCsvBase != null){ + linkDescription += String.format("\n Base is %s", pathToCsvBase); + } + String finalLinkDescription = linkDescription; + + layout.row("links") + .el(Table.class, (viz, data) -> { + viz.title = "Emissions"; + viz.description = "by pollutant. Total values are scaled from the simulation sample size to 100%."; + viz.dataset = data.compute(KelheimOfflineAirPollutionAnalysisByEngineInformation.class, "emissions_total.csv", new String[0]); + viz.enableFilter = false; + viz.showAllRows = true; + viz.width = 1.0; + }) + .el(Links.class, (viz, data) -> { + viz.title = "Emissions per Link per Meter"; + viz.description = finalLinkDescription; + viz.height = 12.0; + viz.datasets.csvFile = data.compute(KelheimOfflineAirPollutionAnalysisByEngineInformation.class, "emissions_per_link_per_m.csv", new String[0]); + viz.datasets.csvBase = this.pathToCsvBase; + viz.network = data.compute(CreateGeoJsonNetwork.class, "network.geojson", new String[0]); + viz.display.color.columnName = "CO2_TOTAL [g/m]"; + viz.display.color.dataset = "csvFile"; + //TODO how to set color ramp?? +// viz.display.color.setColorRamp(Plotly.ColorScheme.RdBu, 5, true); + viz.display.width.scaleFactor = 100; + viz.display.width.columnName = "CO2_TOTAL [g/m]"; + viz.display.width.dataset = "csvFile"; + viz.center = data.context().getCenter(); + viz.width = 3.0; + }); + layout.row("second").el(XYTime.class, (viz, data) -> { + viz.title = "CO₂ Emissions"; + viz.description = "per day. Be aware that CO2 values are provided in the simulation sample size!"; + viz.height = 12.0; + viz.file = data.compute(KelheimOfflineAirPollutionAnalysisByEngineInformation.class, "emissions_grid_per_day.xyt.csv", new String[0]); + }); + layout.row("third") + .el(XYTime.class, (viz, data) -> { + viz.title = "CO₂ Emissions"; + viz.description = "per hour. Be aware that CO2 values are provided in the simulation sample size!"; + viz.height = 12.; + viz.file = data.compute(KelheimOfflineAirPollutionAnalysisByEngineInformation.class, "emissions_grid_per_hour.csv"); + }); + } +} diff --git a/src/main/java/org/matsim/analysis/emissions/KelheimOfflineAirPollutionAnalysisByEngineInformation.java b/src/main/java/org/matsim/analysis/emissions/KelheimOfflineAirPollutionAnalysisByEngineInformation.java index ee32cc66..099f658a 100644 --- a/src/main/java/org/matsim/analysis/emissions/KelheimOfflineAirPollutionAnalysisByEngineInformation.java +++ b/src/main/java/org/matsim/analysis/emissions/KelheimOfflineAirPollutionAnalysisByEngineInformation.java @@ -20,43 +20,73 @@ package org.matsim.analysis.emissions; +import it.unimi.dsi.fastutil.objects.Object2DoubleLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2DoubleMap; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.BasicLocation; +import org.matsim.api.core.v01.Coord; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.application.ApplicationUtils; +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.application.options.SampleOptions; +import org.matsim.application.options.ShpOptions; +import org.matsim.contrib.analysis.time.TimeBinMap; import org.matsim.contrib.emissions.*; +import org.matsim.contrib.emissions.analysis.EmissionsByPollutant; import org.matsim.contrib.emissions.analysis.EmissionsOnLinkEventHandler; +import org.matsim.contrib.emissions.analysis.FastEmissionGridAnalyzer; +import org.matsim.contrib.emissions.analysis.Raster; import org.matsim.contrib.emissions.utils.EmissionsConfigGroup; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.AbstractModule; -import org.matsim.core.controler.Injector; import org.matsim.core.events.EventsUtils; import org.matsim.core.events.MatsimEventsReader; -import org.matsim.core.events.algorithms.EventWriterXML; import org.matsim.core.network.NetworkUtils; +import org.matsim.core.network.filter.NetworkFilterManager; +import org.matsim.core.scenario.ProjectionUtils; import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.core.utils.io.IOUtils; import org.matsim.vehicles.*; import picocli.CommandLine; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; +import java.io.*; +import java.nio.file.Files; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.util.*; import java.util.stream.Collectors; -/** - * processes MATSim output leveraging the emission contrib.
- * needs input tables from/according to HBEFA.
- * produces output tables (csv files) that contain emission values per link (per meter) as well as emission events. - * - */ -class KelheimOfflineAirPollutionAnalysisByEngineInformation implements MATSimAppCommand { +@CommandLine.Command( + name = "kelheim-air-pollution", + description = "processes MATSim output leveraging the emission contrib.\n" + + "Needs input tables from/according to HBEFA.\n" + + "Produces output tables (csv files) that contain emission values per link (per meter) as well as emission events.", + mixinStandardHelpOptions = true, showDefaultValues = true +) +@CommandSpec(requireRunDirectory = true, + requireEvents = true, + requireNetwork = true, + produces = { + "emissions_total.csv", "emissions_grid_per_day.xyt.csv", "emissions_per_link.csv", + "emissions_per_link_per_m.csv", + "emissions_grid_per_hour.csv", + "emissions_vehicle_info.csv", + "emissionNetwork.xml.gz" + } +) +public class KelheimOfflineAirPollutionAnalysisByEngineInformation implements MATSimAppCommand { private static final Logger log = LogManager.getLogger(KelheimOfflineAirPollutionAnalysisByEngineInformation.class); @@ -67,20 +97,23 @@ class KelheimOfflineAirPollutionAnalysisByEngineInformation implements MATSimApp private static final String HBEFA_FILE_COLD_AVERAGE = HBEFA_2020_PATH + "r9230ru2n209r30u2fn0c9rn20n2rujkhkjhoewt84202.enc" ; private static final String HBEFA_FILE_WARM_AVERAGE = HBEFA_2020_PATH + "7eff8f308633df1b8ac4d06d05180dd0c5fdf577.enc"; - @CommandLine.Option(names = "--runDir", description = "Path to MATSim output directory containing network, events, ....", required = true) - private String runDirectory; - @CommandLine.Option(names = "--runId", description = "runId of the corresponding MATSim run to analyzed", required = true) - private String runId; - @CommandLine.Option(names = "--output", description = "output directory (must not pre-exist)", required = true) - private String analysisOutputDirectory; + @CommandLine.Mixin + private final InputOptions input = InputOptions.ofCommand(KelheimOfflineAirPollutionAnalysisByEngineInformation.class); + @CommandLine.Mixin + private final OutputOptions output = OutputOptions.ofCommand(KelheimOfflineAirPollutionAnalysisByEngineInformation.class); + @CommandLine.Mixin + private final ShpOptions shp = new ShpOptions(); + @CommandLine.Mixin + private SampleOptions sample; + @CommandLine.Option(names = "--grid-size", description = "Grid size in meter", defaultValue = "250") + private double gridSize; + //dump out all pollutants. to include only a subset of pollutants, adjust! static List pollutants2Output = Arrays.asList(Pollutant.values()); @Override public Integer call() throws Exception { - if (!runDirectory.endsWith("/")) runDirectory = runDirectory + "/"; - if (!analysisOutputDirectory.endsWith("/")) analysisOutputDirectory = analysisOutputDirectory + "/"; Config config = prepareConfig(); Scenario scenario = ScenarioUtils.loadScenario(config); @@ -106,18 +139,14 @@ private void process(Config config, Scenario scenario) throws IOException { // the following is copied from the example and supplemented... //------------------------------------------------------------------------------ - File folder = new File(analysisOutputDirectory); - folder.mkdirs(); - String outputNetworkFile = analysisOutputDirectory + runId + ".emissionNetwork.xml.gz"; - NetworkUtils.writeNetwork(scenario.getNetwork(), outputNetworkFile); + NetworkUtils.writeNetwork(scenario.getNetwork(), output.getPath( "emissionNetwork.xml.gz").toString()); - final String eventsFile = runDirectory + runId + ".output_events.xml.gz"; + final String eventsFile = input.getEventsPath(); - final String emissionEventOutputFile = analysisOutputDirectory + runId + ".emission.events.offline.xml.gz"; - final String linkEmissionAnalysisFile = analysisOutputDirectory + runId + ".emissionsPerLink.csv"; - final String linkEmissionPerMAnalysisFile = analysisOutputDirectory + runId + ".emissionsPerLinkPerM.csv"; - final String vehicleTypeFile = analysisOutputDirectory + runId + ".emissionVehicleInformation.csv"; + final String linkEmissionAnalysisFile = output.getPath("emissions_per_link.csv").toString(); + final String linkEmissionPerMAnalysisFile = output.getPath("emissions_per_link_per_m.csv").toString(); + final String vehicleTypeFile = output.getPath("emissions_vehicle_info.csv").toString(); EventsManager eventsManager = EventsUtils.createEventsManager(); @@ -130,12 +159,6 @@ public void install(){ } }; - com.google.inject.Injector injector = Injector.createInjector(config, module); - EmissionModule emissionModule = injector.getInstance(EmissionModule.class); - - EventWriterXML emissionEventWriter = new EventWriterXML(emissionEventOutputFile); - emissionModule.getEmissionEventsManager().addHandler(emissionEventWriter); - EmissionsOnLinkEventHandler emissionsEventHandler = new EmissionsOnLinkEventHandler(3600); eventsManager.addHandler(emissionsEventHandler); eventsManager.initProcessing(); @@ -145,10 +168,31 @@ public void install(){ log.info("Finish processing..."); eventsManager.finishProcessing(); - log.info("Closing events file..."); - emissionEventWriter.closeFile(); + //we only output values for a subnetwork, if shp is defined. this speeds up vizes. + Network filteredNetwork; + if (shp.isDefined()) { + ShpOptions.Index index = shp.createIndex(ProjectionUtils.getCRS(scenario.getNetwork()), "_"); + + NetworkFilterManager manager = new NetworkFilterManager(scenario.getNetwork(), config.network()); + manager.addLinkFilter(l -> index.contains(l.getCoord())); + + filteredNetwork = manager.applyFilters(); + } else { + filteredNetwork = scenario.getNetwork(); + } + + log.info("write basic output"); + writeTotal(filteredNetwork, emissionsEventHandler); + writeVehicleInfo(scenario, vehicleTypeFile); + log.info("write link output"); + writeLinkOutput(linkEmissionAnalysisFile, linkEmissionPerMAnalysisFile, filteredNetwork, emissionsEventHandler); + + + log.info("write daily raster"); + writeRaster(scenario.getNetwork(), filteredNetwork, config, emissionsEventHandler); + log.info("write hourly raster"); + writeTimeDependentRaster(scenario.getNetwork(), filteredNetwork, config, emissionsEventHandler); - writeOutput(linkEmissionAnalysisFile, linkEmissionPerMAnalysisFile, vehicleTypeFile, scenario, emissionsEventHandler); int totalVehicles = scenario.getVehicles().getVehicles().size(); log.info("Total number of vehicles: " + totalVehicles); @@ -167,10 +211,10 @@ public void install(){ */ private Config prepareConfig() { Config config = ConfigUtils.createConfig(); - config.vehicles().setVehiclesFile( runDirectory + runId + ".output_allVehicles.xml.gz"); - config.network().setInputFile( runDirectory +runId + ".output_network.xml.gz"); - config.transit().setTransitScheduleFile( runDirectory +runId + ".output_transitSchedule.xml.gz"); - config.transit().setVehiclesFile( runDirectory + runId + ".output_transitVehicles.xml.gz"); + config.vehicles().setVehiclesFile(ApplicationUtils.matchInput("allVehicles.xml.gz", input.getRunDirectory()).toAbsolutePath().toString()); + config.network().setInputFile(ApplicationUtils.matchInput("network", input.getRunDirectory()).toAbsolutePath().toString()); + config.transit().setTransitScheduleFile(ApplicationUtils.matchInput("transitSchedule", input.getRunDirectory()).toAbsolutePath().toString()); + config.transit().setVehiclesFile(ApplicationUtils.matchInput("transitVehicles", input.getRunDirectory()).toAbsolutePath().toString()); config.global().setCoordinateSystem("EPSG:25832"); config.plans().setInputFile(null); config.eventsManager().setNumberOfThreads(null); @@ -207,8 +251,10 @@ private void prepareNetwork(Scenario scenario) { } } roadTypeMapping.addHbefaMappings(scenario.getNetwork()); + } + /** * we set all vehicles to average except for KEXI vehicles, i.e. drt. Drt vehicles are set to electric light commercial vehicles. * @param scenario scenario object for which to prepare vehicle types @@ -240,12 +286,11 @@ private void prepareVehicleTypes(Scenario scenario) { * dumps the output. * @param linkEmissionAnalysisFile path including file name and ending (csv) for the output file containing absolute emission values per link * @param linkEmissionPerMAnalysisFile path including file name and ending (csv) for the output file containing emission values per meter, per link - * @param vehicleTypeFileStr including file name and ending (xml) for the output vehicle file - * @param scenario the analyzed scenario + * @param network the network for which utput is createdS * @param emissionsEventHandler handler holding the emission data (from events-processing) * @throws IOException if output can't be written */ - private void writeOutput(String linkEmissionAnalysisFile, String linkEmissionPerMAnalysisFile, String vehicleTypeFileStr, Scenario scenario, EmissionsOnLinkEventHandler emissionsEventHandler) throws IOException { + private void writeLinkOutput(String linkEmissionAnalysisFile, String linkEmissionPerMAnalysisFile, Network network, EmissionsOnLinkEventHandler emissionsEventHandler) throws IOException { log.info("Emission analysis completed."); @@ -277,6 +322,11 @@ private void writeOutput(String linkEmissionAnalysisFile, String linkEmissionPer Map, Map> link2pollutants = emissionsEventHandler.getLink2pollutants(); for (Id linkId : link2pollutants.keySet()) { + + // Link might be filtered + if (!network.getLinks().containsKey(linkId)) + continue; + absolutWriter.write(linkId.toString()); perMeterWriter.write(linkId.toString()); @@ -288,7 +338,7 @@ private void writeOutput(String linkEmissionAnalysisFile, String linkEmissionPer absolutWriter.write(";" + nf.format(emissionValue)); double emissionPerM = Double.NaN; - Link link = scenario.getNetwork().getLinks().get(linkId); + Link link = network.getLinks().get(linkId); if (link != null) { emissionPerM = emissionValue / link.getLength(); } @@ -306,29 +356,216 @@ private void writeOutput(String linkEmissionAnalysisFile, String linkEmissionPer log.info("Output written to " + linkEmissionPerMAnalysisFile); } - { - //dump used vehicle types. in our (Kelheim) case not really needed as we did not change anything. But generally useful. - File vehicleTypeFile = new File(vehicleTypeFileStr); + } - BufferedWriter vehicleTypeWriter = new BufferedWriter(new FileWriter(vehicleTypeFile)); + private static void writeVehicleInfo(Scenario scenario, String vehicleTypeFileStr) throws IOException { + //dump used vehicle types. in our (Kelheim) case not really needed as we did not change anything. But generally useful. + File vehicleTypeFile = new File(vehicleTypeFileStr); - vehicleTypeWriter.write("vehicleId;vehicleType;emissionsConcept"); + BufferedWriter vehicleTypeWriter = new BufferedWriter(new FileWriter(vehicleTypeFile)); + + vehicleTypeWriter.write("vehicleId;vehicleType;emissionsConcept"); + vehicleTypeWriter.newLine(); + + for (Vehicle vehicle : scenario.getVehicles().getVehicles().values()) { + String emissionsConcept = "null"; + if (vehicle.getType().getEngineInformation() != null && VehicleUtils.getHbefaEmissionsConcept(vehicle.getType().getEngineInformation()) != null) { + emissionsConcept = VehicleUtils.getHbefaEmissionsConcept(vehicle.getType().getEngineInformation()); + } + + vehicleTypeWriter.write(vehicle.getId() + ";" + vehicle.getType().getId().toString() + ";" + emissionsConcept); vehicleTypeWriter.newLine(); + } - for (Vehicle vehicle : scenario.getVehicles().getVehicles().values()) { - String emissionsConcept = "null"; - if (vehicle.getType().getEngineInformation() != null && VehicleUtils.getHbefaEmissionsConcept(vehicle.getType().getEngineInformation()) != null) { - emissionsConcept = VehicleUtils.getHbefaEmissionsConcept(vehicle.getType().getEngineInformation()); - } + vehicleTypeWriter.close(); + log.info("Output written to " + vehicleTypeFileStr); + } + + private void writeTotal(Network network, EmissionsOnLinkEventHandler emissionsEventHandler) { + + Object2DoubleMap sum = new Object2DoubleLinkedOpenHashMap<>(); + + DecimalFormat simple = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); + simple.setMaximumFractionDigits(2); + simple.setMaximumIntegerDigits(5); + + DecimalFormat scientific = new DecimalFormat("0.###E0", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); + + for (Map.Entry, Map> e : emissionsEventHandler.getLink2pollutants().entrySet()) { + + if (!network.getLinks().containsKey(e.getKey())) + continue; + for (Map.Entry p : e.getValue().entrySet()) { + sum.mergeDouble(p.getKey(), p.getValue(), Double::sum); + } + } - vehicleTypeWriter.write(vehicle.getId() + ";" + vehicle.getType().getId().toString() + ";" + emissionsConcept); - vehicleTypeWriter.newLine(); + try (CSVPrinter total = new CSVPrinter(Files.newBufferedWriter(output.getPath("emissions_total.csv")), CSVFormat.DEFAULT)) { + + total.printRecord("Pollutant", "kg"); + for (Pollutant p : Pollutant.values()) { + double val = (sum.getDouble(p) / sample.getSample()) / 1000; + total.printRecord(p, val < 100_000 && val > 100 ? simple.format(val) : scientific.format(val)); } - vehicleTypeWriter.close(); - log.info("Output written to " + vehicleTypeFileStr); + } catch (IOException e) { + throw new UncheckedIOException(e); } } + /** + * Creates the data for the XY-Time plot. The time is fixed and the data is summarized over the run. + * Currently only the CO2_Total Values is printed because Simwrapper can handle only one value. + */ + //ts, april 24: + // this method produces (x,y,t) for the full network = entire germany, which is too big to be loaded into simwrapper. + //we can not feed a filtered network, because otherwise exceptions are thrown because the rastering methods find links which are not in the filtered network + //so we need to do some stupid filtering afterwards, which means that we produce and calculate more data than we dump out.... + private void writeRaster(Network fullNetwork, Network filteredNetwork, Config config, EmissionsOnLinkEventHandler emissionsEventHandler) { + + + + Map rasterMap = FastEmissionGridAnalyzer.processHandlerEmissions(emissionsEventHandler.getLink2pollutants(), fullNetwork, gridSize, 20); + + Raster raster = rasterMap.values().stream().findFirst().orElseThrow(); + + try (CSVPrinter printer = new CSVPrinter(Files.newBufferedWriter(output.getPath("emissions_grid_per_day.xyt.csv")), + CSVFormat.DEFAULT.builder().setCommentMarker('#').build())) { + + String crs = ProjectionUtils.getCRS(fullNetwork); + if (crs == null) + crs = config.network().getInputCRS(); + if (crs == null) + crs = config.global().getCoordinateSystem(); + + // print coordinate system + printer.printComment(crs); + + // print header + printer.print("time"); + printer.print("x"); + printer.print("y"); + + printer.print("value"); + + printer.println(); + + Set coords = filteredNetwork.getNodes().values().stream() + .map(BasicLocation::getCoord) + .collect(Collectors.toSet()); + Double minX = coords.stream().map(coord -> coord.getX()) + .min(Double::compare).orElse(Double.NEGATIVE_INFINITY); + Double maxX = coords.stream().map(coord -> coord.getX()) + .max(Double::compare).orElse(Double.POSITIVE_INFINITY); + Double minY = coords.stream().map(coord -> coord.getY()) + .min(Double::compare).orElse(Double.NEGATIVE_INFINITY); + Double maxY = coords.stream().map(coord -> coord.getY()) + .max(Double::compare).orElse(Double.POSITIVE_INFINITY); + + //we only want to print raster data for the bounding box of the filtered network + for (int xi = raster.getXIndex(minX); xi <= raster.getXIndex(maxX); xi++) { + for (int yi = raster.getYIndex(minY); yi < raster.getYIndex(maxY); yi++) { + + Coord coord = raster.getCoordForIndex(xi, yi); + + printer.print(0.0); + printer.print(coord.getX()); + printer.print(coord.getY()); + + double value = rasterMap.get(Pollutant.CO2_TOTAL).getValueByIndex(xi, yi); + printer.print(value); + + printer.println(); + } + } + + } catch (IOException e) { + log.error("Error writing results", e); + } + } + + //ts, april 24: + // this method produces (x,y,t) for the full network = entire germany, which is too big to be loaded into simwrapper. + //we can not feed a filtered network, because otherwise exceptions are thrown because the rastering methods find links which are not in the filtered network + //so we need to do some stupid filtering afterwards, which means that we produce and calculate more data than we dump out.... + private void writeTimeDependentRaster(Network fullNetwork, Network filteredNetwork, Config config, EmissionsOnLinkEventHandler emissionsEventHandler) { + + //later we print C02_total only. so we pass corresponding data into the rasterization - in order to save resources (i had RAM problems before) + Set otherPollutants = new HashSet<>(pollutants2Output); + otherPollutants.remove(Pollutant.CO2_TOTAL); + TimeBinMap, EmissionsByPollutant>> handlerTimeBinMap = emissionsEventHandler.getTimeBins(); + for (TimeBinMap.TimeBin, EmissionsByPollutant>> perLink : handlerTimeBinMap.getTimeBins()) { + Double time = perLink.getStartTime(); + for (Map.Entry, EmissionsByPollutant> emissionsByPollutantEntry : perLink.getValue().entrySet()) { + otherPollutants.forEach(pollutant -> emissionsByPollutantEntry.getValue().getEmissions().remove(pollutant)); + } + } + + TimeBinMap> timeBinMap = FastEmissionGridAnalyzer.processHandlerEmissionsPerTimeBin(handlerTimeBinMap, fullNetwork, gridSize, 20); + + Map firstBin = timeBinMap.getTimeBin(timeBinMap.getStartTime()).getValue(); + + Raster raster = firstBin.values().stream().findFirst().orElseThrow(); + + try (CSVPrinter printer = new CSVPrinter(IOUtils.getBufferedWriter(output.getPath("emissions_grid_per_hour.csv").toString()), + CSVFormat.DEFAULT.builder().setCommentMarker('#').build())) { + + String crs = ProjectionUtils.getCRS(fullNetwork); + if (crs == null) + crs = config.network().getInputCRS(); + if (crs == null) + crs = config.global().getCoordinateSystem(); + + // print coordinate system + printer.printComment(crs); + + // print header + printer.print("time"); + printer.print("x"); + printer.print("y"); + + printer.print("value"); + + printer.println(); + + Set coords = filteredNetwork.getNodes().values().stream() + .map(BasicLocation::getCoord) + .collect(Collectors.toSet()); + Double minX = coords.stream().map(coord -> coord.getX()) + .min(Double::compare).orElse(Double.NEGATIVE_INFINITY); + Double maxX = coords.stream().map(coord -> coord.getX()) + .max(Double::compare).orElse(Double.POSITIVE_INFINITY); + Double minY = coords.stream().map(coord -> coord.getY()) + .min(Double::compare).orElse(Double.NEGATIVE_INFINITY); + Double maxY = coords.stream().map(coord -> coord.getY()) + .max(Double::compare).orElse(Double.POSITIVE_INFINITY); + + //we only want to print raster data for the bounding box of the filtered network + for (int xi = raster.getXIndex(minX); xi <= raster.getXIndex(maxX); xi++) { + for (int yi = raster.getYIndex(minY); yi < raster.getYIndex(maxY); yi++) { + for (TimeBinMap.TimeBin> timeBin : timeBinMap.getTimeBins()) { + + Coord coord = raster.getCoordForIndex(xi, yi); + double value = timeBin.getValue().get(Pollutant.CO2_TOTAL).getValueByIndex(xi, yi); + +// if (value == 0) +// continue; + + printer.print(timeBin.getStartTime()); + printer.print(coord.getX()); + printer.print(coord.getY()); + + printer.print(value); + + printer.println(); + } + } + } + + } catch (IOException e) { + log.error("Error writing results", e); + } + + } } diff --git a/src/main/resources/META-INF/services/org.matsim.simwrapper.DashboardProvider b/src/main/resources/META-INF/services/org.matsim.simwrapper.DashboardProvider index f64bb71e..939a5edd 100644 --- a/src/main/resources/META-INF/services/org.matsim.simwrapper.DashboardProvider +++ b/src/main/resources/META-INF/services/org.matsim.simwrapper.DashboardProvider @@ -1 +1 @@ -org.matsim.dashboards.KelheimDashboardProvider \ No newline at end of file +org.matsim.analysis.KelheimDashboardProvider \ No newline at end of file From 3b23169af83ebfc428ae6adcbc59ea861c974e85 Mon Sep 17 00:00:00 2001 From: tschlenther Date: Tue, 30 Apr 2024 15:38:53 +0200 Subject: [PATCH 2/2] Make fare customizable via cmd input (#73) authored-by: Chengqi Lu <43133404+luchengqi7@users.noreply.github.com> --- .../java/org/matsim/drtFare/KelheimDrtFareModule.java | 8 ++++++-- .../java/org/matsim/run/RunKelheimRealDrtDemands.java | 2 +- src/main/java/org/matsim/run/RunKelheimScenario.java | 8 +++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/matsim/drtFare/KelheimDrtFareModule.java b/src/main/java/org/matsim/drtFare/KelheimDrtFareModule.java index 4b7b4e16..f0171ddc 100644 --- a/src/main/java/org/matsim/drtFare/KelheimDrtFareModule.java +++ b/src/main/java/org/matsim/drtFare/KelheimDrtFareModule.java @@ -11,18 +11,22 @@ public class KelheimDrtFareModule extends AbstractDvrpModeModule { private final DrtConfigGroup drtCfg; private final Network network; private final double avFare; + private final double baseFare; + private final double surcharge; - public KelheimDrtFareModule(DrtConfigGroup drtCfg, Network network, double avFare) { + public KelheimDrtFareModule(DrtConfigGroup drtCfg, Network network, double avFare, double baseFare, double surcharge) { super(drtCfg.getMode()); this.drtCfg = drtCfg; this.network = network; this.avFare = avFare; + this.baseFare = baseFare; + this.surcharge = surcharge; } @Override public void install() { // Default pricing scheme - KelheimDrtFareParams kelheimDrtFareParams = new KelheimDrtFareParams(2.0, 1.0, getMode()); + KelheimDrtFareParams kelheimDrtFareParams = new KelheimDrtFareParams(baseFare, surcharge, getMode()); kelheimDrtFareParams.setShapeFile("https://svn.vsp.tu-berlin.de/repos/public-svn/matsim/scenarios/countries/de/kelheim/shp/KEXI-fare-shp/DrtFareZonalSystem2.shp"); // Special price for Autonomous vehicles diff --git a/src/main/java/org/matsim/run/RunKelheimRealDrtDemands.java b/src/main/java/org/matsim/run/RunKelheimRealDrtDemands.java index 74b805d4..9925c3f2 100644 --- a/src/main/java/org/matsim/run/RunKelheimRealDrtDemands.java +++ b/src/main/java/org/matsim/run/RunKelheimRealDrtDemands.java @@ -83,7 +83,7 @@ public Integer call() throws Exception { controler.configureQSimComponents(DvrpQSimComponents.activateAllModes(MultiModeDrtConfigGroup.get(config))); MultiModeDrtConfigGroup multiModeDrtConfig = ConfigUtils.addOrGetModule(config, MultiModeDrtConfigGroup.class); for (DrtConfigGroup drtCfg : multiModeDrtConfig.getModalElements()) { - controler.addOverridingModule(new KelheimDrtFareModule(drtCfg, network, avFare)); + controler.addOverridingModule(new KelheimDrtFareModule(drtCfg, network, avFare, 2.0, 1.0)); } controler.run(); } diff --git a/src/main/java/org/matsim/run/RunKelheimScenario.java b/src/main/java/org/matsim/run/RunKelheimScenario.java index 6a8d4dcd..b1c9644e 100644 --- a/src/main/java/org/matsim/run/RunKelheimScenario.java +++ b/src/main/java/org/matsim/run/RunKelheimScenario.java @@ -113,6 +113,12 @@ public class RunKelheimScenario extends MATSimApplication { @CommandLine.Option(names = "--plans", defaultValue = "", description = "Use different input plans") private String planOrigin; + @CommandLine.Option(names = "--base-fare", defaultValue = "2.0", description = "Base fare of KEXI trip") + private double baseFare; + + @CommandLine.Option(names = "--surcharge", defaultValue = "1.0", description = "Surcharge of KEXI trip from / to train station") + private double surcharge; + public RunKelheimScenario(@Nullable Config config) { super(config); } @@ -337,7 +343,7 @@ public void handleEvent(PersonDepartureEvent event) { maxSpeed)); for (DrtConfigGroup drtCfg : multiModeDrtConfig.getModalElements()) { - controler.addOverridingModule(new KelheimDrtFareModule(drtCfg, network, avFare)); + controler.addOverridingModule(new KelheimDrtFareModule(drtCfg, network, avFare, baseFare, surcharge)); } //controler.addOverridingModule(new DrtEstimatorModule());