From b8fc55d89488d8d0cca4cebd66215416d87eb8b6 Mon Sep 17 00:00:00 2001 From: u228298 Date: Mon, 2 Dec 2024 16:38:34 +0100 Subject: [PATCH 1/7] build: adding build deps for the cli --- pom.xml | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/pom.xml b/pom.xml index af14020..eef4373 100644 --- a/pom.xml +++ b/pom.xml @@ -5,13 +5,13 @@ org.springframework.boot spring-boot-starter-parent - 3.2.0 + 3.4.0 ch.sbb.pfi.netzgrafikeditor netzgrafik-editor-converter - 2.7.0-SNAPSHOT - netzgrafik-editor-matsim-converter + 1.0.0-SNAPSHOT + netzgrafik-editor-converter Converter to expand network graphics from the Netzgrafik-Editor into timetables for the entire service day in different formats @@ -30,6 +30,17 @@ + + org.springframework.boot + spring-boot-maven-plugin + + + + build-info + + + + org.apache.maven.plugins maven-surefire-plugin @@ -53,9 +64,10 @@ + org.springframework.boot - spring-boot-starter-web + spring-boot-starter @@ -64,34 +76,34 @@ - org.matsim - matsim - ${matsim.version} + org.projectlombok + lombok - - org.springframework.boot - spring-boot-configuration-processor - true + info.picocli + picocli-spring-boot-starter + 4.7.6 - org.projectlombok - lombok + org.matsim + matsim + ${matsim.version} + + - org.jetbrains - annotations - 24.1.0 + org.springframework.boot + spring-boot-starter-test test - org.springframework.boot - spring-boot-starter-test + org.jetbrains + annotations + 24.1.0 test From c849e25f70a071d0d75679343531a3e2a017bf83 Mon Sep 17 00:00:00 2001 From: u228298 Date: Mon, 2 Dec 2024 16:40:28 +0100 Subject: [PATCH 2/7] feat: add picocli-based command line interface --- .../sbb/pfi/netzgrafikeditor/Application.java | 79 ------------------- .../converter/app/CommandLineConverter.java | 38 +++++++++ .../converter/app/ConversionService.java | 52 ++++++++++++ .../converter/app/ConvertCommand.java | 79 +++++++++++++++++++ .../converter/app/OutputFormat.java | 6 ++ .../app/ServiceDayTimeConverter.java | 19 +++++ 6 files changed, 194 insertions(+), 79 deletions(-) delete mode 100644 src/main/java/ch/sbb/pfi/netzgrafikeditor/Application.java create mode 100644 src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/CommandLineConverter.java create mode 100644 src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ConversionService.java create mode 100644 src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ConvertCommand.java create mode 100644 src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/OutputFormat.java create mode 100644 src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ServiceDayTimeConverter.java diff --git a/src/main/java/ch/sbb/pfi/netzgrafikeditor/Application.java b/src/main/java/ch/sbb/pfi/netzgrafikeditor/Application.java deleted file mode 100644 index 14ccc8e..0000000 --- a/src/main/java/ch/sbb/pfi/netzgrafikeditor/Application.java +++ /dev/null @@ -1,79 +0,0 @@ -package ch.sbb.pfi.netzgrafikeditor; - -import ch.sbb.pfi.netzgrafikeditor.converter.adapter.matsim.MatsimSupplyBuilder; -import ch.sbb.pfi.netzgrafikeditor.converter.core.ConverterSink; -import ch.sbb.pfi.netzgrafikeditor.converter.core.NetworkGraphicConverter; -import ch.sbb.pfi.netzgrafikeditor.converter.core.NetworkGraphicConverterConfig; -import ch.sbb.pfi.netzgrafikeditor.converter.core.NetworkGraphicSource; -import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.SupplyBuilder; -import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.fallback.NoInfrastructureRepository; -import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.fallback.NoRollingStockRepository; -import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.fallback.NoVehicleCircuitsPlanner; -import ch.sbb.pfi.netzgrafikeditor.converter.core.validation.ValidationStrategy; -import ch.sbb.pfi.netzgrafikeditor.converter.io.matsim.TransitScheduleXmlWriter; -import ch.sbb.pfi.netzgrafikeditor.converter.io.netzgrafik.JsonFileReader; -import org.matsim.api.core.v01.Scenario; -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -@SpringBootApplication -public class Application implements CommandLineRunner { - - private Path outputPath; - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - @Override - public void run(String... args) { - if (args.length < 2) { - System.err.println("Please provide the path to the network graphic JSON file and the output directory."); - System.exit(1); - } - - String jsonFilePath = args[0]; - String outputDirPath = args[1]; - outputPath = Paths.get(outputDirPath); - - convertJsonToMatsimSchedule(Paths.get(jsonFilePath)); - } - - private void convertJsonToMatsimSchedule(Path jsonFilePath) { - try { - NetworkGraphicConverterConfig config = NetworkGraphicConverterConfig.builder() - .validationStrategy(ValidationStrategy.WARN_ON_ISSUES) - .build(); - - NetworkGraphicConverter converter = getNetworkGraphicConverter(jsonFilePath, config); - - converter.run(); - - System.out.println("MATSim schedule has been written to: " + outputPath); - System.exit(0); - } catch (IOException e) { - System.err.println("Error during conversion: " + e.getMessage()); - System.exit(1); - } - } - - private NetworkGraphicConverter getNetworkGraphicConverter(Path jsonFilePath, NetworkGraphicConverterConfig config) { - NetworkGraphicSource source = new JsonFileReader(jsonFilePath); - - SupplyBuilder builder = new MatsimSupplyBuilder(new NoInfrastructureRepository(), - new NoVehicleCircuitsPlanner(new NoRollingStockRepository())); - - String baseFilename = jsonFilePath.getFileName().toString(); - String filenameWithoutExtension = jsonFilePath.getFileName() - .toString() - .substring(0, baseFilename.lastIndexOf('.')); - ConverterSink sink = new TransitScheduleXmlWriter(outputPath, filenameWithoutExtension + "."); - - return new NetworkGraphicConverter<>(config, source, builder, sink); - } -} diff --git a/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/CommandLineConverter.java b/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/CommandLineConverter.java new file mode 100644 index 0000000..3a4dcb9 --- /dev/null +++ b/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/CommandLineConverter.java @@ -0,0 +1,38 @@ +package ch.sbb.pfi.netzgrafikeditor.converter.app; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.ExitCodeGenerator; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import picocli.CommandLine; + +@SpringBootApplication +public class CommandLineConverter implements CommandLineRunner, ExitCodeGenerator { + + private static final String FOOTER_KEY = "footer"; + + private final CommandLine.IFactory factory; + private final ConvertCommand convertCommand; + private int exitCode; + + CommandLineConverter(CommandLine.IFactory factory, ConvertCommand convertCommand) { + this.factory = factory; + this.convertCommand = convertCommand; + } + + public static void main(String[] args) { + System.exit(SpringApplication.exit(SpringApplication.run(CommandLineConverter.class, args))); + } + + @Override + public void run(String... args) { + CommandLine commandLine = new CommandLine(convertCommand, factory); + commandLine.getHelpSectionMap().put(FOOTER_KEY, convertCommand.new FooterProvider()); + exitCode = commandLine.execute(args); + } + + @Override + public int getExitCode() { + return exitCode; + } +} diff --git a/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ConversionService.java b/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ConversionService.java new file mode 100644 index 0000000..982dd0f --- /dev/null +++ b/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ConversionService.java @@ -0,0 +1,52 @@ +package ch.sbb.pfi.netzgrafikeditor.converter.app; + +import ch.sbb.pfi.netzgrafikeditor.converter.adapter.gtfs.GtfsSupplyBuilder; +import ch.sbb.pfi.netzgrafikeditor.converter.adapter.gtfs.model.GtfsSchedule; +import ch.sbb.pfi.netzgrafikeditor.converter.adapter.matsim.MatsimSupplyBuilder; +import ch.sbb.pfi.netzgrafikeditor.converter.core.ConverterSink; +import ch.sbb.pfi.netzgrafikeditor.converter.core.NetworkGraphicConverter; +import ch.sbb.pfi.netzgrafikeditor.converter.core.NetworkGraphicConverterConfig; +import ch.sbb.pfi.netzgrafikeditor.converter.core.NetworkGraphicSource; +import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.SupplyBuilder; +import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.fallback.NoInfrastructureRepository; +import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.fallback.NoRollingStockRepository; +import ch.sbb.pfi.netzgrafikeditor.converter.core.supply.fallback.NoVehicleCircuitsPlanner; +import ch.sbb.pfi.netzgrafikeditor.converter.io.gtfs.GtfsScheduleWriter; +import ch.sbb.pfi.netzgrafikeditor.converter.io.matsim.TransitScheduleXmlWriter; +import ch.sbb.pfi.netzgrafikeditor.converter.io.netzgrafik.JsonFileReader; +import org.matsim.api.core.v01.Scenario; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.nio.file.Path; + +@Service +public class ConversionService { + + public void convert(Path networkGraphicFile, Path outputDirectory, NetworkGraphicConverterConfig config, OutputFormat outputFormat) throws IOException { + NetworkGraphicSource source = new JsonFileReader(networkGraphicFile); + + NetworkGraphicConverter converter = switch (outputFormat) { + case GTFS -> { + SupplyBuilder builder = new GtfsSupplyBuilder(new NoInfrastructureRepository(), + new NoVehicleCircuitsPlanner(new NoRollingStockRepository())); + ConverterSink sink = new GtfsScheduleWriter(outputDirectory); + + yield new NetworkGraphicConverter<>(config, source, builder, sink); + } + case MATSIM -> { + SupplyBuilder builder = new MatsimSupplyBuilder(new NoInfrastructureRepository(), + new NoVehicleCircuitsPlanner(new NoRollingStockRepository())); + String baseFilename = networkGraphicFile.getFileName().toString(); + String filenameWithoutExtension = baseFilename.substring(0, baseFilename.lastIndexOf('.')); + ConverterSink sink = new TransitScheduleXmlWriter(outputDirectory, + filenameWithoutExtension + "."); + + yield new NetworkGraphicConverter<>(config, source, builder, sink); + } + }; + + converter.run(); + + } +} \ No newline at end of file diff --git a/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ConvertCommand.java b/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ConvertCommand.java new file mode 100644 index 0000000..09baca7 --- /dev/null +++ b/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ConvertCommand.java @@ -0,0 +1,79 @@ +package ch.sbb.pfi.netzgrafikeditor.converter.app; + +import ch.sbb.pfi.netzgrafikeditor.converter.core.NetworkGraphicConverterConfig; +import ch.sbb.pfi.netzgrafikeditor.converter.core.validation.ValidationStrategy; +import ch.sbb.pfi.netzgrafikeditor.converter.util.time.ServiceDayTime; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.info.BuildProperties; +import org.springframework.stereotype.Component; +import picocli.CommandLine; + +import java.nio.file.Path; +import java.util.concurrent.Callable; + +@Component +@CommandLine.Command(name = "convert", mixinStandardHelpOptions = true, description = "Converts network graphics into timetables in various formats.", versionProvider = ConvertCommand.ManifestVersionProvider.class, footer = {""}) +@RequiredArgsConstructor +public class ConvertCommand implements Callable { + + private final BuildProperties buildProperties; + private final ConversionService conversionService; + @CommandLine.Parameters(index = "0", description = "The network graphic file to convert.") + private Path networkGraphicFile; + @CommandLine.Parameters(index = "1", description = "The output directory for the converted timetable.") + private Path outputDirectory; + @CommandLine.Option(names = {"-v", "--validation"}, description = "Validation strategy (SKIP_VALIDATION, WARN_ON_ISSUES, FAIL_ON_ISSUES, FIX_ISSUES).", defaultValue = "WARN_ON_ISSUES") + private ValidationStrategy validationStrategy; + @CommandLine.Option(names = {"-t", "--train-names"}, description = "Use train names as route or line IDs (true/false).", defaultValue = "false") + private boolean useTrainNamesAsIds; + @CommandLine.Option(names = {"-s", "--service-day-start"}, description = "Service day start time (HH:mm).", converter = ServiceDayTimeConverter.class, defaultValue = "04:00") + private ServiceDayTime serviceDayStart; + @CommandLine.Option(names = {"-e", "--service-day-end"}, description = "Service day end time (HH:mm).", converter = ServiceDayTimeConverter.class, defaultValue = "25:00") + private ServiceDayTime serviceDayEnd; + @CommandLine.Option(names = {"-f", "--format"}, description = "Output format (GTFS or MATSim).", defaultValue = "GTFS") + private OutputFormat outputFormat; + + @Override + public Integer call() throws Exception { + conversionService.convert(networkGraphicFile, outputDirectory, deriveNetworkGraphicConfig(), outputFormat); + return 0; + } + + private NetworkGraphicConverterConfig deriveNetworkGraphicConfig() { + return NetworkGraphicConverterConfig.builder() + .validationStrategy(validationStrategy) + .useTrainNamesAsIds(useTrainNamesAsIds) + .serviceDayStart(serviceDayStart) + .serviceDayEnd(serviceDayEnd) + .build(); + } + + class ManifestVersionProvider implements CommandLine.IVersionProvider { + @Override + public String[] getVersion() { + return new String[]{String.format("%s %s", buildProperties.getArtifact(), buildProperties.getVersion())}; + } + } + + class FooterProvider implements CommandLine.IHelpSectionRenderer { + + private static final String TEMPLATE = """ + + %s Copyright (C) 2024 Swiss Federal Railways SBB AG + + This program is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty ofmMERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. See the GNU General Public License for more details. + """; + + @Override + public String render(CommandLine.Help help) { + return String.format(TEMPLATE, buildProperties.getArtifact()); + } + } +} diff --git a/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/OutputFormat.java b/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/OutputFormat.java new file mode 100644 index 0000000..37a9ac1 --- /dev/null +++ b/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/OutputFormat.java @@ -0,0 +1,6 @@ +package ch.sbb.pfi.netzgrafikeditor.converter.app; + +public enum OutputFormat { + GTFS, + MATSIM +} diff --git a/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ServiceDayTimeConverter.java b/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ServiceDayTimeConverter.java new file mode 100644 index 0000000..c8f415a --- /dev/null +++ b/src/main/java/ch/sbb/pfi/netzgrafikeditor/converter/app/ServiceDayTimeConverter.java @@ -0,0 +1,19 @@ +package ch.sbb.pfi.netzgrafikeditor.converter.app; + +import ch.sbb.pfi.netzgrafikeditor.converter.util.time.ServiceDayTime; +import picocli.CommandLine; + +public class ServiceDayTimeConverter implements CommandLine.ITypeConverter { + + @Override + public ServiceDayTime convert(String value) { + String[] parts = value.split(":"); + if (parts.length != 2) { + throw new IllegalArgumentException("Invalid time format. Expected HH:mm."); + } + + int hours = Integer.parseInt(parts[0]); + int minutes = Integer.parseInt(parts[1]); + return ServiceDayTime.of(hours, minutes, 0); + } +} From 3d66b9d2f9d849380af3547ab4f2bcace6081348 Mon Sep 17 00:00:00 2001 From: u228298 Date: Mon, 2 Dec 2024 16:40:49 +0100 Subject: [PATCH 3/7] doc: update CLI example in README --- README.md | 50 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 63d4ac9..9282e70 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,45 @@ the [Netzgrafik-Editor](https://github.com/SchweizerischeBundesbahnen/netzgrafik the entire service day in different formats, as for example GTFS static or MATSim transit schedules. +## Usage + +Run the command line tool to convert a network graphic to either a GTFS or MATSim timetable: + +```text +Usage: convert [-htV] [-e=] [-f=] + [-s=] [-v=] + +Converts network graphics into timetables in various formats. + The network graphic file to convert. + The output directory for the converted timetable. + -e, --service-day-end= + Service day end time (HH:mm). + -f, --format= + Output format (GTFS or MATSim). + -h, --help Show this help message and exit. + -s, --service-day-start= + Service day start time (HH:mm). + -t, --train-names Use train names as route or line IDs (true/false). + -v, --validation= + Validation strategy (SKIP_VALIDATION, + WARN_ON_ISSUES, FAIL_ON_ISSUES, FIX_ISSUES). + -V, --version Print version information and exit. +``` + +Example: + +```sh +# configure arguments +NETWORK_GRAPHIC_FILE=src/test/resources/ng/scenarios/realistic.json +OUTPUT_DIRECTORY=integration-test/output/cmd + +# run the Spring command line runner app to convert to GTFS format +./mvnw spring-boot:run -Dspring-boot.run.arguments="$NETWORK_GRAPHIC_FILE $OUTPUT_DIRECTORY -f GTFS" + +# run the Spring command line runner app to convert to MATSim format with custom service day times +./mvnw spring-boot:run -Dspring-boot.run.arguments="$NETWORK_GRAPHIC_FILE $OUTPUT_DIRECTORY -f MATSIM -s 04:30 -e 26:00" +``` + ## Design The converter has a modular design (DI): @@ -22,17 +61,6 @@ The class diagram outlines the core classes and their relationships: ![Class diagram](docs/uml/class-diagram.svg) -## Usage - -```sh -# configure arguments -NETWORK_GRAPHIC_FILE=src/test/resources/ng/scenarios/realistic.json -OUTPUT_DIRECTORY=integration-test/output/ - -# run spring command line runner app -./mvnw spring-boot:run -Dspring-boot.run.arguments="$NETWORK_GRAPHIC_FILE $OUTPUT_DIRECTORY" -``` - ## License This project is licensed under GNU [GPL-3.0](LICENSE). From 23f74dcbc91c13ccfbff92efd9b128e29080f1e6 Mon Sep 17 00:00:00 2001 From: u228298 Date: Mon, 2 Dec 2024 16:42:38 +0100 Subject: [PATCH 4/7] chore: ignore picocli written fields --- .idea/misc.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.idea/misc.xml b/.idea/misc.xml index ecfa09c..36e5acf 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,11 @@ + + + + + +