From ad529e0ea2edfadcf773173cdace21274f9bdf80 Mon Sep 17 00:00:00 2001 From: Valentin LE BESCOND Date: Mon, 21 Mar 2022 18:16:03 +0100 Subject: [PATCH 01/10] add emissions computation and analysis --- .../core/scenario/cutter/ScenarioWriter.java | 5 + .../eqasim/ile_de_france/RunSimulation.java | 17 +++ .../emissions/EmissionsByPolluant.java | 42 ++++++ .../EmissionsOnLinkEventsHandler.java | 71 ++++++++++ .../emissions/OsmHbefaMapping.java | 133 ++++++++++++++++++ .../emissions/RunComputeEmissionsEvents.java | 85 +++++++++++ .../emissions/RunComputeEmissionsGrid.java | 58 ++++++++ .../emissions/RunExportEmissionsNetwork.java | 107 ++++++++++++++ pom.xml | 6 + 9 files changed, 524 insertions(+) create mode 100644 ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/EmissionsByPolluant.java create mode 100644 ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/EmissionsOnLinkEventsHandler.java create mode 100644 ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/OsmHbefaMapping.java create mode 100644 ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunComputeEmissionsEvents.java create mode 100644 ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunComputeEmissionsGrid.java create mode 100644 ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunExportEmissionsNetwork.java diff --git a/core/src/main/java/org/eqasim/core/scenario/cutter/ScenarioWriter.java b/core/src/main/java/org/eqasim/core/scenario/cutter/ScenarioWriter.java index c2ee4bb80..0ff309db1 100644 --- a/core/src/main/java/org/eqasim/core/scenario/cutter/ScenarioWriter.java +++ b/core/src/main/java/org/eqasim/core/scenario/cutter/ScenarioWriter.java @@ -42,6 +42,11 @@ public void run(File outputDirectory) { .writeFile(new File(outputDirectory, prefix + "transit_schedule.xml.gz").toString()); new MatsimVehicleWriter(scenario.getTransitVehicles()) .writeFile(new File(outputDirectory, prefix + "transit_vehicles.xml.gz").toString()); + + if (config.vehicles().getVehiclesFile() != null) { + new MatsimVehicleWriter(scenario.getVehicles()) + .writeFile(new File(outputDirectory, prefix + "vehicles.xml.gz").toString()); + } } diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java index a1af9b3e5..36b65fd6e 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java @@ -3,13 +3,20 @@ import org.eqasim.core.simulation.analysis.EqasimAnalysisModule; import org.eqasim.core.simulation.mode_choice.EqasimModeChoiceModule; import org.eqasim.ile_de_france.mode_choice.IDFModeChoiceModule; +import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.population.Person; import org.matsim.core.config.CommandLine; import org.matsim.core.config.CommandLine.ConfigurationException; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.Controler; import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.vehicles.Vehicle; +import org.matsim.vehicles.VehicleUtils; + +import java.util.HashMap; +import java.util.Map; public class RunSimulation { static public void main(String[] args) throws ConfigurationException { @@ -26,6 +33,16 @@ static public void main(String[] args) throws ConfigurationException { configurator.configureScenario(scenario); ScenarioUtils.loadScenario(scenario); + // if there is a vehicles file defined in config, manually assign them to their agents + if (config.vehicles().getVehiclesFile() != null) { + for (Person person : scenario.getPopulation().getPersons().values()) { + Id vehicleId = Id.createVehicleId(person.getId()); + Map> modeVehicle = new HashMap<>(); + modeVehicle.put("car", vehicleId); + VehicleUtils.insertVehicleIdsIntoAttributes(person, modeVehicle); + } + } + Controler controller = new Controler(scenario); configurator.configureController(controller); controller.addOverridingModule(new EqasimAnalysisModule()); diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/EmissionsByPolluant.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/EmissionsByPolluant.java new file mode 100644 index 000000000..a229bd10a --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/EmissionsByPolluant.java @@ -0,0 +1,42 @@ +package org.eqasim.ile_de_france.emissions; +// this is a modified copy of a private contribs.emissions class +// TODO: will need to be updated after the matsim 14 release to profit from https://github.com/matsim-org/matsim-libs/pull/1859 + +import org.matsim.contrib.emissions.Pollutant; + +import java.util.HashMap; +import java.util.Map; + +/** + * Sums up emissions by pollutant. Basically wraps a hash map but is here for better + * readability of org.matsim.contrib.emissions.analysis.EmissionsOnLinkEventHandler + */ +class EmissionsByPollutant { + // The EmissionsByPollutant potentially adds up the same emissions coming from cold and warm. Thus, this cannot be combined into the enum approach + // without some thinking. kai, jan'20 + // yyyy todo I think that this now can be done. kai, jan'20 + + private final Map emissionByPollutant; + + EmissionsByPollutant(Map emissions) { + this.emissionByPollutant = emissions; + } + + void addEmissions( Map emissions ) { + emissions.forEach(this::addEmission); + } + + double addEmission(Pollutant pollutant, double value) { + return emissionByPollutant.merge(pollutant, value, Double::sum); + } + + Map getEmissions() { + return emissionByPollutant; + } + + double getEmission(Pollutant pollutant) { + return emissionByPollutant.get(pollutant); + } + + +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/EmissionsOnLinkEventsHandler.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/EmissionsOnLinkEventsHandler.java new file mode 100644 index 000000000..547b0591a --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/EmissionsOnLinkEventsHandler.java @@ -0,0 +1,71 @@ +package org.eqasim.ile_de_france.emissions; +// this is a modified copy of a private contribs.emissions class +// TODO: will need to be updated after the matsim 14 release to profit from https://github.com/matsim-org/matsim-libs/pull/1859 + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.contrib.analysis.time.TimeBinMap; +import org.matsim.contrib.emissions.events.ColdEmissionEvent; +import org.matsim.contrib.emissions.events.ColdEmissionEventHandler; +import org.matsim.contrib.emissions.events.WarmEmissionEvent; +import org.matsim.contrib.emissions.events.WarmEmissionEventHandler; +import org.matsim.contrib.emissions.Pollutant; + +import java.util.HashMap; +import java.util.Map; + +/** + * Collects Warm- and Cold-Emission-Events by time bin and by link-id + */ +class EmissionsOnLinkEventHandler implements WarmEmissionEventHandler, ColdEmissionEventHandler { + + private final TimeBinMap, EmissionsByPollutant>> timeBins; + + EmissionsOnLinkEventHandler(double timeBinSizeInSeconds) { + + this.timeBins = new TimeBinMap<>(timeBinSizeInSeconds); + } + + /** + * Yields collected emissions + * + * @return Collected emissions by time bin and by link id + */ + TimeBinMap, EmissionsByPollutant>> getTimeBins() { + return timeBins; + } + + @Override + public void reset(int iteration) { + timeBins.clear(); + } + + @Override + public void handleEvent(WarmEmissionEvent event) { + Map map = new HashMap<>() ; + for( Map.Entry entry : event.getWarmEmissions().entrySet() ){ + map.put( entry.getKey(), entry.getValue() ) ; + } + handleEmissionEvent(event.getTime(), event.getLinkId(), map ); + } + + @Override + public void handleEvent(ColdEmissionEvent event) { + + handleEmissionEvent(event.getTime(), event.getLinkId(), event.getColdEmissions()); + } + + private void handleEmissionEvent(double time, Id linkId, Map emissions) { + + TimeBinMap.TimeBin, EmissionsByPollutant>> currentBin = timeBins.getTimeBin(time); + + if (!currentBin.hasValue()){ + currentBin.setValue( new HashMap<>() ); + } + if (!currentBin.getValue().containsKey(linkId)){ + currentBin.getValue().put( linkId, new EmissionsByPollutant( new HashMap<>( emissions ) ) ); + } else{ + currentBin.getValue().get( linkId ).addEmissions( emissions ); + } + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/OsmHbefaMapping.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/OsmHbefaMapping.java new file mode 100644 index 000000000..6f366107b --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/OsmHbefaMapping.java @@ -0,0 +1,133 @@ +package org.eqasim.ile_de_france.emissions; +// this is a modified copy of a private contribs.emissions class +// TODO: will need to be updated after the matsim 14 release to profit from https://github.com/matsim-org/matsim-libs/pull/1859 + +import java.util.HashMap; +import java.util.Map; + +import org.matsim.api.core.v01.network.Link; + +import com.google.inject.Provides; +import org.matsim.api.core.v01.network.Network; +import org.matsim.contrib.emissions.EmissionUtils; + +/** + * Created by molloyj on 01.12.2017. + * + * + * handled OSM road types: + * motorway,trunk,primary,secondary, tertiary, unclassified,residential,service + * motorway_link, trunk_link,primary_link, secondary_link + * tertiary_link, living_street, pedestrian,track,road + * + * Hbefa categories and respective speeds + * URB/MW-Nat./80 - 130 + * URB/MW-City/60 - 110 + * URB/Trunk-Nat./70 - 110 + * URB/Trunk-City/50 - 90 + * URB/Distr/50 - 80 + * URB/Local/50 - 60 + * URB/Access/30 - 50 + * + * Conversions from OSM to hbefa types + * motorway;MW + * primary;Trunk + * secondary;Distr + * tertiary;Local + * residential;Access + * living;Access + */ + +public class OsmHbefaMapping { + private static final int MAX_SPEED = 130; + private static final String OSM_HIGHWAY_TAG = "osm:way:highway"; + Map hbfeaMap = new HashMap<>(); + + public static class Hbefa { + String name; + int min; + int max; + + public Hbefa(String name, int min, int max) { + this.name = name; + this.min = min; + this.max = max; + } + } + + public void addHbefaMappings(Network network) { + for (Link link : network.getLinks().values()) { + String hbefaString = determineHebfaType(link); + if (hbefaString != null) { + EmissionUtils.setHbefaRoadType(link, hbefaString); + } + } + } + + @Provides + public static OsmHbefaMapping build() { + OsmHbefaMapping mapping = new OsmHbefaMapping(); + mapping.put("motorway-Nat.", new Hbefa("MW-Nat.",80,130)); + mapping.put("motorway", new Hbefa("MW-City",60,90)); + mapping.put("primary-Nat.", new Hbefa("Trunk-Nat.",80,110)); + mapping.put("primary", new Hbefa("Trunk-City",50,80)); + mapping.put("trunk", new Hbefa("Trunk-City",50,80)); + mapping.put("secondary", new Hbefa("Distr",50,80)); + mapping.put("tertiary", new Hbefa("Local",50,60)); + mapping.put("residential", new Hbefa("Access",30,50)); + mapping.put("service", new Hbefa("Access",30,50)); + mapping.put("living", new Hbefa("Access",30,50)); + + return mapping; + } + + private void put(String s, Hbefa hbefa) { + hbfeaMap.put(s, hbefa); + } + + public String determineHebfaType(Link link) { + String roadType = (String) link.getAttributes().getAttribute(OSM_HIGHWAY_TAG); + String hbefaType = null; + if (roadType != null) { + hbefaType = getHEBFAtype(roadType,link.getFreespeed()); + } + return hbefaType; + + } + + private String getHEBFAtype(String roadType, double freeVelocity) { + + + String[] ss = roadType.split("_"); //want to remove + String type = ss[0]; + + //TODO: could make distinction between national and city, based on shapefile, or regions. + double freeVelocity_kmh = freeVelocity * 3.6; + + if (type.equals("unclassified") || type.equals("road")) { + if (freeVelocity_kmh <= 50) type = "living"; + else if (freeVelocity_kmh == 60) type = "tertiary"; + else if (freeVelocity_kmh == 70) type = "secondary"; + else if (freeVelocity_kmh <= 90) type = "primary"; + else type = "motorway"; + } + + //specify that if speed > 90 and primary or motorway, then Nat. + if (type.equals("motorway") || type.equals("primary") && freeVelocity_kmh >= 90) { + type += "-Nat."; + } + if (hbfeaMap.get(type) == null) { + return null; + //throw new RuntimeException("'"+ type +"' not in hbefa map"); + } + int min_speed = hbfeaMap.get(type).min; + int max_speed = hbfeaMap.get(type).max; + int capped_speed = (int) Math.min(Math.max(min_speed, freeVelocity_kmh), max_speed); + + return "URB/" + hbfeaMap.get(type).name + "/" + capped_speed; + + + } + + +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunComputeEmissionsEvents.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunComputeEmissionsEvents.java new file mode 100644 index 000000000..2a5a2830b --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunComputeEmissionsEvents.java @@ -0,0 +1,85 @@ +package org.eqasim.ile_de_france.emissions; + +import org.apache.commons.lang3.ArrayUtils; +import org.eqasim.ile_de_france.IDFConfigurator; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.network.NetworkWriter; +import org.matsim.contrib.emissions.EmissionModule; +import org.matsim.contrib.emissions.utils.EmissionsConfigGroup; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.config.CommandLine; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigGroup; +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.scenario.ScenarioUtils; + +// TODO: will need to be updated after the matsim 14 release to profit from https://github.com/matsim-org/matsim-libs/pull/1859 + +public class RunComputeEmissionsEvents { + + public static void main(String[] args) throws CommandLine.ConfigurationException { + + CommandLine cmd = new CommandLine.Builder(args) // + .requireOptions("config-path", "hbefa-cold-avg", "hbefa-hot-avg") // + .allowOptions("hbefa-cold-detailed", "hbefa-hot-detailed") + .build(); + + ConfigGroup[] configGroups = ArrayUtils.addAll(new IDFConfigurator().getConfigGroups(), new EmissionsConfigGroup()); + + Config config = ConfigUtils.loadConfig(cmd.getOptionStrict("config-path"), configGroups); + cmd.applyConfiguration(config); + + EmissionsConfigGroup emissionsConfig = (EmissionsConfigGroup) config.getModule("emissions"); + emissionsConfig.setHbefaVehicleDescriptionSource(EmissionsConfigGroup.HbefaVehicleDescriptionSource.asEngineInformationAttributes); + emissionsConfig.setHbefaRoadTypeSource(EmissionsConfigGroup.HbefaRoadTypeSource.fromLinkAttributes); + emissionsConfig.setDetailedVsAverageLookupBehavior( + EmissionsConfigGroup.DetailedVsAverageLookupBehavior.tryDetailedThenTechnologyAverageThenAverageTable); + emissionsConfig.setNonScenarioVehicles(EmissionsConfigGroup.NonScenarioVehicles.abort); + emissionsConfig.setHbefaTableConsistencyCheckingLevel(EmissionsConfigGroup.HbefaTableConsistencyCheckingLevel.consistent); + + emissionsConfig.setAverageColdEmissionFactorsFile(cmd.getOptionStrict("hbefa-cold-avg")); + emissionsConfig.setAverageWarmEmissionFactorsFile(cmd.getOptionStrict("hbefa-hot-avg")); + + if (cmd.hasOption("hbefa-cold-detailed") && cmd.hasOption("hbefa-hot-detailed")) { + emissionsConfig.setDetailedColdEmissionFactorsFile(cmd.getOptionStrict("hbefa-cold-detailed")); + emissionsConfig.setDetailedWarmEmissionFactorsFile(cmd.getOptionStrict("hbefa-hot-detailed")); + } + + Scenario scenario = ScenarioUtils.createScenario(config); + ScenarioUtils.loadScenario(scenario); + + OsmHbefaMapping osmHbefaMapping = OsmHbefaMapping.build(); + Network network = scenario.getNetwork(); + osmHbefaMapping.addHbefaMappings(network); + + EventsManager eventsManager = EventsUtils.createEventsManager(); + AbstractModule module = new AbstractModule(){ + @Override + public void install(){ + bind( Scenario.class ).toInstance( scenario ); + bind( EventsManager.class ).toInstance( eventsManager ); + bind( EmissionModule.class ) ; + } + }; + + com.google.inject.Injector injector = Injector.createInjector(config, module ); + EmissionModule emissionModule = injector.getInstance(EmissionModule.class); + + final String outputDirectory = scenario.getConfig().controler().getOutputDirectory() + "/"; + EventWriterXML emissionEventWriter = new EventWriterXML( outputDirectory + "output_emissions_events.xml.gz" ) ; + emissionModule.getEmissionEventsManager().addHandler(emissionEventWriter); + + eventsManager.initProcessing(); + MatsimEventsReader matsimEventsReader = new MatsimEventsReader(eventsManager); + matsimEventsReader.readFile( outputDirectory + "./output_events.xml.gz" ); + eventsManager.finishProcessing(); + + emissionEventWriter.closeFile(); + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunComputeEmissionsGrid.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunComputeEmissionsGrid.java new file mode 100644 index 000000000..1da27d2a5 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunComputeEmissionsGrid.java @@ -0,0 +1,58 @@ +package org.eqasim.ile_de_france.emissions; + +import org.apache.commons.lang3.ArrayUtils; +import org.eqasim.ile_de_france.IDFConfigurator; +import org.locationtech.jts.geom.Geometry; +import org.matsim.api.core.v01.network.Network; +import org.matsim.contrib.emissions.analysis.EmissionGridAnalyzer; +import org.matsim.contrib.emissions.utils.EmissionsConfigGroup; +import org.matsim.core.config.CommandLine; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigGroup; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.network.io.MatsimNetworkReader; +import org.matsim.core.utils.gis.ShapeFileReader; +import org.opengis.feature.simple.SimpleFeature; + +// TODO: will need to be updated after the matsim 14 release to profit from https://github.com/matsim-org/matsim-libs/pull/1859 + +public class RunComputeEmissionsGrid { + + public static void main(String[] args) throws CommandLine.ConfigurationException { + + CommandLine cmd = new CommandLine.Builder(args) // + .requireOptions("config-path", "domain-shp-path") // + .allowOptions("scale-factor", "grid-size", "smooth-radius", "time-bin-size") + .build(); + + ConfigGroup[] configGroups = ArrayUtils.addAll(new IDFConfigurator().getConfigGroups(), new EmissionsConfigGroup()); + + Config config = ConfigUtils.loadConfig(cmd.getOptionStrict("config-path"), configGroups); + cmd.applyConfiguration(config); + final String outputDirectory = config.controler().getOutputDirectory() + "/"; + + Network network = NetworkUtils.createNetwork(); + new MatsimNetworkReader(network).readFile(outputDirectory + "output_network.xml.gz"); + + SimpleFeature analysisFeature = ShapeFileReader.getAllFeatures(cmd.getOptionStrict("domain-shp-path")).iterator().next(); + Geometry analysisGeometry = (Geometry) analysisFeature.getDefaultGeometry(); + + double scaleFactor = Double.parseDouble(cmd.getOption("scale-factor").orElse("1.0")); + int gridSize = Integer.parseInt(cmd.getOption("grid-size").orElse("25")); + int smoothRadius = Integer.parseInt(cmd.getOption("smooth-radius").orElse("50")); + int timeBinSize = Integer.parseInt(cmd.getOption("time-bin-size").orElse("3600")); + + new EmissionGridAnalyzer.Builder() // + .withBounds(analysisGeometry) // + .withNetwork(network) // + .withCountScaleFactor(scaleFactor) // + .withGridSize(gridSize) // + .withSmoothingRadius(smoothRadius) // + .withTimeBinSize(timeBinSize) // + .withGridType(EmissionGridAnalyzer.GridType.Square) // + .build() // + .processToJsonFile(outputDirectory + "output_emissions_events.xml.gz", outputDirectory + "output_emissions.json"); + } + +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunExportEmissionsNetwork.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunExportEmissionsNetwork.java new file mode 100644 index 000000000..c543f01a8 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/emissions/RunExportEmissionsNetwork.java @@ -0,0 +1,107 @@ +package org.eqasim.ile_de_france.emissions; + +import org.apache.commons.lang3.ArrayUtils; +import org.eqasim.ile_de_france.IDFConfigurator; +import org.locationtech.jts.geom.Coordinate; +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.contrib.analysis.time.TimeBinMap; +import org.matsim.contrib.emissions.Pollutant; +import org.matsim.contrib.emissions.events.EmissionEventsReader; +import org.matsim.contrib.emissions.utils.EmissionsConfigGroup; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.config.CommandLine; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigGroup; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.events.EventsUtils; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.network.io.MatsimNetworkReader; +import org.matsim.core.utils.geometry.geotools.MGC; +import org.matsim.core.utils.gis.PolylineFeatureFactory; +import org.matsim.core.utils.gis.ShapeFileWriter; +import org.opengis.feature.simple.SimpleFeature; + +import java.util.*; + +// TODO: will need to be updated after the matsim 14 release to profit from https://github.com/matsim-org/matsim-libs/pull/1859 + +public class RunExportEmissionsNetwork { + + public static void main(String[] args) throws CommandLine.ConfigurationException { + + CommandLine cmd = new CommandLine.Builder(args) // + .requireOptions("config-path") // + .allowOptions("time-bin-size") + .build(); + + ConfigGroup[] configGroups = ArrayUtils.addAll(new IDFConfigurator().getConfigGroups(), new EmissionsConfigGroup()); + + Config config = ConfigUtils.loadConfig(cmd.getOptionStrict("config-path"), configGroups); + cmd.applyConfiguration(config); + final String outputDirectory = config.controler().getOutputDirectory() + "/"; + + int timeBinSize = Integer.parseInt(cmd.getOption("time-bin-size").orElse("3600")); + + EventsManager eventsManager = EventsUtils.createEventsManager(); + EmissionsOnLinkEventHandler handler = new EmissionsOnLinkEventHandler(timeBinSize); + + EmissionEventsReader eventsReader = new EmissionEventsReader(eventsManager); + + eventsManager.addHandler(handler); + eventsManager.initProcessing(); + eventsReader.readFile(outputDirectory + "output_emissions_events.xml.gz"); + eventsManager.finishProcessing(); + + Network network = NetworkUtils.createNetwork(); + new MatsimNetworkReader(network).readFile(outputDirectory + "output_network.xml.gz"); + Map, ? extends Link> links = network.getLinks(); + TimeBinMap, EmissionsByPollutant>> res = handler.getTimeBins(); + Collection features = new LinkedList<>(); + + PolylineFeatureFactory linkFactory = new PolylineFeatureFactory.Builder() // + .setCrs(MGC.getCRS("epsg:2154")).setName("Emissions") // + .addAttribute("link", String.class) // + .addAttribute("time", Integer.class) // + .addAttribute("PM", Double.class) // + .addAttribute("FC", Double.class) // + .addAttribute("CO", Double.class) // + .addAttribute("FC_MJ", Double.class) // + .addAttribute("HC", Double.class) // + .addAttribute("NOx", Double.class) // + .addAttribute("CO2_rep", Double.class) // + .create(); + + for (TimeBinMap.TimeBin, EmissionsByPollutant>> timeBin : res.getTimeBins()) { + int startTime = (int) timeBin.getStartTime(); + Map, EmissionsByPollutant> map = timeBin.getValue(); + for (Map.Entry, EmissionsByPollutant> entry : map.entrySet()) { + Id link_id = entry.getKey(); + Link link = links.get(link_id); + Coordinate fromCoordinate = new Coordinate(link.getFromNode().getCoord().getX(), + link.getFromNode().getCoord().getY()); + Coordinate toCoordinate = new Coordinate(link.getToNode().getCoord().getX(), + link.getToNode().getCoord().getY()); + + List attributes = new ArrayList<>(); + + attributes.add(link_id.toString()); + attributes.add(startTime); + EmissionsByPollutant emissions = entry.getValue(); + Map pollutants = emissions.getEmissions(); + for (Map.Entry pollutant : pollutants.entrySet()) { + attributes.add(pollutant.getValue()); + } + + SimpleFeature feature = linkFactory.createPolyline( // + new Coordinate[] { fromCoordinate, toCoordinate }, // + attributes.toArray(), + null); + + features.add(feature); + } + } + ShapeFileWriter.writeGeometries(features, outputDirectory + "emissions_network.shp"); + } +} diff --git a/pom.xml b/pom.xml index 2ec28baed..b74e60232 100644 --- a/pom.xml +++ b/pom.xml @@ -94,6 +94,12 @@ ${matsim.version} + + org.matsim.contrib + emissions + ${matsim.version} + + junit junit From 29eea4ce4587757ac71ff015a0fa70f344f05dbe Mon Sep 17 00:00:00 2001 From: Valentin LE BESCOND Date: Mon, 28 Mar 2022 16:52:32 +0200 Subject: [PATCH 02/10] move vehicle assignment to IDFConfigurator --- .../eqasim/ile_de_france/IDFConfigurator.java | 22 +++++++++++++++++++ .../eqasim/ile_de_france/RunSimulation.java | 11 +--------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java index af1adafa2..044312764 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java @@ -1,6 +1,28 @@ package org.eqasim.ile_de_france; import org.eqasim.core.simulation.EqasimConfigurator; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.population.Person; +import org.matsim.core.config.Config; +import org.matsim.core.config.groups.QSimConfigGroup; +import org.matsim.vehicles.Vehicle; +import org.matsim.vehicles.VehicleUtils; + +import java.util.HashMap; +import java.util.Map; public class IDFConfigurator extends EqasimConfigurator { + public void adjustScenario(Scenario scenario) { + // if there is a vehicles file defined in config, manually assign them to their agents + Config config = scenario.getConfig(); + if (config.qsim().getVehiclesSource() == QSimConfigGroup.VehiclesSource.fromVehiclesData) { + for (Person person : scenario.getPopulation().getPersons().values()) { + Id vehicleId = Id.createVehicleId(person.getId()); + Map> modeVehicle = new HashMap<>(); + modeVehicle.put("car", vehicleId); + VehicleUtils.insertVehicleIdsIntoAttributes(person, modeVehicle); + } + } + } } diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java index 36b65fd6e..29a38d136 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java @@ -32,16 +32,7 @@ static public void main(String[] args) throws ConfigurationException { Scenario scenario = ScenarioUtils.createScenario(config); configurator.configureScenario(scenario); ScenarioUtils.loadScenario(scenario); - - // if there is a vehicles file defined in config, manually assign them to their agents - if (config.vehicles().getVehiclesFile() != null) { - for (Person person : scenario.getPopulation().getPersons().values()) { - Id vehicleId = Id.createVehicleId(person.getId()); - Map> modeVehicle = new HashMap<>(); - modeVehicle.put("car", vehicleId); - VehicleUtils.insertVehicleIdsIntoAttributes(person, modeVehicle); - } - } + configurator.adjustScenario(scenario); Controler controller = new Controler(scenario); configurator.configureController(controller); From 8b51a6d43ad1c0f22f64c9012d76fc12da185a76 Mon Sep 17 00:00:00 2001 From: Valentin LE BESCOND Date: Mon, 28 Mar 2022 17:32:09 +0200 Subject: [PATCH 03/10] optimize imports --- .../main/java/org/eqasim/ile_de_france/RunSimulation.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java index 29a38d136..2a5dd8362 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java @@ -3,20 +3,13 @@ import org.eqasim.core.simulation.analysis.EqasimAnalysisModule; import org.eqasim.core.simulation.mode_choice.EqasimModeChoiceModule; import org.eqasim.ile_de_france.mode_choice.IDFModeChoiceModule; -import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.population.Person; import org.matsim.core.config.CommandLine; import org.matsim.core.config.CommandLine.ConfigurationException; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.Controler; import org.matsim.core.scenario.ScenarioUtils; -import org.matsim.vehicles.Vehicle; -import org.matsim.vehicles.VehicleUtils; - -import java.util.HashMap; -import java.util.Map; public class RunSimulation { static public void main(String[] args) throws ConfigurationException { From 08ff81e3f6b253318dac57bf00edaa3616dbfbcf Mon Sep 17 00:00:00 2001 From: Valentin LE BESCOND Date: Fri, 1 Apr 2022 16:43:07 +0200 Subject: [PATCH 04/10] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ba259a52..8b9594369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ included in the (note yet determined) next version number. **Development version** +- Add air pollution emissions computation and analysis - Add fixed epsilon functionality - Transform scenario configurators to proper classes (formerly static methods) - Set routing parameter for waiting time in Île-de-France to -1.0 From 530bf311a9b363c1e4d0df555a306bafe54ea206 Mon Sep 17 00:00:00 2001 From: Valentin LE BESCOND Date: Thu, 11 May 2023 09:40:23 +0200 Subject: [PATCH 05/10] avoid error when using the analysis with useScheduleBasedTransport config parameter --- .../analysis/AnalysisOutputListener.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/eqasim/core/simulation/analysis/AnalysisOutputListener.java b/core/src/main/java/org/eqasim/core/simulation/analysis/AnalysisOutputListener.java index 8d50fd40a..09674e353 100644 --- a/core/src/main/java/org/eqasim/core/simulation/analysis/AnalysisOutputListener.java +++ b/core/src/main/java/org/eqasim/core/simulation/analysis/AnalysisOutputListener.java @@ -37,6 +37,7 @@ public class AnalysisOutputListener implements IterationStartsListener, Iteratio private final int analysisInterval; private boolean isAnalysisActive = false; + private boolean doPtAnalysis = true; private final DistanceUnit scenarioDistanceUnit; private final DistanceUnit analysisDistanceUnit; @@ -51,6 +52,9 @@ public AnalysisOutputListener(EqasimConfigGroup config, OutputDirectoryHierarchy this.analysisInterval = config.getAnalysisInterval(); + // pt analysis throws an error when simulating pt in Qsim + this.doPtAnalysis = config.getUseScheduleBasedTransport(); + this.tripAnalysisListener = tripListener; this.legAnalysisListener = legListener; this.ptAnalysisListener = ptListener; @@ -65,7 +69,9 @@ public void notifyIterationStarts(IterationStartsEvent event) { isAnalysisActive = true; event.getServices().getEvents().addHandler(tripAnalysisListener); event.getServices().getEvents().addHandler(legAnalysisListener); - event.getServices().getEvents().addHandler(ptAnalysisListener); + if (this.doPtAnalysis) { + event.getServices().getEvents().addHandler(ptAnalysisListener); + } } } } @@ -84,8 +90,10 @@ public void notifyIterationEnds(IterationEndsEvent event) { new LegWriter(legAnalysisListener.getLegItems(), scenarioDistanceUnit, analysisDistanceUnit) .write(outputDirectory.getIterationFilename(event.getIteration(), LEGS_FILE_NAME)); - new PublicTransportLegWriter(ptAnalysisListener.getTripItems()) - .write(outputDirectory.getIterationFilename(event.getIteration(), PT_FILE_NAME)); + if (this.doPtAnalysis) { + new PublicTransportLegWriter(ptAnalysisListener.getTripItems()) + .write(outputDirectory.getIterationFilename(event.getIteration(), PT_FILE_NAME)); + } } } catch (IOException e) { throw new RuntimeException(e); @@ -99,8 +107,10 @@ public void notifyShutdown(ShutdownEvent event) { new File(outputDirectory.getOutputFilename(TRIPS_FILE_NAME)).toPath()); Files.copy(new File(outputDirectory.getIterationFilename(event.getIteration(), LEGS_FILE_NAME)).toPath(), new File(outputDirectory.getOutputFilename(LEGS_FILE_NAME)).toPath()); - Files.copy(new File(outputDirectory.getIterationFilename(event.getIteration(), PT_FILE_NAME)).toPath(), - new File(outputDirectory.getOutputFilename(PT_FILE_NAME)).toPath()); + if (this.doPtAnalysis) { + Files.copy(new File(outputDirectory.getIterationFilename(event.getIteration(), PT_FILE_NAME)).toPath(), + new File(outputDirectory.getOutputFilename(PT_FILE_NAME)).toPath()); + } } catch (IOException e) { } } From dae8a5915210d5c4420901b632981bea5cbc4b67 Mon Sep 17 00:00:00 2001 From: Valentin LE BESCOND Date: Tue, 21 Nov 2023 06:43:54 +0100 Subject: [PATCH 06/10] Optionally add the motorcycle mode --- .../core/scenario/cutter/ConfigCutter.java | 4 + .../mode_choice/EqasimModeChoiceModule.java | 11 +++ .../parameters/ModeParameters.java | 9 +++ .../MotorcycleUtilityEstimator.java | 54 +++++++++++++ .../predictors/MotorcyclePredictor.java | 41 ++++++++++ .../variables/MotorcycleVariables.java | 16 ++++ .../eqasim/ile_de_france/IDFConfigurator.java | 78 ++++++++++++++++++- .../eqasim/ile_de_france/RunSimulation.java | 6 ++ .../mode_choice/IDFModeAvailability.java | 15 ++++ .../mode_choice/IDFModeChoiceModule.java | 6 ++ .../costs/IDFMotorcycleCostModel.java | 25 ++++++ .../parameters/IDFCostParameters.java | 2 + .../parameters/IDFModeParameters.java | 16 ++++ .../IDFMotorcycleUtilityEstimator.java | 53 +++++++++++++ 14 files changed, 332 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/estimators/MotorcycleUtilityEstimator.java create mode 100644 core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/predictors/MotorcyclePredictor.java create mode 100644 core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/variables/MotorcycleVariables.java create mode 100644 ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/costs/IDFMotorcycleCostModel.java create mode 100644 ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/utilities/estimators/IDFMotorcycleUtilityEstimator.java diff --git a/core/src/main/java/org/eqasim/core/scenario/cutter/ConfigCutter.java b/core/src/main/java/org/eqasim/core/scenario/cutter/ConfigCutter.java index 3514f3da4..1a72a2c78 100644 --- a/core/src/main/java/org/eqasim/core/scenario/cutter/ConfigCutter.java +++ b/core/src/main/java/org/eqasim/core/scenario/cutter/ConfigCutter.java @@ -1,6 +1,7 @@ package org.eqasim.core.scenario.cutter; import org.matsim.core.config.Config; +import org.matsim.core.config.groups.QSimConfigGroup; public class ConfigCutter { private final String prefix; @@ -16,5 +17,8 @@ public void run(Config config) { config.households().setInputFile(prefix + "households.xml.gz"); config.transit().setTransitScheduleFile(prefix + "transit_schedule.xml.gz"); config.transit().setVehiclesFile(prefix + "transit_vehicles.xml.gz"); + if (config.qsim().getVehiclesSource() == QSimConfigGroup.VehiclesSource.fromVehiclesData) { + config.vehicles().setVehiclesFile(prefix + "vehicles.xml.gz"); + } } } diff --git a/core/src/main/java/org/eqasim/core/simulation/mode_choice/EqasimModeChoiceModule.java b/core/src/main/java/org/eqasim/core/simulation/mode_choice/EqasimModeChoiceModule.java index b6bdf3773..85a5027ba 100644 --- a/core/src/main/java/org/eqasim/core/simulation/mode_choice/EqasimModeChoiceModule.java +++ b/core/src/main/java/org/eqasim/core/simulation/mode_choice/EqasimModeChoiceModule.java @@ -16,11 +16,13 @@ import org.eqasim.core.simulation.mode_choice.utilities.UtilityEstimator; import org.eqasim.core.simulation.mode_choice.utilities.estimators.BikeUtilityEstimator; import org.eqasim.core.simulation.mode_choice.utilities.estimators.CarUtilityEstimator; +import org.eqasim.core.simulation.mode_choice.utilities.estimators.MotorcycleUtilityEstimator; import org.eqasim.core.simulation.mode_choice.utilities.estimators.PtUtilityEstimator; import org.eqasim.core.simulation.mode_choice.utilities.estimators.WalkUtilityEstimator; import org.eqasim.core.simulation.mode_choice.utilities.estimators.ZeroUtilityEstimator; import org.eqasim.core.simulation.mode_choice.utilities.predictors.BikePredictor; import org.eqasim.core.simulation.mode_choice.utilities.predictors.CarPredictor; +import org.eqasim.core.simulation.mode_choice.utilities.predictors.MotorcyclePredictor; import org.eqasim.core.simulation.mode_choice.utilities.predictors.PersonPredictor; import org.eqasim.core.simulation.mode_choice.utilities.predictors.PtPredictor; import org.eqasim.core.simulation.mode_choice.utilities.predictors.WalkPredictor; @@ -46,6 +48,7 @@ public class EqasimModeChoiceModule extends AbstractEqasimExtension { public static final String UTILITY_ESTIMATOR_NAME = "EqasimUtilityEstimator"; public static final String CAR_ESTIMATOR_NAME = "CarUtilityEstimator"; + public static final String MOTORCYCLE_ESTIMATOR_NAME = "MotorcycleUtilityEstimator"; public static final String PT_ESTIMATOR_NAME = "PtUtilityEstimator"; public static final String BIKE_ESTIMATOR_NAME = "BikeUtilityEstimator"; public static final String WALK_ESTIMATOR_NAME = "WalkUtilityEstimator"; @@ -67,6 +70,7 @@ protected void installEqasimExtension() { bindTripEstimator(UTILITY_ESTIMATOR_NAME).to(ModalUtilityEstimator.class); bind(CarPredictor.class); + bind(MotorcyclePredictor.class); bind(PtPredictor.class); bind(BikePredictor.class); bind(WalkPredictor.class); @@ -74,6 +78,7 @@ protected void installEqasimExtension() { bindUtilityEstimator(ZERO_ESTIMATOR_NAME).to(ZeroUtilityEstimator.class); bindUtilityEstimator(CAR_ESTIMATOR_NAME).to(CarUtilityEstimator.class); + bindUtilityEstimator(MOTORCYCLE_ESTIMATOR_NAME).to(MotorcycleUtilityEstimator.class); bindUtilityEstimator(PT_ESTIMATOR_NAME).to(PtUtilityEstimator.class); bindUtilityEstimator(BIKE_ESTIMATOR_NAME).to(BikeUtilityEstimator.class); bindUtilityEstimator(WALK_ESTIMATOR_NAME).to(WalkUtilityEstimator.class); @@ -111,6 +116,12 @@ public CostModel provideCarCostModel(Map> factory, E return getCostModel(factory, config, "car"); } + @Provides + @Named("motorcycle") + public CostModel provideMotorcycleCostModel(Map> factory, EqasimConfigGroup config) { + return getCostModel(factory, config, "motorcycle"); + } + @Provides @Named("pt") public CostModel providePtCostModel(Map> factory, EqasimConfigGroup config) { diff --git a/core/src/main/java/org/eqasim/core/simulation/mode_choice/parameters/ModeParameters.java b/core/src/main/java/org/eqasim/core/simulation/mode_choice/parameters/ModeParameters.java index fb1edf678..68d646635 100644 --- a/core/src/main/java/org/eqasim/core/simulation/mode_choice/parameters/ModeParameters.java +++ b/core/src/main/java/org/eqasim/core/simulation/mode_choice/parameters/ModeParameters.java @@ -11,6 +11,14 @@ public class CarParameters { public double constantParkingSearchPenalty_min = 0.0; } + public class MotorcycleParameters { + public double alpha_u = 0.0; + public double betaTravelTime_u_min = 0.0; + + public double constantAccessEgressWalkTime_min = 0.0; + public double constantParkingSearchPenalty_min = 0.0; + } + public class PtParameters { public double alpha_u = 0.0; public double betaLineSwitch_u = 0.0; @@ -36,6 +44,7 @@ public class WalkParameters { public double betaCost_u_MU = 0.0; public final CarParameters car = new CarParameters(); + public final MotorcycleParameters motorcycle = new MotorcycleParameters(); public final PtParameters pt = new PtParameters(); public final BikeParameters bike = new BikeParameters(); public final WalkParameters walk = new WalkParameters(); diff --git a/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/estimators/MotorcycleUtilityEstimator.java b/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/estimators/MotorcycleUtilityEstimator.java new file mode 100644 index 000000000..57b95118d --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/estimators/MotorcycleUtilityEstimator.java @@ -0,0 +1,54 @@ +package org.eqasim.core.simulation.mode_choice.utilities.estimators; + +import com.google.inject.Inject; +import org.eqasim.core.simulation.mode_choice.parameters.ModeParameters; +import org.eqasim.core.simulation.mode_choice.utilities.UtilityEstimator; +import org.eqasim.core.simulation.mode_choice.utilities.predictors.MotorcyclePredictor; +import org.eqasim.core.simulation.mode_choice.utilities.variables.MotorcycleVariables; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; + +import java.util.List; + +public class MotorcycleUtilityEstimator implements UtilityEstimator { + private final ModeParameters parameters; + private final MotorcyclePredictor predictor; + + @Inject + public MotorcycleUtilityEstimator(ModeParameters parameters, MotorcyclePredictor predictor) { + this.parameters = parameters; + this.predictor = predictor; + } + + protected double estimateConstantUtility() { + return parameters.motorcycle.alpha_u; + } + + protected double estimateTravelTimeUtility(MotorcycleVariables variables) { + return parameters.motorcycle.betaTravelTime_u_min * variables.travelTime_min; + } + + protected double estimateAccessEgressTimeUtility(MotorcycleVariables variables) { + return parameters.walk.betaTravelTime_u_min * variables.accessEgressTime_min; + } + + protected double estimateMonetaryCostUtility(MotorcycleVariables variables) { + return parameters.betaCost_u_MU * EstimatorUtils.interaction(variables.euclideanDistance_km, + parameters.referenceEuclideanDistance_km, parameters.lambdaCostEuclideanDistance) * variables.cost_MU; + } + + @Override + public double estimateUtility(Person person, DiscreteModeChoiceTrip trip, List elements) { + MotorcycleVariables variables = predictor.predictVariables(person, trip, elements); + + double utility = 0.0; + + utility += estimateConstantUtility(); + utility += estimateTravelTimeUtility(variables); + utility += estimateAccessEgressTimeUtility(variables); + utility += estimateMonetaryCostUtility(variables); + + return utility; + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/predictors/MotorcyclePredictor.java b/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/predictors/MotorcyclePredictor.java new file mode 100644 index 000000000..cd00acb58 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/predictors/MotorcyclePredictor.java @@ -0,0 +1,41 @@ +package org.eqasim.core.simulation.mode_choice.utilities.predictors; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import org.eqasim.core.simulation.mode_choice.cost.CostModel; +import org.eqasim.core.simulation.mode_choice.parameters.ModeParameters; +import org.eqasim.core.simulation.mode_choice.utilities.variables.MotorcycleVariables; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; + +import java.util.List; + +public class MotorcyclePredictor extends CachedVariablePredictor { + private final CostModel costModel; + private final ModeParameters parameters; + + @Inject + public MotorcyclePredictor(ModeParameters parameters, @Named("motorcycle") CostModel costModel) { + this.costModel = costModel; + this.parameters = parameters; + } + + @Override + public MotorcycleVariables predict(Person person, DiscreteModeChoiceTrip trip, List elements) { + if (elements.size() > 1) { + throw new IllegalStateException("We do not support multi-stage car trips yet."); + } + + Leg leg = (Leg) elements.get(0); + + double travelTime_min = leg.getTravelTime().seconds() / 60.0 + parameters.motorcycle.constantParkingSearchPenalty_min; + double cost_MU = costModel.calculateCost_MU(person, trip, elements); + + double euclideanDistance_km = PredictorUtils.calculateEuclideanDistance_km(trip); + double accessEgressTime_min = parameters.motorcycle.constantAccessEgressWalkTime_min; + + return new MotorcycleVariables(travelTime_min, cost_MU, euclideanDistance_km, accessEgressTime_min); + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/variables/MotorcycleVariables.java b/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/variables/MotorcycleVariables.java new file mode 100644 index 000000000..0f08beaf3 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/variables/MotorcycleVariables.java @@ -0,0 +1,16 @@ +package org.eqasim.core.simulation.mode_choice.utilities.variables; + +public class MotorcycleVariables implements BaseVariables { + final public double travelTime_min; + final public double cost_MU; + final public double euclideanDistance_km; + final public double accessEgressTime_min; + + public MotorcycleVariables(double travelTime_min, double cost_MU, double euclideanDistance_km, + double accessEgressTime_min) { + this.travelTime_min = travelTime_min; + this.cost_MU = cost_MU; + this.euclideanDistance_km = euclideanDistance_km; + this.accessEgressTime_min = accessEgressTime_min; + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java index 044312764..373ed2dbd 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java @@ -1,16 +1,22 @@ package org.eqasim.ile_de_france; +import org.eqasim.core.components.config.EqasimConfigGroup; import org.eqasim.core.simulation.EqasimConfigurator; +import org.eqasim.ile_de_france.mode_choice.IDFModeChoiceModule; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.population.Person; +import org.matsim.contribs.discrete_mode_choice.modules.config.DiscreteModeChoiceConfigGroup; import org.matsim.core.config.Config; +import org.matsim.core.config.groups.PlanCalcScoreConfigGroup; import org.matsim.core.config.groups.QSimConfigGroup; import org.matsim.vehicles.Vehicle; import org.matsim.vehicles.VehicleUtils; -import java.util.HashMap; -import java.util.Map; +import java.util.*; public class IDFConfigurator extends EqasimConfigurator { public void adjustScenario(Scenario scenario) { @@ -18,11 +24,75 @@ public void adjustScenario(Scenario scenario) { Config config = scenario.getConfig(); if (config.qsim().getVehiclesSource() == QSimConfigGroup.VehiclesSource.fromVehiclesData) { for (Person person : scenario.getPopulation().getPersons().values()) { - Id vehicleId = Id.createVehicleId(person.getId()); + Id carId = Id.createVehicleId(person.getId()); + Id motorcycleId = Id.createVehicleId(String.format("m_%s", person.getId())); Map> modeVehicle = new HashMap<>(); - modeVehicle.put("car", vehicleId); + modeVehicle.put("car", carId); + if (person.getAttributes().getAsMap().containsKey("motorcycleAvailability")) { + if (!((String) person.getAttributes().getAttribute("motorcycleAvailability")).equals("none")) { + modeVehicle.put("motorcycle", motorcycleId); + } + } VehicleUtils.insertVehicleIdsIntoAttributes(person, modeVehicle); } } } + + public void adjustScenarioMotorcycle(Scenario scenario) { + // add motorcycle stuff + Config config = scenario.getConfig(); + { // QSim + List qsimMainModes = new ArrayList<>(config.qsim().getMainModes()); + qsimMainModes.add("motorcycle"); + config.qsim().setMainModes(qsimMainModes); + + config.qsim().setLinkDynamics(QSimConfigGroup.LinkDynamics.SeepageQ); + + List qsimSeepModes = new ArrayList<>(config.qsim().getSeepModes()); + qsimSeepModes.add("motorcycle"); + config.qsim().setSeepModes(qsimSeepModes); + } + { // DMC + DiscreteModeChoiceConfigGroup dmcConfig = (DiscreteModeChoiceConfigGroup) config.getModules() + .get(DiscreteModeChoiceConfigGroup.GROUP_NAME); + Collection cachedModes = dmcConfig.getCachedModes(); + cachedModes.add("motorcycle"); + dmcConfig.setCachedModes(cachedModes); + + List vehicleConstraintModes = new ArrayList<>(dmcConfig.getVehicleTourConstraintConfig().getRestrictedModes()); + vehicleConstraintModes.add("motorcycle"); + dmcConfig.getVehicleTourConstraintConfig().setRestrictedModes(vehicleConstraintModes); + } + { // Eqasim + EqasimConfigGroup eqasimConfig = EqasimConfigGroup.get(config); + eqasimConfig.setEstimator(TransportMode.motorcycle, IDFModeChoiceModule.MOTORCYCLE_ESTIMATOR_NAME); + eqasimConfig.setCostModel(TransportMode.motorcycle, IDFModeChoiceModule.MOTORCYCLE_COST_MODEL_NAME); + + eqasimConfig.setUseScheduleBasedTransport(false); + } + { // planCalcScore + PlanCalcScoreConfigGroup.ModeParams modeParameters = new PlanCalcScoreConfigGroup.ModeParams("motorcycle"); + modeParameters.setConstant(0.0); + modeParameters.setMarginalUtilityOfDistance(0.0); + modeParameters.setMonetaryDistanceRate(0.0); + config.planCalcScore().addModeParams(modeParameters); + + config.planCalcScore().setWriteExperiencedPlans(true); + } + { // plansCalcRoute + List networkModes = new ArrayList<>(config.plansCalcRoute().getNetworkModes()); + networkModes.add("motorcycle"); + config.plansCalcRoute().setNetworkModes(networkModes); + } + { // Network + Network network = scenario.getNetwork(); + for (Link link : network.getLinks().values()) { + Set modes = new HashSet<>(link.getAllowedModes()); + if (modes.contains("car")) { + modes.add("motorcycle"); + link.setAllowedModes(modes); + } + } + } + } } diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java index 2a5dd8362..9f4e49553 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/RunSimulation.java @@ -15,6 +15,7 @@ public class RunSimulation { static public void main(String[] args) throws ConfigurationException { CommandLine cmd = new CommandLine.Builder(args) // .requireOptions("config-path") // + .allowOptions("with-motorcycles") .allowPrefixes("mode-choice-parameter", "cost-parameter") // .build(); @@ -27,6 +28,11 @@ static public void main(String[] args) throws ConfigurationException { ScenarioUtils.loadScenario(scenario); configurator.adjustScenario(scenario); + boolean withMotorcycles = cmd.hasOption("with-motorcycles"); + if (withMotorcycles) { + configurator.adjustScenarioMotorcycle(scenario); + } + Controler controller = new Controler(scenario); configurator.configureController(controller); controller.addOverridingModule(new EqasimAnalysisModule()); diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java index b193c2bb6..68afbf049 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java @@ -34,6 +34,21 @@ public Collection getAvailableModes(Person person, List elements) { + return costParameters.motorcycleCost_EUR_km * getInVehicleDistance_km(elements); + } +} diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/parameters/IDFCostParameters.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/parameters/IDFCostParameters.java index 4e2563840..deaa70604 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/parameters/IDFCostParameters.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/parameters/IDFCostParameters.java @@ -4,11 +4,13 @@ public class IDFCostParameters implements ParameterDefinition { public double carCost_EUR_km = 0.0; + public double motorcycleCost_EUR_km = 0.0; public static IDFCostParameters buildDefault() { IDFCostParameters parameters = new IDFCostParameters(); parameters.carCost_EUR_km = 0.15; + parameters.motorcycleCost_EUR_km = 0.15; return parameters; } diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/parameters/IDFModeParameters.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/parameters/IDFModeParameters.java index bf948e053..dab7abf47 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/parameters/IDFModeParameters.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/parameters/IDFModeParameters.java @@ -8,11 +8,17 @@ public class IDFCarParameters { public double betaCrossingUrbanArea; } + public class IDFMotorcycleParameters { + public double betaInsideUrbanArea; + public double betaCrossingUrbanArea; + } + public class IDFBikeParameters { public double betaInsideUrbanArea; } public final IDFCarParameters idfCar = new IDFCarParameters(); + public final IDFMotorcycleParameters idfMotorcycle = new IDFMotorcycleParameters(); public final IDFBikeParameters idfBike = new IDFBikeParameters(); public static IDFModeParameters buildDefault() { @@ -33,6 +39,16 @@ public static IDFModeParameters buildDefault() { parameters.idfCar.betaInsideUrbanArea = -0.5; parameters.idfCar.betaCrossingUrbanArea = -1.0; + // Motorcycle ; copy of Car for now + parameters.motorcycle.alpha_u = 1.35; + parameters.motorcycle.betaTravelTime_u_min = -0.06; + + parameters.motorcycle.constantAccessEgressWalkTime_min = 4.0; + parameters.motorcycle.constantParkingSearchPenalty_min = 4.0; + + parameters.idfMotorcycle.betaInsideUrbanArea = -0.5; + parameters.idfMotorcycle.betaCrossingUrbanArea = -1.0; + // PT parameters.pt.alpha_u = 0.0; parameters.pt.betaLineSwitch_u = -0.17; diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/utilities/estimators/IDFMotorcycleUtilityEstimator.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/utilities/estimators/IDFMotorcycleUtilityEstimator.java new file mode 100644 index 000000000..367eaaa36 --- /dev/null +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/utilities/estimators/IDFMotorcycleUtilityEstimator.java @@ -0,0 +1,53 @@ +package org.eqasim.ile_de_france.mode_choice.utilities.estimators; + +import com.google.inject.Inject; +import org.eqasim.core.simulation.mode_choice.utilities.estimators.MotorcycleUtilityEstimator; +import org.eqasim.core.simulation.mode_choice.utilities.predictors.MotorcyclePredictor; +import org.eqasim.ile_de_france.mode_choice.parameters.IDFModeParameters; +import org.eqasim.ile_de_france.mode_choice.utilities.predictors.IDFSpatialPredictor; +import org.eqasim.ile_de_france.mode_choice.utilities.variables.IDFSpatialVariables; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; + +import java.util.List; + +public class IDFMotorcycleUtilityEstimator extends MotorcycleUtilityEstimator { + private final IDFModeParameters parameters; + private final IDFSpatialPredictor spatialPredictor; + + @Inject + public IDFMotorcycleUtilityEstimator(IDFModeParameters parameters, IDFSpatialPredictor spatialPredictor, + MotorcyclePredictor motorcyclePredictor) { + super(parameters, motorcyclePredictor); + + this.parameters = parameters; + this.spatialPredictor = spatialPredictor; + } + + protected double estimateUrbanUtility(IDFSpatialVariables variables) { + double utility = 0.0; + + if (variables.hasUrbanOrigin && variables.hasUrbanDestination) { + utility += parameters.idfMotorcycle.betaInsideUrbanArea; + } + + if (variables.hasUrbanOrigin || variables.hasUrbanDestination) { + utility += parameters.idfMotorcycle.betaCrossingUrbanArea; + } + + return utility; + } + + @Override + public double estimateUtility(Person person, DiscreteModeChoiceTrip trip, List elements) { + IDFSpatialVariables variables = spatialPredictor.predictVariables(person, trip, elements); + + double utility = 0.0; + + utility += super.estimateUtility(person, trip, elements); + utility += estimateUrbanUtility(variables); + + return utility; + } +} From b5820dd3c91c3b2399ed8feeb0ef832772e6eb94 Mon Sep 17 00:00:00 2001 From: Valentin LE BESCOND Date: Tue, 21 Nov 2023 06:46:46 +0100 Subject: [PATCH 07/10] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b151a0381..49ca521cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ included in the (note yet determined) next version number. **Development version** +- Add the motorcycle mode - Updated to MATSim 14 - Isolated the mode choice model in a standalone runnable script - Fixed LegIndex count between iterations in legs analysis From d9a2144d54c1726bed05bd9feebcdfa602c3f268 Mon Sep 17 00:00:00 2001 From: Valentin LE BESCOND Date: Tue, 21 Nov 2023 06:55:27 +0100 Subject: [PATCH 08/10] handle the absence of the 'motorcycleAvailability' attribute correctly --- .../ile_de_france/mode_choice/IDFModeAvailability.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java index 68afbf049..f0fe866c0 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java @@ -41,8 +41,10 @@ public Collection getAvailableModes(Person person, List Date: Tue, 21 Nov 2023 07:35:54 +0100 Subject: [PATCH 09/10] properly handle the absence of the 'motorcycleAvailability' attribute correctly --- .../eqasim/ile_de_france/mode_choice/IDFModeAvailability.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java index f0fe866c0..a502a5404 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/mode_choice/IDFModeAvailability.java @@ -46,6 +46,9 @@ public Collection getAvailableModes(Person person, List Date: Tue, 21 Nov 2023 07:36:44 +0100 Subject: [PATCH 10/10] remove unrelevant use of setUseScheduleBasedTransport --- .../src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java b/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java index 373ed2dbd..a01a65ea7 100644 --- a/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java +++ b/ile_de_france/src/main/java/org/eqasim/ile_de_france/IDFConfigurator.java @@ -67,8 +67,6 @@ public void adjustScenarioMotorcycle(Scenario scenario) { EqasimConfigGroup eqasimConfig = EqasimConfigGroup.get(config); eqasimConfig.setEstimator(TransportMode.motorcycle, IDFModeChoiceModule.MOTORCYCLE_ESTIMATOR_NAME); eqasimConfig.setCostModel(TransportMode.motorcycle, IDFModeChoiceModule.MOTORCYCLE_COST_MODEL_NAME); - - eqasimConfig.setUseScheduleBasedTransport(false); } { // planCalcScore PlanCalcScoreConfigGroup.ModeParams modeParameters = new PlanCalcScoreConfigGroup.ModeParams("motorcycle");