diff --git a/src/main/java/org/matsim/dashboard/AdaptV2OutputFiles.java b/src/main/java/org/matsim/dashboard/AdaptV2OutputFiles.java new file mode 100644 index 0000000..54ecaef --- /dev/null +++ b/src/main/java/org/matsim/dashboard/AdaptV2OutputFiles.java @@ -0,0 +1,231 @@ +package org.matsim.dashboard; + +import com.univocity.parsers.common.input.EOFException; +import org.matsim.application.MATSimAppCommand; +import org.matsim.application.options.CsvOptions; +import org.matsim.vehicles.MatsimVehicleReader; +import org.matsim.vehicles.VehicleUtils; +import org.matsim.vehicles.Vehicles; +import org.w3c.dom.Comment; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; +import picocli.CommandLine; +import tech.tablesaw.api.DoubleColumn; +import tech.tablesaw.api.Table; +import tech.tablesaw.io.csv.CsvReadOptions; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static org.matsim.application.ApplicationUtils.globFile; + +@CommandLine.Command( + name = "adapt-v2-output", + description = "Adapt run output from a v2 kelheim sim run, such that standard dashboards can be created for the run." +) +public class AdaptV2OutputFiles implements MATSimAppCommand { + + @CommandLine.Option(names = "--runDir", description = "Path of V2 run directory with files to adapt.", required = true) + private String dir; + + @CommandLine.Option(names = "--drt-modes", description = "Drt modes to be analyzed. Separated by comma.", defaultValue = "drt,av") + private String drtModes; + + @CommandLine.Option(names = "--drt-stops-files", description = "File names for drt stops files. This has to be adapted when param --drt-modes is adapted. Needs to be an absolute path Separated by comma.", + defaultValue = "/net/ils/matsim-kelheim/scenarios/input/kelheim-v2.0/kelheim-v2.0-drt-stops.xml,/net/ils/matsim-kelheim/scenarios/input/kelheim-v2.0/av-stops-DP-AS.xml") + private String drtStops; + + public static void main(String[] args) { + new AdaptV2OutputFiles().execute(args); + } + + @Override + public Integer call() throws Exception { + +// copy stops files to run dir + List stopsFiles = Arrays.stream(drtStops.split(",")).toList(); + + stopsFiles.forEach(path -> { + File oldFile = new File(path); + Path targetPath = Path.of(dir + "/" + oldFile.getName()); + + copyFile(targetPath, oldFile); + }); + +// copy original config file + File inputConfigFile = new File(globFile(Path.of(dir), "*output_config.xml").toString()); + Path targetConfigPath = Path.of(inputConfigFile.getPath().split(".xml")[0] + "_withZonalSystemParams_withTravelTimeCalculatorParam.xml"); + copyFile(targetConfigPath, inputConfigFile); + +// comment out config params which produce errors + adaptConfigAndWriteXml(inputConfigFile); + + List modes = Arrays.stream(drtModes.split(",")).toList(); + + Map> filePaths = new HashMap<>(); + +// get all relevant filePaths and save to map for iterating over them + modes.forEach(m -> { + String legsPath = globFile(Path.of(dir + "/ITERS/it.999"), "*drt_legs_" + m + ".csv").toString(); + String vehicleStatsPath = globFile(Path.of(dir), "*drt_vehicle_stats_" + m + ".csv").toString(); + String customerStatsPath = globFile(Path.of(dir), "*drt_customer_stats_" + m + ".csv").toString(); + + filePaths.put(m, List.of(legsPath, vehicleStatsPath, customerStatsPath)); + }); + + +// copy drt and av legs file from iters folder to global output + filePaths.values().forEach(v -> { + File legsFile = new File(v.getFirst()); + Path targetLegsPath = Path.of(dir + "/" + legsFile.getName()); + + copyFile(targetLegsPath, legsFile); + }); + +// add totalServiceDuration column to vehicle stats file + filePaths.forEach((k, v) -> { + File vehicleStatsFile = new File(v.get(1)); + + Table vehicleStats; + try { + vehicleStats = Table.read().csv(CsvReadOptions.builder(vehicleStatsFile) + .separator(CsvOptions.detectDelimiter(vehicleStatsFile.getPath())) + .build()); + } catch (IOException e) { + throw new EOFException(); + } + + if (!vehicleStats.containsColumn("totalServiceDuration")) { + + Vehicles vehicles = VehicleUtils.createVehiclesContainer(); + MatsimVehicleReader.VehicleReader vehicleReader = new MatsimVehicleReader.VehicleReader(vehicles); + + vehicleReader.readFile(globFile(Path.of(dir), "*" + k + "_vehicles.xml").toString()); + + AtomicReference serviceDuration = new AtomicReference<>(0.); + vehicles.getVehicles().values().forEach(veh -> serviceDuration.updateAndGet(v1 -> v1 + (Double) veh.getAttributes().getAttribute("t_1") - (Double) veh.getAttributes().getAttribute("t_0"))); + + double[] values = new double[vehicleStats.rowCount()]; + Arrays.fill(values, serviceDuration.get()); + + DoubleColumn totalServiceDuration = DoubleColumn.create("totalServiceDuration", values); + vehicleStats.addColumns(totalServiceDuration); + + vehicleStats.write().csv(vehicleStatsFile); + } + }); + +// add rides_pax and dummy groupSize_mean columns to customer stats file + filePaths.values().forEach(v -> { + File customerStatsFile = new File(v.get(2)); + + Table customerStats; + try { + customerStats = Table.read().csv(CsvReadOptions.builder(customerStatsFile) + .separator(CsvOptions.detectDelimiter(customerStatsFile.getPath())) + .build()); + } catch (IOException e) { + throw new EOFException(); + } + + if (!customerStats.containsColumn("rides_pax") || !customerStats.containsColumn("groupSize_mean")) { + if (customerStats.containsColumn("rides")) { + DoubleColumn rides = customerStats.doubleColumn("rides"); + DoubleColumn ridesPax = rides.copy().setName("rides_pax"); + customerStats.addColumns(ridesPax); + } + double[] defaultValues = new double[customerStats.rowCount()]; + + Arrays.fill(defaultValues, -1); + DoubleColumn groupSizeMean = DoubleColumn.create("groupSize_mean", defaultValues); + customerStats.addColumns(groupSizeMean); + + customerStats.write().csv(customerStatsFile); + } + }); + + return 0; + } + + private static void adaptConfigAndWriteXml(File inputConfigFile) throws ParserConfigurationException, SAXException, IOException, TransformerException { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(inputConfigFile); + doc.getDocumentElement().normalize(); + + NodeList moduleList = doc.getElementsByTagName("module"); + for (int i = 0; i < moduleList.getLength(); i++) { + Element moduleElement = (Element) moduleList.item(i); + + if (moduleElement.getAttribute("name").equals("travelTimeCalculator")) { + NodeList paramList = moduleElement.getElementsByTagName("param"); + for (int j = 0; j < paramList.getLength(); j++) { + Element paramElement = (Element) paramList.item(j); + if (paramElement.getAttribute("name").equals("travelTimeCalculator")) { + // Comment out the param element + Comment comment = doc.createComment(paramElement.getTextContent()); + moduleElement.replaceChild(comment, paramElement); + break; + } + } + break; + } + + if (moduleElement.getAttribute("name").equals("multiModeDrt")) { + NodeList paramSetList = moduleElement.getElementsByTagName("parameterset"); + + for (int k = 0; k < paramSetList.getLength(); k++) { + Element paramSetElement = (Element) paramSetList.item(k); + + if (paramSetElement.getAttribute("type").equals("drt")) { + NodeList innerParamSetList = paramSetElement.getElementsByTagName("parameterset"); + + for (int l = 0; l < innerParamSetList.getLength(); l++) { + Element innerParamSetElement = (Element) innerParamSetList.item(l); + if (innerParamSetElement.getAttribute("type").equals("zonalSystem")) { + + // Comment out the "parameterset" of type "zonalSystem" + Comment comment = doc.createComment(innerParamSetElement.getTextContent()); + paramSetElement.replaceChild(comment, innerParamSetElement); + break; + } + } + } + } + } + } + + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + DOMSource source = new DOMSource(doc); + StreamResult result = new StreamResult(inputConfigFile); + transformer.setOutputProperty(OutputKeys.INDENT, "no"); + transformer.transform(source, result); + } + + private static void copyFile(Path targetPath, File inputFile) { + if (Files.notExists(targetPath) && inputFile.exists() && inputFile.isFile()) { + try { + Files.copy(inputFile.toPath(), targetPath); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } +} diff --git a/src/main/java/org/matsim/dashboard/KelheimSimWrapperRunner.java b/src/main/java/org/matsim/dashboard/KelheimSimWrapperRunner.java index bf0a9fe..63050b7 100644 --- a/src/main/java/org/matsim/dashboard/KelheimSimWrapperRunner.java +++ b/src/main/java/org/matsim/dashboard/KelheimSimWrapperRunner.java @@ -31,7 +31,7 @@ import org.matsim.core.config.ConfigUtils; import org.matsim.simwrapper.SimWrapper; import org.matsim.simwrapper.SimWrapperConfigGroup; -import org.matsim.simwrapper.dashboard.NoiseDashboard; +import org.matsim.simwrapper.dashboard.*; import picocli.CommandLine; import java.io.File; @@ -97,7 +97,14 @@ public Integer call() throws Exception { simwrapperCfg.defaultParams().shp = shp.getShapeFile().toString(); } - if (!standard) { + if (standard) { + simwrapperCfg.defaultDashboards = SimWrapperConfigGroup.Mode.enabled; + sw.addDashboard(new OverviewDashboard()); + sw.addDashboard(new TripDashboard("kelheim_mode_share.csv", "kelheim_mode_share_per_dist.csv", null)); + sw.addDashboard(new TrafficDashboard()); + sw.addDashboard(new TrafficCountsDashboard()); + sw.addDashboard(new StuckAgentDashboard()); + } else { //skip default dashboards simwrapperCfg.defaultDashboards = SimWrapperConfigGroup.Mode.disabled; }