package eu.simstadt.regionchooser; import java.io.BufferedWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Scanner; import java.util.concurrent.Callable; import org.osgeo.proj4j.CoordinateReferenceSystem; import eu.simstadt.regionchooser.RegionChooserCLI.GetVersion; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.IVersionProvider; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; import picocli.CommandLine.Spec; /** * Command Line Interface for RegionChooser. Could be useful to extract large regions on server, or automate the process * from batch/python scripts. * */ // Usage: region_chooser [-hlV] [-e=31467] -o=output.gml -w=polygon.wkt -i=input. // gml[,input.gml...] [-i=input.gml[,input.gml...]]... // Extracts a region from one or more citygmls. // -i, --input=input.gml[,input.gml...] // Citygml files to extract from // -o, --output=output.gml Output file // -e, --epsg=31467 EPSG id for coordinate reference system // -l, --local Are WKT coordinates in local CRS? // -w, --wkt=polygon.wkt File containing WKT polygon, or - for stdin // -h, --help Show this help message and exit. // -V, --version Print version information and exit. // Example: // --input CGSC_Repository/Würzburg.proj/LoD2_566_5516_2_BY.gml,CGSC_Repository/Würzburg.proj/LoD2_568_5516_2_BY.gml // --output ./output.gml // --wkt ./grombuhl.txt @Command(name = "region_chooser", mixinStandardHelpOptions = true, description = "Extracts a region from one or more citygmls.", sortOptions = false, versionProvider = GetVersion.class) class RegionChooserCLI implements Callable { @Spec CommandSpec spec; @Option(names = { "-i", "--input" }, required = true, split = ",", description = "Citygml files to extract from", paramLabel = "input.gml") Path[] citygmls; @Option(names = { "-o", "--output" }, required = true, description = "Output file", paramLabel = "output.gml") Path outputCityGML; @Option(names = { "-e", "--epsg" }, description = "EPSG id for coordinate reference system.\nWill use the one from input.gml if unspecified.", paramLabel = "31467") Integer espgId; @Option(names = { "-l", "--local" }, description = "Are WKT coordinates in local CRS?\nCoordinates are in WGS84 if unspecified.", paramLabel = "local_coordinates?") boolean localCoordinates; @Option(names = { "-w", "--wkt" }, required = true, description = "File containing WKT polygon, or - for stdin", paramLabel = "polygon.wkt") String wktFile = "-"; @Override public Integer call() throws Exception { CoordinateReferenceSystem localCRS; if (espgId == null) { localCRS = RegionChooserUtils.crsFromCityGMLHeader(citygmls[0]); } else { localCRS = RegionChooserUtils.crsFromSrsName("EPSG:" + espgId); } logInfo("Coordinate system: " + localCRS); String wktPolygon; if (wktFile.equals("-")) { if (System.in.available() == 0) { throw new IllegalArgumentException("Please provide \"POLYGON((x1 y1, x2 y2, ...))\" to standard input."); } else { wktPolygon = getInput(); } } else { wktPolygon = new String(Files.readAllBytes(Paths.get(wktFile)), StandardCharsets.UTF_8); if (wktPolygon.isEmpty()) { throw new IllegalArgumentException("Please write \"POLYGON((x1 y1, x2 y2, ...))\" inside " + wktFile); } } if (!localCoordinates) { // WKT coordinates are in WGS84, so should be first converted to srsName wktPolygon = RegionChooserUtils.wktPolygonToLocalCRS(wktPolygon, localCRS); } logInfo("WKT Polygon expressed in local coordinates: " + wktPolygon); int count; if (outputCityGML.toString().equals("-")) { logInfo("CityGML written to stdout."); PrintWriter stdOut = spec.commandLine().getOut(); count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, localCRS.toString(), stdOut, citygmls); stdOut.flush(); // To make sure the footer is written too. } else { try (BufferedWriter gmlWriter = Files.newBufferedWriter(outputCityGML)) { count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, localCRS.toString(), gmlWriter, citygmls); } } logInfo("Found buildings : " + count); return 0; } // this example implements Callable, so parsing, error handling and handling user // requests for usage help or version help can be done with one line of code. public static void main(String... args) { int exitCode = new CommandLine(new RegionChooserCLI()).execute(args); System.exit(exitCode); } private void logInfo(String message) { spec.commandLine().getErr().println(message); } private static String getInput() { try (Scanner myObj = new Scanner(System.in)) { return myObj.nextLine(); } } static class GetVersion implements IVersionProvider { @Override public String[] getVersion() throws Exception { return new String[] { RegionChooserUtils.getApplicationVersion() }; } } }