|
| 1 | +package org.eqasim.core.scenario.cutter; |
| 2 | + |
| 3 | +import org.apache.commons.io.FileUtils; |
| 4 | +import org.eqasim.core.scenario.cutter.extent.ScenarioExtent; |
| 5 | +import org.eqasim.core.scenario.cutter.extent.ShapeScenarioExtent; |
| 6 | +import org.eqasim.core.simulation.EqasimConfigurator; |
| 7 | +import org.eqasim.core.simulation.vdf.VDFConfigGroup; |
| 8 | +import org.eqasim.core.simulation.vdf.engine.VDFEngineConfigGroup; |
| 9 | +import org.matsim.api.core.v01.IdSet; |
| 10 | +import org.matsim.api.core.v01.Scenario; |
| 11 | +import org.matsim.api.core.v01.network.Link; |
| 12 | +import org.matsim.api.core.v01.network.Network; |
| 13 | +import org.matsim.api.core.v01.population.Person; |
| 14 | +import org.matsim.core.config.CommandLine; |
| 15 | +import org.matsim.core.config.CommandLine.ConfigurationException; |
| 16 | +import org.matsim.core.config.Config; |
| 17 | +import org.matsim.core.config.ConfigUtils; |
| 18 | +import org.matsim.core.network.NetworkUtils; |
| 19 | +import org.matsim.core.network.algorithms.TransportModeNetworkFilter; |
| 20 | +import org.matsim.core.population.io.PopulationReader; |
| 21 | +import org.matsim.core.scenario.ScenarioUtils; |
| 22 | + |
| 23 | +import java.io.File; |
| 24 | +import java.io.IOException; |
| 25 | +import java.nio.file.Paths; |
| 26 | +import java.util.*; |
| 27 | + |
| 28 | +public class RunScenarioCutterV2 { |
| 29 | + |
| 30 | + public static final String[] SHAPEFILE_EXTENSIONS = new String[]{".shp", ".cpg", ".dbf", ".qmd", ".shx", ".prj"}; |
| 31 | + |
| 32 | + static public void main(String[] args) |
| 33 | + throws ConfigurationException, IOException, InterruptedException { |
| 34 | + CommandLine cmd = new CommandLine.Builder(args) // |
| 35 | + .requireOptions("config-path", "output-path", "extent-path", "vdf-travel-times-path") // |
| 36 | + .allowOptions("threads", "prefix", "extent-attribute", "extent-value", "plans-path", "events-path") // |
| 37 | + .allowOptions("flag-area-link-modes") // |
| 38 | + .build(); |
| 39 | + |
| 40 | + String outputPath = cmd.getOptionStrict("output-path"); |
| 41 | + |
| 42 | + EqasimConfigurator eqasimConfigurator = new EqasimConfigurator(); |
| 43 | + Config config = ConfigUtils.loadConfig(cmd.getOptionStrict("config-path"), eqasimConfigurator.getConfigGroups()); |
| 44 | + cmd.applyConfiguration(config); |
| 45 | + eqasimConfigurator.addOptionalConfigGroups(config); |
| 46 | + |
| 47 | + if(!config.getModules().containsKey(VDFConfigGroup.GROUP_NAME) || !config.getModules().containsKey(VDFEngineConfigGroup.GROUP_NAME)) { |
| 48 | + throw new IllegalStateException(String.format("This scenario cutter only works with configs where both '%s' and '%s' modules are used", VDFConfigGroup.GROUP_NAME, VDFEngineConfigGroup.GROUP_NAME)); |
| 49 | + } |
| 50 | + |
| 51 | + List<String> scenarioCutterArgs = new ArrayList<>(); |
| 52 | + for(String requiredOption: RunScenarioCutter.REQUIRED_ARGS) { |
| 53 | + scenarioCutterArgs.add("--"+requiredOption); |
| 54 | + scenarioCutterArgs.add(cmd.getOptionStrict(requiredOption)); |
| 55 | + } |
| 56 | + for(String optionalOption: RunScenarioCutter.OPTIONAL_ARGS) { |
| 57 | + if(cmd.hasOption(optionalOption)) { |
| 58 | + scenarioCutterArgs.add("--"+optionalOption); |
| 59 | + scenarioCutterArgs.add(cmd.getOptionStrict(optionalOption)); |
| 60 | + } |
| 61 | + } |
| 62 | + scenarioCutterArgs.add("--skip-routing"); |
| 63 | + scenarioCutterArgs.add("true"); |
| 64 | + |
| 65 | + RunScenarioCutter.main(scenarioCutterArgs.toArray(String[]::new)); |
| 66 | + |
| 67 | + String prefix = cmd.getOption("prefix").orElse(""); |
| 68 | + |
| 69 | + Scenario scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig()); |
| 70 | + eqasimConfigurator.configureScenario(scenario); |
| 71 | + // We first load the population resulting from the legacy cutter and store the person ids |
| 72 | + new PopulationReader(scenario).readFile(Paths.get(outputPath, prefix+"population.xml.gz").toString()); |
| 73 | + IdSet<Person> personIds = new IdSet<>(Person.class); |
| 74 | + scenario.getPopulation().getPersons().values().stream().map(Person::getId).forEach(personIds::add); |
| 75 | + |
| 76 | + // We now read the data from the original scenario |
| 77 | + scenario = ScenarioUtils.createScenario(config); |
| 78 | + eqasimConfigurator.configureScenario(scenario); |
| 79 | + ScenarioUtils.loadScenario(scenario); |
| 80 | + eqasimConfigurator.adjustScenario(scenario); |
| 81 | + |
| 82 | + // We remove from the original population, the persons that do not appear in the one resulting from the legacy cutter |
| 83 | + IdSet<Person> personsToRemove = new IdSet<>(Person.class); |
| 84 | + scenario.getPopulation().getPersons().values().stream().map(Person::getId).filter(personId -> !personIds.contains(personId)).forEach(personsToRemove::add); |
| 85 | + personsToRemove.forEach(scenario.getPopulation()::removePerson); |
| 86 | + |
| 87 | + // Now we process the network |
| 88 | + File extentPath = new File(cmd.getOptionStrict("extent-path")); |
| 89 | + Optional<String> extentAttribute = cmd.getOption("extent-attribute"); |
| 90 | + Optional<String> extentValue = cmd.getOption("extent-value"); |
| 91 | + ScenarioExtent extent = new ShapeScenarioExtent.Builder(extentPath, extentAttribute, extentValue).build(); |
| 92 | + |
| 93 | + Set<String> insideModes = new HashSet<>(); |
| 94 | + if(Boolean.parseBoolean(cmd.getOption("flag-area-link-modes").orElse("false"))) { |
| 95 | + scenario.getNetwork().getLinks().values() |
| 96 | + .stream().filter(link -> extent.isInside(link.getFromNode().getCoord()) && extent.isInside(link.getFromNode().getCoord())) |
| 97 | + .forEach(link -> { |
| 98 | + Set<String> linkModes = new HashSet<>(link.getAllowedModes()); |
| 99 | + for(String mode: link.getAllowedModes()) { |
| 100 | + String insideMode = "inside_"+mode; |
| 101 | + insideModes.add(insideMode); |
| 102 | + linkModes.add(insideMode); |
| 103 | + } |
| 104 | + link.setAllowedModes(linkModes); |
| 105 | + }); |
| 106 | + |
| 107 | + for(String mode: insideModes) { |
| 108 | + findLargestFullyConnectedSubnetwork(scenario.getNetwork(), mode); |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + // "Cut" config |
| 113 | + // (we need to reload it, because it has become locked at this point) |
| 114 | + config = ConfigUtils.loadConfig(cmd.getOptionStrict("config-path"), eqasimConfigurator.getConfigGroups()); |
| 115 | + cmd.applyConfiguration(config); |
| 116 | + eqasimConfigurator.addOptionalConfigGroups(config); |
| 117 | + ConfigCutter configCutter = new ConfigCutter(prefix); |
| 118 | + configCutter.run(config); |
| 119 | + |
| 120 | + // Before writing the config, we make sure we configure VDF to update the travel times only in the study area |
| 121 | + String extentBasePath = Paths.get(outputPath, "extent").toAbsolutePath().toString(); |
| 122 | + String copiedExtentPath = Paths.get(extentBasePath, extentPath.getName()).toString(); |
| 123 | + FileUtils.forceMkdir(new File(extentBasePath)); |
| 124 | + copyExtentFiles(extentPath.getAbsolutePath(), copiedExtentPath); |
| 125 | + VDFConfigGroup vdfConfigGroup = VDFConfigGroup.getOrCreate(config); |
| 126 | + vdfConfigGroup.setUpdateAreaShapefile("extent/" + extentPath.getName()); |
| 127 | + // We also set the VDF config to use the vdf.bin file for initial travel times |
| 128 | + vdfConfigGroup.setInputFile("vdf.bin"); |
| 129 | + |
| 130 | + new ScenarioWriter(config, scenario, prefix).run(new File(outputPath).getAbsoluteFile()); |
| 131 | + |
| 132 | + FileUtils.copyFile(new File(cmd.getOptionStrict("vdf-travel-times-path")), new File(outputPath, "vdf.bin")); |
| 133 | + } |
| 134 | + |
| 135 | + public static void findLargestFullyConnectedSubnetwork(Network network, String mode) { |
| 136 | + Network subNetwork = NetworkUtils.createNetwork(); |
| 137 | + new TransportModeNetworkFilter(network).filter(subNetwork, Set.of(mode)); |
| 138 | + |
| 139 | + NetworkUtils.runNetworkCleaner(subNetwork); |
| 140 | + |
| 141 | + for(Link link: network.getLinks().values()) { |
| 142 | + if(link.getAllowedModes().contains(mode) && !subNetwork.getLinks().containsKey(link.getId())) { |
| 143 | + Set<String> modes = new HashSet<>(link.getAllowedModes()); |
| 144 | + modes.remove(mode); |
| 145 | + link.setAllowedModes(modes); |
| 146 | + } |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + private static void copyExtentFiles(String sourcePath, String destPath) throws IOException { |
| 151 | + if(sourcePath.endsWith(".shp")) { |
| 152 | + sourcePath = sourcePath.substring(0, sourcePath.length()-4); |
| 153 | + destPath = destPath.substring(0, destPath.length()-4); |
| 154 | + for(String extension: SHAPEFILE_EXTENSIONS) { |
| 155 | + FileUtils.copyFile(new File(sourcePath + extension), new File(destPath + extension)); |
| 156 | + } |
| 157 | + } else { |
| 158 | + FileUtils.copyFile(new File(sourcePath), new File(destPath)); |
| 159 | + } |
| 160 | + } |
| 161 | +} |
0 commit comments