diff --git a/core/src/main/java/org/eqasim/core/tools/RemovePersonsWithActivityTypes.java b/core/src/main/java/org/eqasim/core/tools/RemovePersonsWithActivityTypes.java index 32e5e2691..c7f00bd57 100644 --- a/core/src/main/java/org/eqasim/core/tools/RemovePersonsWithActivityTypes.java +++ b/core/src/main/java/org/eqasim/core/tools/RemovePersonsWithActivityTypes.java @@ -1,6 +1,7 @@ package org.eqasim.core.tools; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Identifiable; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.population.Activity; @@ -16,7 +17,7 @@ public class RemovePersonsWithActivityTypes { - private static final Logger logger = Logger.getLogger(RemovePersonsWithActivityTypes.class); + private static final Logger logger = LogManager.getLogger(RemovePersonsWithActivityTypes.class); public static void main(String[] args) throws CommandLine.ConfigurationException { CommandLine commandLine = new CommandLine.Builder(args).requireOptions("input-path", "output-path", "activity-types").build(); 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 index 3933674d7..7929341f6 100644 --- 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 @@ -1,8 +1,10 @@ package org.eqasim.ile_de_france.emissions; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.eqasim.ile_de_france.IDFConfigurator; 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.contrib.emissions.EmissionModule; import org.matsim.contrib.emissions.OsmHbefaMapping; @@ -17,6 +19,7 @@ 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.scenario.ScenarioUtils; public class RunComputeEmissionsEvents { @@ -53,6 +56,26 @@ public static void main(String[] args) throws CommandLine.ConfigurationException OsmHbefaMapping osmHbefaMapping = OsmHbefaMapping.build(); Network network = scenario.getNetwork(); + // if the network is from pt2matsim it might not have "type" but "osm:way:highway" attribute instead + for (Link link: network.getLinks().values()) { + String roadTypeAttribute = NetworkUtils.getType(link); + String osmRoadTypeAttribute = (String) link.getAttributes().getAttribute("osm:way:highway"); + if (StringUtils.isBlank(roadTypeAttribute)) { + if (!StringUtils.isBlank(osmRoadTypeAttribute)) { + NetworkUtils.setType(link, osmRoadTypeAttribute); + } + else { // not a road (railway for example) + NetworkUtils.setType(link, "unclassified"); + } + } + // '_link' types are not defined in the OSM mapping, set t undefined + if (NetworkUtils.getType(link).contains("_link")) { + NetworkUtils.setType(link, "unclassified"); + } + if (NetworkUtils.getType(link).equals("living_street")) { + NetworkUtils.setType(link, "living"); + } + } osmHbefaMapping.addHbefaMappings(network); EventsManager eventsManager = EventsUtils.createEventsManager(); 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 index 7c6689ac1..2bf7b473d 100644 --- 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 @@ -34,6 +34,7 @@ public static void main(String[] args) throws CommandLine.ConfigurationException CommandLine cmd = new CommandLine.Builder(args) // .requireOptions("config-path") // .allowOptions("time-bin-size") + .allowOptions("pollutants") .build(); ConfigGroup[] configGroups = ArrayUtils.addAll(new IDFConfigurator().getConfigGroups(), new EmissionsConfigGroup()); @@ -44,6 +45,8 @@ public static void main(String[] args) throws CommandLine.ConfigurationException int timeBinSize = Integer.parseInt(cmd.getOption("time-bin-size").orElse("3600")); + String[] wanted_pollutants = cmd.getOption("pollutants").orElse("PM,CO,NOx").split(","); + EventsManager eventsManager = EventsUtils.createEventsManager(); EmissionsOnLinkEventHandler handler = new EmissionsOnLinkEventHandler(timeBinSize); @@ -59,19 +62,14 @@ public static void main(String[] args) throws CommandLine.ConfigurationException Map, ? extends Link> links = network.getLinks(); TimeBinMap, EmissionsByPollutant>> res = handler.getTimeBins(); Collection features = new LinkedList<>(); - - PolylineFeatureFactory linkFactory = new PolylineFeatureFactory.Builder() // + PolylineFeatureFactory.Builder builder = 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(); + .addAttribute("time", Integer.class); + for (String pollutant: wanted_pollutants) { + builder.addAttribute(pollutant, Double.class); + } + PolylineFeatureFactory linkFactory = builder.create(); for (TimeBinMap.TimeBin, EmissionsByPollutant>> timeBin : res.getTimeBins()) { int startTime = (int) timeBin.getStartTime(); @@ -90,8 +88,15 @@ public static void main(String[] args) throws CommandLine.ConfigurationException attributes.add(startTime); EmissionsByPollutant emissions = entry.getValue(); Map pollutants = emissions.getEmissions(); - for (Map.Entry pollutant : pollutants.entrySet()) { - attributes.add(pollutant.getValue()); + for (String pollutant: wanted_pollutants) { + try { + Pollutant pollutant_key = Pollutant.valueOf(pollutant); + attributes.add(pollutants.getOrDefault(pollutant_key, Double.NaN)); + } + catch (IllegalArgumentException e) { + attributes.add(Double.NaN); + } + } SimpleFeature feature = linkFactory.createPolyline( // diff --git a/ile_de_france/src/test/java/org/eqasim/ile_de_france/TestEmissions.java b/ile_de_france/src/test/java/org/eqasim/ile_de_france/TestEmissions.java new file mode 100644 index 000000000..890a7837a --- /dev/null +++ b/ile_de_france/src/test/java/org/eqasim/ile_de_france/TestEmissions.java @@ -0,0 +1,191 @@ +package org.eqasim.ile_de_france; + +import org.apache.commons.io.FileUtils; +import org.eqasim.core.simulation.EqasimConfigurator; +import org.eqasim.core.simulation.analysis.EqasimAnalysisModule; +import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension; +import org.eqasim.core.simulation.mode_choice.EqasimModeChoiceModule; +import org.eqasim.core.simulation.mode_choice.parameters.ModeParameters; +import org.eqasim.ile_de_france.emissions.RunComputeEmissionsEvents; +import org.eqasim.ile_de_france.emissions.RunExportEmissionsNetwork; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +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.core.config.CommandLine; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.ControlerConfigGroup; +import org.matsim.core.config.groups.QSimConfigGroup; +import org.matsim.core.controler.Controler; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.core.utils.gis.ShapeFileReader; +import org.matsim.examples.ExamplesUtils; +import org.matsim.utils.objectattributes.attributable.Attributes; +import org.matsim.vehicles.*; +import org.opengis.feature.simple.SimpleFeature; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +public class TestEmissions { + + @Before + public void setUp() throws IOException { + URL fixtureUrl = getClass().getResource("/melun"); + FileUtils.copyDirectory(new File(fixtureUrl.getPath()), new File("melun_test/input")); + var coldAverageFile = "sample_41_EFA_ColdStart_vehcat_2020average.csv"; + var coldDetailedFile = "sample_41_EFA_ColdStart_SubSegm_2020detailed.csv"; + var hotAverageFile = "sample_41_EFA_HOT_vehcat_2020average.csv"; + var hotDetailedFile = "sample_41_EFA_HOT_SubSegm_2020detailed.csv"; + for (String file: new String[]{ coldAverageFile, coldDetailedFile, hotAverageFile, hotDetailedFile}) { + Files.copy(ExamplesUtils.class.getResourceAsStream("/test/scenarios/emissions-sampleScenario/" + file), + Paths.get("melun_test/input/", file), + REPLACE_EXISTING); + } + } + + @After + public void tearDown() throws IOException { + FileUtils.deleteDirectory(new File("melun_test")); + } + + private void runMelunSimulation() { + EqasimConfigurator eqasimConfigurator = new EqasimConfigurator(); + Config config = ConfigUtils.loadConfig("melun_test/input/config_emissions.xml", eqasimConfigurator.getConfigGroups()); + ((ControlerConfigGroup) config.getModules().get(ControlerConfigGroup.GROUP_NAME)).setOutputDirectory("melun_test/output"); + + Scenario scenario = ScenarioUtils.createScenario(config); + eqasimConfigurator.configureScenario(scenario); + ScenarioUtils.loadScenario(scenario); + eqasimConfigurator.adjustScenario(scenario); + + + Controler controller = new Controler(scenario); + eqasimConfigurator.configureController(controller); + controller.addOverridingModule(new EqasimModeChoiceModule()); + controller.addOverridingModule(new EqasimAnalysisModule()); + controller.addOverridingModule(new AbstractEqasimExtension() { + @Override + protected void installEqasimExtension() { + bind(ModeParameters.class); + bindModeAvailability("DefaultModeAvailability").toProvider(() -> (person, trips) -> { + Set modes = new HashSet<>(); + modes.add(TransportMode.walk); + modes.add(TransportMode.pt); + modes.add(TransportMode.car); + modes.add(TransportMode.bike); + // Add special mode "car_passenger" if applicable + Boolean isCarPassenger = (Boolean) person.getAttributes().getAttribute("isPassenger"); + if(isCarPassenger) { + modes.add("car_passenger"); + } + return modes; + }).asEagerSingleton(); + } + }); + controller.run(); + } + + private void runCreateVehicles() { + VehicleType testCarType = VehicleUtils.createVehicleType(Id.create("test_car", VehicleType.class)); + testCarType.setLength(7.5); + testCarType.setWidth(1.); + testCarType.setNetworkMode("car"); + Attributes hbefa_attributes = testCarType.getEngineInformation().getAttributes(); + hbefa_attributes.putAttribute("HbefaVehicleCategory", "PASSENGER_CAR"); + hbefa_attributes.putAttribute("HbefaTechnology", "diesel"); + hbefa_attributes.putAttribute("HbefaSizeClass", "<1,4L"); + hbefa_attributes.putAttribute("HbefaEmissionsConcept", "PC diesel Euro-3 (DPF)"); + + Vehicles vehicles = VehicleUtils.createVehiclesContainer(); + vehicles.addVehicleType(testCarType); + + EqasimConfigurator eqasimConfigurator = new EqasimConfigurator(); + Config config = ConfigUtils.loadConfig("melun_test/input/config.xml", eqasimConfigurator.getConfigGroups()); + Scenario scenario = ScenarioUtils.loadScenario(config); + for (Person person: scenario.getPopulation().getPersons().values()) { + Vehicle vehicle = VehicleUtils.createVehicle( + Id.createVehicleId(person.getId().toString()), + testCarType); + vehicles.addVehicle(vehicle); + } + + MatsimVehicleWriter writer = new MatsimVehicleWriter(vehicles); + writer.writeFile("melun_test/input/vehicles.xml"); + } + + private void runModifyConfig() { + Config config = ConfigUtils.loadConfig("melun_test/input/config.xml"); + config.controler().setOutputDirectory("melun_test/output"); + config.qsim().setVehiclesSource(QSimConfigGroup.VehiclesSource.fromVehiclesData); + config.vehicles().setVehiclesFile("vehicles.xml"); + ConfigUtils.writeConfig(config, "melun_test/input/config_emissions.xml"); + } + + private void runModifyNetwork() { + Config config = ConfigUtils.loadConfig("melun_test/input/config.xml"); + Scenario scenario = ScenarioUtils.loadScenario(config); + Network network = scenario.getNetwork(); + for (Link link: network.getLinks().values()) { + // this forces the OSM Mapping code to use URB/Local/50 as it the only thing we have in the sample HBEFA. + NetworkUtils.setType(link, "tertiary"); + link.getAttributes().putAttribute(NetworkUtils.ALLOWED_SPEED, 50 / 3.6); + } + NetworkUtils.writeNetwork(network, "melun_test/input/network.xml.gz"); + } + + private void runMelunEmissions() throws CommandLine.ConfigurationException { + RunComputeEmissionsEvents.main(new String[] { + "--config-path", "melun_test/input/config_emissions.xml", + "--hbefa-cold-avg", "sample_41_EFA_ColdStart_vehcat_2020average.csv", + "--hbefa-hot-avg", "sample_41_EFA_HOT_vehcat_2020average.csv", + "--hbefa-cold-detailed", "sample_41_EFA_ColdStart_SubSegm_2020detailed.csv", + "--hbefa-hot-detailed", "sample_41_EFA_HOT_SubSegm_2020detailed.csv", + }); + + RunExportEmissionsNetwork.main(new String[] { + "--config-path", "melun_test/input/config_emissions.xml", + "--pollutants", "PM,CO,NOx,Unknown", + "--time-bin-size", "3600" + }); + + Collection features = ShapeFileReader.getAllFeatures("melun_test/output/emissions_network.shp"); + assert features.size() == 32999; + SimpleFeature feature = features.stream().filter(f -> + f.getAttribute("link").toString().equals("163994") + & f.getAttribute("time").toString().equals("43200") + ).findFirst().orElse(null); + assert feature != null; + assert feature.getAttribute("PM").equals(0.006847378350421); + assert feature.getAttribute("CO").equals(0.456258730331835); + assert feature.getAttribute("NOx").equals(0.477558671071797); + assert feature.getAttribute("Unknown").equals(Double.NaN); + + // TODO : test RunComputeEmissionsGrid + } + + @Test + public void runTestEmissions() throws CommandLine.ConfigurationException { + runCreateVehicles(); + runModifyConfig(); + runModifyNetwork(); + runMelunSimulation(); + runMelunEmissions(); + } + +} diff --git a/pom.xml b/pom.xml index ff5d9c37b..e0140f8b5 100644 --- a/pom.xml +++ b/pom.xml @@ -100,6 +100,12 @@ ${matsim.version} + + org.matsim + matsim-examples + ${matsim.version} + + junit junit