diff --git a/.gitignore b/.gitignore
index f190e4bc19841cf1aa289fc09f5b260ea70f0b82..eb2353e6a5bbacc39950c1f4c8d831003d13c863 100644
--- a/.gitignore
+++ b/.gitignore
@@ -272,3 +272,4 @@ $RECYCLE.BIN/
 *.lnk
 
 # End of https://www.gitignore.io/api/java,maven,macos,linux,eclipse,windows,netbeans,intellij
+/-
diff --git a/buildConfig/RegionChooser.bat b/buildConfig/RegionChooser.bat
index f28ebeed8e7a29d6734480426babef3e73350303..430ee7995efae8d4eb9422d91d568444d52ef8ca 100644
--- a/buildConfig/RegionChooser.bat
+++ b/buildConfig/RegionChooser.bat
@@ -1 +1,2 @@
-java -classpath lib/* -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX
\ No newline at end of file
+java -classpath lib/* -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX
+REM TODO: Update, similar to RegionChooser.sh
\ No newline at end of file
diff --git a/buildConfig/RegionChooser.command b/buildConfig/RegionChooser.command
index 95ccbc8fc351c686086e0ac2d571b287c7713bfb..02a02bd34072239804923e54a14d2b3406ec0281 100755
--- a/buildConfig/RegionChooser.command
+++ b/buildConfig/RegionChooser.command
@@ -1,3 +1,9 @@
 #!/bin/bash
-cd "$(dirname "$0")" # set the current working directory to the directory this script is in
-java -d64 -classpath lib/*: -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX
+if [[ -z "$1" ]]
+  then
+	echo No parameter, launching RegionChooser GUI
+	java -classpath 'lib/*' eu.simstadt.regionchooser.RegionChooserFX
+  else
+	echo Launching RegionChooserCLI "$@"
+    java -classpath 'lib/*' eu.simstadt.regionchooser.RegionChooserCLI "$@"
+fi
\ No newline at end of file
diff --git a/buildConfig/RegionChooser.sh b/buildConfig/RegionChooser.sh
index 2696b380a4fee8df427606f73ab348825d887d77..26d7058eee79a814fc43574395adca82cec0e56d 100644
--- a/buildConfig/RegionChooser.sh
+++ b/buildConfig/RegionChooser.sh
@@ -1,2 +1,9 @@
 #!/bin/bash
-java -classpath 'lib/*' -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX
\ No newline at end of file
+if [[ -z "$1" ]]
+  then
+	echo No parameter, launching RegionChooser GUI
+	java -classpath 'lib/*' eu.simstadt.regionchooser.RegionChooserFX
+  else
+	echo Launching RegionChooserCLI "$@"
+    java -classpath 'lib/*' eu.simstadt.regionchooser.RegionChooserCLI "$@"
+fi
diff --git a/pom.xml b/pom.xml
index 7aecf17a04bb3ef14e1085f758308e2af52d76c9..19f1d2170d85d9061d24f5de0bc019e1a5e7e4e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
 
 	<groupId>eu.simstadt</groupId>
 	<artifactId>region-chooser</artifactId>
-	<version>0.2.3-SNAPSHOT</version>
+	<version>0.2.9-SNAPSHOT</version>
 
 	<properties>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -27,6 +27,12 @@
 			<scope>test</scope>
 		</dependency>
 
+		<dependency>
+			<groupId>info.picocli</groupId>
+			<artifactId>picocli</artifactId>
+			<version>4.6.3</version>
+		</dependency>
+
 		<!-- https://mvnrepository.com/artifact/org.osgeo/proj4j -->
 		<dependency>
 			<groupId>org.osgeo</groupId>
diff --git a/src/main/java/eu/simstadt/regionchooser/RegionChooserBrowser.java b/src/main/java/eu/simstadt/regionchooser/RegionChooserBrowser.java
index 92bf767a2e1c775d97ef0810e2bf962df713a03b..5f6f157a8c2b071d0d49efe6b65604edb9c1babf 100644
--- a/src/main/java/eu/simstadt/regionchooser/RegionChooserBrowser.java
+++ b/src/main/java/eu/simstadt/regionchooser/RegionChooserBrowser.java
@@ -8,9 +8,9 @@
 import java.nio.file.Paths;
 import java.util.logging.Logger;
 import java.util.prefs.Preferences;
+import java.util.stream.Stream;
 import org.locationtech.jts.io.ParseException;
 import com.ximpleware.NavException;
-import com.ximpleware.XPathEvalException;
 import com.ximpleware.XPathParseException;
 import eu.simstadt.regionchooser.fast_xml_parser.ConvexHullCalculator;
 import javafx.application.Platform;
@@ -22,6 +22,7 @@
 import javafx.scene.layout.Region;
 import javafx.scene.web.WebEngine;
 import javafx.scene.web.WebView;
+import javafx.stage.DirectoryChooser;
 import javafx.stage.FileChooser;
 import javafx.stage.Stage;
 import netscape.javascript.JSObject;
@@ -30,6 +31,7 @@
 public class RegionChooserBrowser extends Region
 {
 	private static final Logger LOGGER = Logger.getLogger(RegionChooserBrowser.class.getName());
+	private static final String PREF_RECENT_REPOSITORY = "RECENT_REPOSITORY";
 
 	/**
 	 * JavaFX Backend for RegionChooser. Inside simstadt_openlayers.js frontend, this class is available as `fxapp`.
@@ -40,16 +42,16 @@ public class RegionChooserBrowser extends Region
 
 		public JavaScriptFXBridge() {
 			Preferences userPrefs = Preferences.userRoot().node("/eu/simstadt/desktop");
-			String repoString = userPrefs.get("RECENT_REPOSITORY", "../TestRepository");
+			String repoString = userPrefs.get(PREF_RECENT_REPOSITORY, "../TestRepository");
 			repo = Paths.get(repoString);
 		}
 
 		/**
 		 * Launches a background thread in which the hull gets extracted for every CityGML file. The hull gets sent back
 		 * to the JS app in order to be displayed.
-		 * 
+		 *
 		 * NOTE: To be very honest, I don't really understand concurrency in JavaFX. Eric
-		 * 
+		 *
 		 */
 		public void refreshHulls() {
 			//NOTE: Could add progress bar?
@@ -62,24 +64,54 @@ public Void call() throws IOException {
 				}
 			};
 
-			task.setOnRunning(e -> jsApp.call("display", "Importing citgyml. Please wait."));
+			task.setOnRunning(e -> {
+				jsApp.call("display", "Importing citgyml. Please wait.");
+				jsApp.call("showRepositoryName", repo.getFileName().toString());
+				jsApp.call("init");
+			});
 
 			task.setOnSucceeded(e -> jsApp.call("ready"));
 
 			new Thread(task).start();
 		}
 
-		public void downloadRegionFromCityGML(String wktPolygon, String project, String citygml, String srsName)
-				throws IOException, ParseException, XPathParseException, NavException, XPathEvalException {
-			StringBuilder sb = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, srsName,
-					citygmlPath(project, citygml));
+		/**
+		 * This method is called from Javascript, with a prepared wktPolygon written in local coordinates.
+		 */
+		public int downloadRegionFromCityGMLs(String wktPolygon, String project, String csvCitygmls, String srsName)
+				throws IOException, ParseException, XPathParseException, NavException {
+			// It doesn't seem possible to pass arrays or list from JS to Java. So csvCitygmls contains names separated by ;
+			Path[] paths = Stream.of(csvCitygmls.split(";")).map(s -> citygmlPath(project, s)).toArray(Path[]::new);
 
-			File buildingIdsFile = selectSaveFileWithDialog(project, citygml, "selected_region");
-			if (buildingIdsFile != null) {
-				try (BufferedWriter writer = Files.newBufferedWriter(buildingIdsFile.toPath())) {
-					//NOTE: isn't there a better way??
-					writer.write(sb.toString());
-				}
+
+			File outputFile = selectSaveFileWithDialog(project,
+					csvCitygmls.replace(";", "_").replace(".gml", ""), "selected_region");
+
+			int count;
+			try (BufferedWriter gmlWriter = Files.newBufferedWriter(outputFile.toPath())) {
+				count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, srsName, gmlWriter, paths);
+			}
+			return count;
+		}
+
+
+		public void selectRepository() {
+			Preferences userPrefs = Preferences.userRoot().node("/eu/simstadt/desktop");
+			String currentRepo = userPrefs.get(PREF_RECENT_REPOSITORY, "../TestRepository");
+
+			DirectoryChooser fileChooser = new DirectoryChooser();
+			Stage mainStage = (Stage) RegionChooserBrowser.this.getScene().getWindow();
+			fileChooser.setTitle("Select Repository");
+			fileChooser.setInitialDirectory(new File(currentRepo));
+			File repoLocation = fileChooser.showDialog(mainStage);
+
+			if (repoLocation != null) {
+				repo = repoLocation.toPath();
+				userPrefs.put(PREF_RECENT_REPOSITORY, repo.toAbsolutePath().toString());
+				LOGGER.info("Repository was set to " + repo);
+				refreshHulls();
+			} else {
+				LOGGER.warning("No repository chosen.");
 			}
 		}
 
diff --git a/src/main/java/eu/simstadt/regionchooser/RegionChooserCLI.java b/src/main/java/eu/simstadt/regionchooser/RegionChooserCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..5bee332f1ff943ab071e3a6fc45bc0c1439831b3
--- /dev/null
+++ b/src/main/java/eu/simstadt/regionchooser/RegionChooserCLI.java
@@ -0,0 +1,140 @@
+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 picocli.CommandLine;
+import picocli.CommandLine.Command;
+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, version = "regionChooser 0.2.9", description = "Extracts a region from one or more citygmls.", sortOptions = false)
+class RegionChooserCLI implements Callable<Integer>
+{
+	@Spec
+	CommandSpec spec;
+
+	//TODO: Add --gui?
+
+	@Option(names = { "-i",
+			"--input" }, required = true, split = ",", description = "Citygml files to extract from", paramLabel = "input.gml")
+	//TODO: Allow folders too?
+	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);
+		} else {
+			try (BufferedWriter gmlWriter = Files.newBufferedWriter(outputCityGML)) {
+				count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, localCRS.toString(), gmlWriter,
+						citygmls);
+			}
+		}
+
+		logInfo("Found buildings : " + count);
+
+		return 0;
+	}
+
+	private void logInfo(String message) {
+		spec.commandLine().getErr().println(message);
+	}
+
+	private static String getInput() {
+		try (Scanner myObj = new Scanner(System.in)) {
+			return myObj.nextLine();
+		}
+	}
+
+	// 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);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/eu/simstadt/regionchooser/RegionChooserFX.java b/src/main/java/eu/simstadt/regionchooser/RegionChooserFX.java
index c1ebd815771d16fcefbedc9bc00e57b6124f7df4..f3910e72303934b82cba278e7bf502bffbd0d344 100644
--- a/src/main/java/eu/simstadt/regionchooser/RegionChooserFX.java
+++ b/src/main/java/eu/simstadt/regionchooser/RegionChooserFX.java
@@ -1,26 +1,29 @@
 package eu.simstadt.regionchooser;
 
-import java.time.LocalDate;
-import java.util.Objects;
 import javafx.application.Application;
 import javafx.scene.Scene;
+import javafx.scene.image.Image;
 import javafx.scene.paint.Color;
 import javafx.stage.Stage;
 
 
 public class RegionChooserFX extends Application
 {
+	public static final Image APP_ICON_48 = new Image(RegionChooserFX.class.getResourceAsStream("simstadt_48.png"));
+
 	/**
 	 * Starting point of RegionChooser application. Either "Run as Java" from Eclipse or run
 	 * "RegionChooser.bat/.sh/.command" from deployed SimStadt folder.
-	 * 
+	 *
 	 * This application is basically just a scene and a browser for the RegionChooser website (HTML + Javascript frontend
 	 * + Java backend). The Java part is written in RegionChooserBrowser.
 	 */
+
 	@Override
 	public void start(Stage stage) {
-		stage.setTitle("RegionChooser " + getApplicationVersion());
+		stage.setTitle("RegionChooser " + RegionChooserUtils.getApplicationVersion());
 		Scene scene = new Scene(new RegionChooserBrowser(), 1024, 720, Color.web("#666970"));
+		stage.getIcons().addAll(APP_ICON_48);
 		stage.setScene(scene);
 		stage.show();
 	}
@@ -28,16 +31,5 @@ public void start(Stage stage) {
 	public static void main(String[] args) {
 		launch(args);
 	}
-
-	/**
-	 * Returns application version, if it has been written in the JAR file during deployment.
-	 * 
-	 * e.g. "0.9.1-SNAPSHOT (rev. 73cbe48e, 2018-07-20)"
-	 */
-	private String getApplicationVersion() {
-		Package regionChooserJar = RegionChooserFX.class.getPackage();
-		return Objects.toString(regionChooserJar.getImplementationVersion(),
-				String.format("development version (%s)", LocalDate.now()));
-	}
 }
 
diff --git a/src/main/java/eu/simstadt/regionchooser/RegionChooserUtils.java b/src/main/java/eu/simstadt/regionchooser/RegionChooserUtils.java
index 4bc38769e0ca51c39611085a31bc46645dca569c..b9e5f5cae20867adadbaa644be2dcd18da339775 100644
--- a/src/main/java/eu/simstadt/regionchooser/RegionChooserUtils.java
+++ b/src/main/java/eu/simstadt/regionchooser/RegionChooserUtils.java
@@ -3,6 +3,8 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.time.LocalDate;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -10,6 +12,9 @@
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.GeometryFactory;
 import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.io.ParseException;
+import org.locationtech.jts.io.WKTReader;
+import org.locationtech.jts.io.WKTWriter;
 import org.osgeo.proj4j.BasicCoordinateTransform;
 import org.osgeo.proj4j.CRSFactory;
 import org.osgeo.proj4j.CoordinateReferenceSystem;
@@ -23,6 +28,7 @@
 	private static final Pattern srsNamePattern = Pattern.compile("(?i)(?<=srsName=[\"'])[^\"']+(?=[\"'])");
 	private static final int CITYGML_HEADER_LENGTH = 50;
 	private static final String EPSG = "EPSG:";
+	private static final int BUFFER = 1024;
 
 	private RegionChooserUtils() {
 		// only static use
@@ -32,18 +38,18 @@ private RegionChooserUtils() {
 	 * The srsName (The name by which this reference system is identified) inside the CityGML file can have multiple
 	 * formats. This method tries to parse the string and detect the corresponding reference system. If it is found, it
 	 * returns a proj4j.CoordinateReferenceSystem. It throws an IllegalArgumentException otherwise.
-	 * 
+	 *
 	 * This method should be able to parse any EPSG id : e.g. "EPSG:1234". German Citygmls might also have "DE_DHDN_3GK3"
 	 * or "ETRS89_UTM32" as srsName, so those are also included. It isn't guaranteed that those formats are correctly
 	 * parsed, though.
-	 * 
+	 *
 	 * The EPSG ids and parameters are defined in resources ('nad/epsg') inside proj4j-0.1.0.jar. Some EPSG ids are
 	 * missing though, e.g. 7415
-	 * 
+	 *
 	 * @param srsName
 	 * @return CoordinateReferenceSystem
 	 */
-	private static CoordinateReferenceSystem crsFromSrsName(String srsName) {
+	public static CoordinateReferenceSystem crsFromSrsName(String srsName) {
 		// EPSG:31467
 		Pattern pEPSG = Pattern.compile("^(EPSG:\\d+)$");
 		Matcher mEPSG = pEPSG.matcher(srsName);
@@ -85,9 +91,9 @@ private static CoordinateReferenceSystem crsFromSrsName(String srsName) {
 
 	/**
 	 * Converts a jts.geom.Polygon from one CoordinateReferenceSystem to another.
-	 * 
+	 *
 	 * NOTE: It would be easier with org.geotools.referencing.CRS instead of Proj4J
-	 * 
+	 *
 	 * @param polygonInOriginalCRS
 	 * @param originalCRS
 	 * @param newCRS
@@ -107,12 +113,21 @@ public static Polygon changePolygonCRS(Polygon polygonInOriginalCRS, CoordinateR
 		return geometryFactory.createPolygon(convexHullcoordinates);
 	}
 
+	public static String wktPolygonToLocalCRS(String wktPolygonInWGS84, CoordinateReferenceSystem localCRS)
+			throws ParseException {
+		final WKTReader wktReader = new WKTReader();
+		final WKTWriter wktWriter = new WKTWriter();
+		// WKT coordinates are in WGS84, so should be first converted to srsName
+		Polygon wgs84Polygon = (Polygon) wktReader.read(wktPolygonInWGS84);
+		return wktWriter.write(changePolygonCRS(wgs84Polygon, WGS84, localCRS));
+	}
+
 	/**
-	 * 
+	 *
 	 * Fast scan of the 50 first lines of a Citygml file to look for srsName. It might not be as reliable as parsing the
 	 * whole CityGML, but it should be much faster and use much less memory. For a more reliable solution, use
 	 * GeoCoordinatesAccessor. This solution can be convenient for Web services, RegionChooser or HullExtractor.
-	 * 
+	 *
 	 * @param citygmlPath
 	 * @return
 	 * @throws IOException
@@ -132,7 +147,7 @@ public static CoordinateReferenceSystem crsFromCityGMLHeader(Path citygmlPath) t
 
 	/**
 	 * Finds every CityGML in every .proj folder in a repository.
-	 * 
+	 *
 	 * @param repository
 	 * @return a stream of CityGML Paths
 	 * @throws IOException
@@ -146,4 +161,14 @@ public static Stream<Path> everyCityGML(Path repository) throws IOException {
 								p.toString().toLowerCase().endsWith(".gml"));
 	}
 
+	/**
+	 * Returns application version, if it has been written in the JAR file during deployment.
+	 *
+	 * e.g. "0.9.1-SNAPSHOT (rev. 73cbe48e, 2018-07-20)"
+	 */
+	public static String getApplicationVersion() {
+		Package regionChooserJar = RegionChooserFX.class.getPackage();
+		return Objects.toString(regionChooserJar.getImplementationVersion(),
+				String.format("development version (%s)", LocalDate.now()));
+	}
 }
diff --git a/src/main/java/eu/simstadt/regionchooser/RegionExtractor.java b/src/main/java/eu/simstadt/regionchooser/RegionExtractor.java
index e7889390f205cad30504e088edfb636cc8222621..316d378bd2c5c92365adeebd7f5d539122746a65 100644
--- a/src/main/java/eu/simstadt/regionchooser/RegionExtractor.java
+++ b/src/main/java/eu/simstadt/regionchooser/RegionExtractor.java
@@ -1,6 +1,7 @@
 package eu.simstadt.regionchooser;
 
 import java.io.IOException;
+import java.io.Writer;
 import java.nio.file.Path;
 import java.util.logging.Logger;
 import java.util.regex.Matcher;
@@ -21,10 +22,10 @@
 
 public class RegionExtractor
 {
-	private static final WKTReader wktReader = new WKTReader();
+	private static final WKTReader WKT_READER = new WKTReader();
 
 	private static final Logger LOGGER = Logger.getLogger(RegionExtractor.class.getName());
-	private static final GeometryFactory gf = new GeometryFactory();
+	private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
 
 	/**
 	 * Main method behind RegionChooser. Given CityGMLs (as Path[]) and a geometry (as Well-known text POLYGON, in the
@@ -34,10 +35,12 @@
 	 * 
 	 * @param wktPolygon
 	 * @param srsName
+	 * @param output
 	 * @param citygmlPaths
 	 * 
 	 * 
-	 * @return a StringBuffer, full with the extracted Citygml, including header, buildings and footer.
+	 * @writes the extracted Citygml, including header, buildings and footer to output
+	 * @return counts of found building.
 	 * @throws ParseException
 	 * @throws IOException
 	 * @throws XPathEvalException
@@ -45,13 +48,12 @@
 	 * @throws XPathParseException
 	 * @throws NumberFormatException
 	 */
-	static StringBuilder selectRegionDirectlyFromCityGML(String wktPolygon, String srsName, Path... citygmlPaths)
-			throws ParseException, XPathParseException, NavException, IOException {
+	static int selectRegionDirectlyFromCityGML(String wktPolygon, String srsName, Writer sb,
+			Path... citygmlPaths) throws ParseException, XPathParseException, NavException, IOException {
 
 		int buildingsCount = 0;
 		int foundBuildingsCount = 0;
-		StringBuilder sb = new StringBuilder();
-		Geometry poly = wktReader.read(wktPolygon);
+		Geometry poly = WKT_READER.read(wktPolygon);
 
 		CityGmlIterator citygml = null;
 		for (int i = 0; i < citygmlPaths.length; i++) {
@@ -65,7 +67,7 @@ static StringBuilder selectRegionDirectlyFromCityGML(String wktPolygon, String s
 				buildingsCount += 1;
 				if (buildingXmlNode.hasCoordinates()) {
 					Coordinate coord = new Coordinate(buildingXmlNode.x, buildingXmlNode.y);
-					Point point = gf.createPoint(coord);
+					Point point = GEOMETRY_FACTORY.createPoint(coord);
 					if (point.within(poly)) {
 						foundBuildingsCount++;
 						sb.append(buildingXmlNode.toString());
@@ -75,16 +77,19 @@ static StringBuilder selectRegionDirectlyFromCityGML(String wktPolygon, String s
 					LOGGER.info("1000 buildings parsed");
 				}
 			}
-
 		}
 
 		if (citygml == null) {
 			throw new IllegalArgumentException("There should be at least one citygml");
 		}
 
+		if (foundBuildingsCount == 0) {
+			LOGGER.warning("No building found in the selected region.");
+		}
+
 		LOGGER.info("Buildings found in selected region " + foundBuildingsCount);
 		sb.append(citygml.getFooter());
-		return sb;
+		return foundBuildingsCount;
 	}
 
 	/**
diff --git a/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/BuildingXmlNode.java b/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/BuildingXmlNode.java
index 8a752b099217df0f2d07910324f524679c2f33c5..1c2098534fe5b3d61387a006b25fbc75696607c2 100644
--- a/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/BuildingXmlNode.java
+++ b/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/BuildingXmlNode.java
@@ -28,7 +28,8 @@ public BuildingXmlNode(VTDNav navigator, int buildingOffset, int buildingLength)
 		this.coordinatesFinder = new AutoPilot(navigator);
 		this.buildingLength = buildingLength;
 		this.buildingOffset = buildingOffset;
-		extractCoordinates(); //NOTE: Should it be done lazily? Is there any reason to extract a BuildingXmlNode without coordinates?
+		extractCoordinates();
+		//TODO: Get Building ID too, in order to avoid duplicates?
 	}
 
 	public boolean hasCoordinates() {
diff --git a/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/CityGmlIterator.java b/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/CityGmlIterator.java
index b165ffac0bd9dd1e5dbd3121d243536c0b05a938..90af5396ef7a9763a5cfea3ea2ec9d496fe3234d 100644
--- a/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/CityGmlIterator.java
+++ b/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/CityGmlIterator.java
@@ -97,7 +97,7 @@ public String getHeader() throws NavException {
 	 * @return Citygml footer
 	 * @throws NavException
 	 */
-	public Object getFooter() throws IOException, NavException {
+	public String getFooter() throws IOException, NavException {
 		int footerOffset = buildingOffset + buildingLength;
 		int footerLength = (int) (Files.size(citygmlPath) - footerOffset);
 		return navigator.toRawString(footerOffset, footerLength);
diff --git a/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/ConvexHullCalculator.java b/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/ConvexHullCalculator.java
index 299110d86e487fb0b9607ae7694098de5b3d1ab6..4e9e6b99cc3c46978cf12c61f9500854449d42c7 100644
--- a/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/ConvexHullCalculator.java
+++ b/src/main/java/eu/simstadt/regionchooser/fast_xml_parser/ConvexHullCalculator.java
@@ -92,6 +92,7 @@ public static void extractHullsForEveryCityGML(Path repository, Consumer<String>
 					try {
 						Path kmlPath = getHullPath(repository, gmlPath);
 						if (Files.exists(kmlPath)) {
+							//TODO: Check if size is the same as original. Recreate otherwise.
 							LOGGER.fine("Using cache from " + repository.relativize(kmlPath));
 							return new String(Files.readAllBytes(kmlPath), StandardCharsets.UTF_8);
 						} else {
diff --git a/src/main/resources/eu/simstadt/regionchooser/simstadt_48.png b/src/main/resources/eu/simstadt/regionchooser/simstadt_48.png
new file mode 100644
index 0000000000000000000000000000000000000000..b25a17ccbbc1ff25b2b39134193a3484f2d8fcef
Binary files /dev/null and b/src/main/resources/eu/simstadt/regionchooser/simstadt_48.png differ
diff --git a/src/main/resources/eu/simstadt/regionchooser/website/index.html b/src/main/resources/eu/simstadt/regionchooser/website/index.html
index 6cfa47b6794999275492e980202aa2d520f6f9ae..141a41524beaf026e85be61ea9e093e2ee7f7729 100644
--- a/src/main/resources/eu/simstadt/regionchooser/website/index.html
+++ b/src/main/resources/eu/simstadt/regionchooser/website/index.html
@@ -5,9 +5,8 @@
 		<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
 		<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
 		<title>Simstadt Region Chooser</title>
-		<meta name="keywords" content="polygon,creator,google map,v3,draw,paint"/>
-		<meta name="description"
-			content="Google Map V3 Polygon Creator for Simstadt"/>
+		<meta name="keywords" content="region chooser,citygml,polygon,creator,extract,simstadt,buildings"/>
+		<meta name="description" content="RegionChooser for Simstadt"/>
 
 		<link rel="stylesheet" type="text/css" href="style/style.css"/>
 		<!-- Firebug for js console:
@@ -24,7 +23,8 @@
 	<body>
 		<div id="header">
 			<ul>
-				<li class="title">Simstadt Region Chooser</li>
+				<li class="title"><span id="repo_path">RegionChooser</span></li>
+				<li class="title" id="select_repository" style="visibility:hidden"><button onclick="regionChooser.selectRepository()">Select repository</button></li>
 			</ul>
 		</div>
 		<div id="map" class="map" tabindex="0"></div>
diff --git a/src/main/resources/eu/simstadt/regionchooser/website/script/simstadt_openlayers.js b/src/main/resources/eu/simstadt/regionchooser/website/script/simstadt_openlayers.js
index 6cc760c02ffba35c93b482f229ffbd545d60f2e7..c06cf11ee740b22f80be4084d6c6b65eb9c7c4a4 100644
--- a/src/main/resources/eu/simstadt/regionchooser/website/script/simstadt_openlayers.js
+++ b/src/main/resources/eu/simstadt/regionchooser/website/script/simstadt_openlayers.js
@@ -1,12 +1,20 @@
 var regionChooser = (function(){
+	//TODO: Somehow split in classes. This file is getting too big and mixed
 	var publicScope = {};
 	var fromJavaFX = navigator.userAgent.indexOf('JavaFX') !== -1;
 	var dataPanel = $('#dataPanel');
 	var wgs84Sphere = new ol.Sphere(6378137);
-	var gmlId = 0;
+	var gmlId;
+	
+	publicScope.init = function(){
+		//NOTE: Only called from JavaFX. At startup, or when Repo has been changed.
+		gmlId = 0;
+		kml_source.clear();
+		document.getElementById("select_repository").style.visibility = "visible";
+	}
 	
 	if (fromJavaFX){
-		$("html").addClass("wait");
+		document.documentElement.className = 'wait';
 	}
 
 	var osm_layer = new ol.layer.Tile({
@@ -59,9 +67,9 @@ var regionChooser = (function(){
 		feature.setId(gmlId++);
 		kml_source.addFeature(feature);
 		dataPanel.append('.');
-		srsName = feature.get("srsName") || "EPSG:31467";
+		srsName = feature.get("srsName");
 		if (proj4.defs(srsName) === undefined){
-			console.warning(srsName + " isn't defined by Proj4js!")
+			console.warn(srsName + " isn't defined by Proj4js!")
 		}
 	};
 	
@@ -154,12 +162,8 @@ var regionChooser = (function(){
 		var sketch_percentage = Math.round(intersectionArea / polygonArea * 100);
 		intersections.addFeature(intersection);
 		var link = '<li>'
-		if (fromJavaFX) {
-			link += "<a href=\"#\" onclick=\"regionChooser.downloadRegionFrom" + feature["source"] + "(" + feature.getId()
-					+ ");return false;\">" + feature["name"] + "</a>";
-		} else {
-			link += feature['name'];
-		}
+		// TODO: If possible, highlight the corresponding polygon when hovering above a name.
+		link += '<input type="checkbox" id="citygml_' + feature.getId() + '" class="select_citygml" onclick="regionChooser.isDownloadPossible();"><label for="citygml_' + feature.getId() + '">' +  feature['name'] + '</label>';
 
 		link += " (" + citygml_percentage + "%";
 		if (sketch_percentage == 100) {
@@ -168,6 +172,11 @@ var regionChooser = (function(){
 		dataPanel.append(link + ")\n");
 	}
 	
+	publicScope.isDownloadPossible = function(){
+		var checkedBoxes = Array.from(document.querySelectorAll("input.select_citygml")).filter(c => c.checked);
+		document.getElementById("download_region_button").disabled = (checkedBoxes.length == 0);
+	}
+	
 	function findIntersection(feature, polygon) {
 		try {
 			return turf.intersect(polygon, feature["geoJSON"]);
@@ -181,19 +190,24 @@ var regionChooser = (function(){
 		var polygonArea = sketch.getGeometry().getArea();
 		var intersection_found = false;
 		intersections.clear();
+		//NOTE: getFeatures seems to not be sorted anymore. :-/
 		features_by_project = groupBy(kml_source.getFeatures(), "project");
 		
-		Object.keys(features_by_project).sort().forEach(function(project) {
-		    features = features_by_project[project];
-		    features_and_intersections = features.map(f=> [f, findIntersection(f,polygon)]).filter(l => l[1] !== undefined);
-		    if (features_and_intersections.length > 0){
-		    	intersection_found = true;
+		Object.keys(features_by_project).forEach(function(project) {
+			features = features_by_project[project];
+			features_and_intersections = features.map(f=> [f, findIntersection(f,polygon)]).filter(l => l[1] !== undefined);
+			if (features_and_intersections.length > 0){
+				intersection_found = true;
 				dataPanel.append("<h2 class='info'>" + project);
-		    	features_and_intersections.forEach(l => showLinkToDownload(l[0], l[1], polygonArea));
-		    }
+				features_and_intersections.forEach(l => showLinkToDownload(l[0], l[1], polygonArea));
+			}
 		});
 		
-		if (!intersection_found) {
+		if (intersection_found) {
+			document.getElementById("download_region").style.visibility = 'visible';
+		}
+		else {
+			document.getElementById("download_region").style.visibility = 'hidden';
 			dataPanel.append("No intersection found with any CityGML file.<br/>\n");
 		}
 	}
@@ -202,28 +216,45 @@ var regionChooser = (function(){
 		dataPanel.append(text + "<br/>\n");
 	}
 	
-	publicScope.downloadRegionFromCityGML = function(i) {
-		// TODO: Disable all links
-		// TODO: DRY
-		var feature = kml_source.getFeatureById(i);
+	publicScope.downloadRegionFromCityGMLs = function(checkbox_ids) {
+		var features = checkbox_ids.map(checkbox_id => {
+			var i = Number(checkbox_id.replace("citygml_", ""));
+			return kml_source.getFeatureById(i);
+		})
+		
+		var project = features[0].get("project");
+		var srsName = features[0].get("srsName");
+		
+		if (!features.every( f => f.get("project") === project)){
+			dataPanel.prepend("<h2 class='error'>Sorry, the CityGML files should all belong to the same project.</h2><br/>\n");
+			return;
+		}
+		
+		if (!features.every( f => f.get("srsName") === srsName)){
+			dataPanel.prepend("<h2 class='error'>Sorry, the CityGML files should all be written with the same coordinate system.</h2><br/>\n");
+		}
+		
+		document.documentElement.className = 'wait';
+		
+		var citygmlNames = features.map(f => f.get("name"));
 		// Waiting 100ms in order to let the cursor change
 		setTimeout(function() {
 			var start = new Date().getTime();
-			var srsName = feature.get("srsName") || "EPSG:31467";
 			if (proj4.defs(srsName)){
-				$("html").addClass("wait");
 				console.log("Selected region is written in " + srsName + " coordinate system.");
 				try {
-					fxapp.downloadRegionFromCityGML(sketchAsWKT(srsName), feature.get("project"), feature.get("name"), srsName);
-					dataPanel.append("<h2 class='ok'>Done!</h2><br/>\n");
+					var count = fxapp.downloadRegionFromCityGMLs(sketchAsWKT(srsName), project, citygmlNames.join(";"), srsName);
+					dataPanel.prepend("<h2 class='ok'>Done! (" + count + " buildings found) </h2><br/>\n");
 				} catch (e) {
-					dataPanel.append("<h2 class='error'>Some problem occured!</h2><br/>\n");
+					console.warn("ERROR : " + e);
+					dataPanel.prepend("<h2 class='error'>Some problem occured!</h2><br/>\n");
 				}
 				var end = new Date().getTime();
 				var time = end - start;
 				console.log('Download Execution time: ' + (time / 1000).toFixed(3) + 's');
 				setTimeout(function() {
-					$("html").removeClass("wait");
+					document.getElementById("download_region_button").disabled = false;
+					document.documentElement.className = ''; // Stop waiting
 				}, 100);
 			} else {
 				var msg = "ERROR : Unknown coordinate system : \"" + srsName + "\". Cannot extract any region";
@@ -235,24 +266,18 @@ var regionChooser = (function(){
 
 	function displayInfo() {
 		dataPanel.empty();
-		var geom = /** @type {ol.geom.Polygon} */
-		(sketch.getGeometry().clone().transform(sourceProj, 'EPSG:4326'));
+		var geom = sketch.getGeometry().clone().transform(sourceProj, 'EPSG:4326');
 		var coordinates = geom.getLinearRing(0).getCoordinates();
 		var area = Math.abs(wgs84Sphere.geodesicArea(coordinates));
-		var coords = geom.getLinearRing(0).getCoordinates();
-		if (!fromJavaFX) {
-			var wgs84_coords = "";
-			var n = coords.length;
-			for (var i = 0; i < n; i++) {
-				var wgs84_coord = coords[i];
-				wgs84_coords += "(" + wgs84_coord[1] + "," + wgs84_coord[0] + ")<br/>";
-
-			}
-			dataPanel.append("WGS84 Coordinates<br/>");
-			dataPanel.append(wgs84_coords + "<br/>\n");
-		}
+		//NOTE: Could show m², ha or km² depending on magnitude
 		dataPanel.append("<h3 class='clean'>Area : " + (area / 10000).toFixed(1) + " ha\n");
+		dataPanel.append('<div style="visibility:hidden" id="download_region">' + 
+			'<button type="button" onclick="regionChooser.downloadFromSelectedCityGMLs()" id="download_region_button" disabled>Download Region</button><br/>\n' +
+			'<a href="#" onclick="regionChooser.checkCityGMLS(true);">(Select All)</a>\n' +
+			'<a href="#" onclick="regionChooser.checkCityGMLS(false);">(Select None)</a>\n'+
+			'</div>\n');
 		findIntersections();
+		dataPanel.append('<button type="button" onclick="regionChooser.copyCoordinatesToClipboard()" id="get_wgs84">Copy coordinates</button><br/>\n')
 	}
 
 	draw.on('drawend', function() {
@@ -274,7 +299,7 @@ var regionChooser = (function(){
 			draw.finishDrawing();
 		} finally {
 			displayHelp();
-			$("html").removeClass("wait");
+			document.documentElement.className = ''; // Stop waiting
 			draw.setActive(true);
 			featureOverlay.getFeatures().clear();
 			intersections.clear();
@@ -283,7 +308,6 @@ var regionChooser = (function(){
 	}
 	
 	function sketchAsWKT(srsName) {
-		srsName = (typeof srsName === 'undefined') ? 'EPSG:4326' : srsName;
 		var wktFormat = new ol.format.WKT();
 		return wktFormat.writeFeature(sketch, {
 			dataProjection : ol.proj.get(srsName),
@@ -292,7 +316,7 @@ var regionChooser = (function(){
 	}
 	
 	function focusOnMap() {
-		$('#map').focus();
+		document.getElementById("map").focus();
 	}
 	
 	var fxapp = undefined;
@@ -303,7 +327,7 @@ var regionChooser = (function(){
 			fxapp.log(message);
 		}
 		
-		console.warning = function(message){
+		console.warn = function(message){
 			fxapp.warning(message);
 		}
 	}
@@ -322,16 +346,83 @@ var regionChooser = (function(){
 		dataPanel.append("You can add a new point to an existing edge by clicking and dragging.<br>\n");
 		dataPanel.append("You can remove a point with SHIFT + clicking.<br>\n");
 		dataPanel.append("You can cancel drawing with ESC or DEL.<br><br>\n");
-		dataPanel.append("After drawing a polygon which intersects with a GML file, you can download the corresponding part by clicking on the filename.<br>\n");
+		dataPanel.append("After drawing a polygon which intersects with at least one GML file,<br>\n");
+		dataPanel.append("you can download the corresponding part by checking the<br>\n");
+		dataPanel.append("desired filenames and clicking on 'Download' button.<br>\n");
+		dataPanel.append("<br>\n");
+		dataPanel.append("More info is available in the ");
+		dataPanel.append("<a href='http://simstadt.hft-stuttgart.de/related-softwares/region-chooser/'>SimStadt documentation</a><br>\n");
 	}
 
 	// Executed by JavaFX when whole page is loaded.
 	publicScope.ready = function() {
 		updateGMLPolygons();
 		displayHelp();
-		$("html").removeClass("wait");
+		document.documentElement.className = ''; // Stop waiting
 		console.log("READY!");
 	}
+	
+	publicScope.downloadFromSelectedCityGMLs = function() {
+		document.getElementById("download_region_button").disabled = true;
+		var checkedBoxes = Array.from(document.querySelectorAll("input.select_citygml")).filter(c => c.checked);
+		// CheckBoxes isn't empty, because otherwise the button cannot be clicked.
+		publicScope.downloadRegionFromCityGMLs(checkedBoxes.map(c => c.id));
+	}
+	
+	publicScope.checkCityGMLS = function(allOrNone) {
+		document.querySelectorAll("input.select_citygml").forEach(c => c.checked = allOrNone);
+		publicScope.isDownloadPossible();
+	}
+	
+	publicScope.selectRepository = function() {
+		fxapp.selectRepository();
+	}
+	
+	publicScope.copyCoordinatesToClipboard = function(){
+		var geom = sketch.getGeometry().clone().transform(sourceProj, 'EPSG:4326');
+		var wgs84Coords = geom.getLinearRing(0).getCoordinates();
+		var wktPolygon = "POLYGON((";
+		wktPolygon += wgs84Coords.map(lonLat => lonLat.join(" ")).join(", ");
+		publicScope.copyToClipboard(wktPolygon + "))");
+	}
+	
+	// Copies a string to the clipboard. Must be called from within an
+	// event handler such as click. May return false if it failed, but
+	// this is not always possible. Browser support for Chrome 43+,
+	// Firefox 42+, Safari 10+, Edge and Internet Explorer 10+.
+	// Internet Explorer: The clipboard feature may be disabled by
+	// an administrator. By default a prompt is shown the first
+	// time the clipboard is used (per session).
+	// https://stackoverflow.com/a/33928558/6419007
+	publicScope.copyToClipboard = function(text) {
+	if (window.clipboardData && window.clipboardData.setData) {
+		// Internet Explorer-specific code path to prevent textarea being shown while dialog is visible.
+		return window.clipboardData.setData("Text", text);
+	}
+	else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
+		var textarea = document.createElement("textarea");
+		textarea.textContent = text;
+		textarea.style.position = "fixed";  // Prevent scrolling to bottom of page in Microsoft Edge.
+		document.body.appendChild(textarea);
+		textarea.select();
+		try {
+			document.execCommand("copy");  // Security exception may be thrown by some browsers.
+			dataPanel.append("<h2 class='ok'>Coordinates copied to clipboard!</h2><br/>\n");
+			return;
+		}
+		catch (ex) {
+			console.warn("Copy to clipboard failed.", ex);
+			return prompt("Copy to clipboard: Ctrl+C, Enter", text);
+		}
+		finally {
+			document.body.removeChild(textarea);
+		}
+	}
+}
+	
+	publicScope.showRepositoryName = function(path) {
+		document.getElementById("repo_path").textContent = path;
+	}
 
 	focusOnMap();
 	//var regionChooser = publicScope; //NOTE: In order to open closure. For debugging
diff --git a/src/main/resources/eu/simstadt/regionchooser/website/style/style.css b/src/main/resources/eu/simstadt/regionchooser/website/style/style.css
index d47cf34b8552df144cc5b559c6c25152aa4cd3b7..5e748cf692c8a1293d9464678319a0e8e2976ba0 100644
--- a/src/main/resources/eu/simstadt/regionchooser/website/style/style.css
+++ b/src/main/resources/eu/simstadt/regionchooser/website/style/style.css
@@ -130,10 +130,6 @@ div#dataPanel li {
     line-height: 1;
 }
 
-div#side input{
-	width:90%;
-}
-
 div#side input.navi{
 	font-size:18px;
 	height:30px;
diff --git a/src/test/java/eu/simstadt/regionchooser/RegionChooserCLITests.java b/src/test/java/eu/simstadt/regionchooser/RegionChooserCLITests.java
new file mode 100644
index 0000000000000000000000000000000000000000..81722644717bcc3dfa219cb0cc1024019e849f10
--- /dev/null
+++ b/src/test/java/eu/simstadt/regionchooser/RegionChooserCLITests.java
@@ -0,0 +1,151 @@
+package eu.simstadt.regionchooser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.jupiter.api.AfterEach; // JUnit 5
+import org.junit.jupiter.api.BeforeEach; // JUnit 5
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import picocli.CommandLine;
+
+
+class RegionChooserCLITests
+{
+	final PrintStream originalOut = System.out;
+	final PrintStream originalErr = System.err;
+	final ByteArrayOutputStream out = new ByteArrayOutputStream();
+	final ByteArrayOutputStream err = new ByteArrayOutputStream();
+
+	private static final Path TEST_REPOSITORY = Paths.get("src/test/resources/testdata/");
+
+
+	@TempDir
+	Path tempDirectory;
+	Path outGML;
+
+	@BeforeEach
+	public void setUp() {
+		out.reset();
+		err.reset();
+		System.setOut(new PrintStream(out));
+		System.setErr(new PrintStream(err));
+		outGML = tempDirectory.resolve("output.gml");
+	}
+
+	@AfterEach
+	public void restore() throws IOException {
+		System.setOut(originalOut);
+		System.setErr(originalErr);
+		Files.deleteIfExists(outGML);
+	}
+
+	@Test
+	void testNoInput() {
+		new CommandLine(new RegionChooserCLI()).execute("");
+		String expectedErr = "Missing required options: '--input=input.gml', '--output=output.gml', '--wkt=polygon.wkt'";
+		assertTrue(err.toString().contains(expectedErr), err.toString() + " should contain " + expectedErr);
+	}
+
+	@Test
+	void testExtractRegionFromTwoCitygmls() throws IOException {
+		String wktPolygon = "POLYGON((3512984.7003764412 5405148.310572891,3513038.6360455155 5405010.072163861,3513142.7277745553 5405004.02571992,3514204.1661769524 5405563.192081669,3514399.2818417274 5405720.905457244,3514291.6158155007 5405896.706492759,3512984.7003764412 5405148.310572891))";
+		Path citygml1 = TEST_REPOSITORY.resolve("Stuttgart.proj/Stuttgart_LOD0_LOD1_small.gml");
+		Path citygml2 = TEST_REPOSITORY.resolve("Stuttgart.proj/Stöckach_überarbeitete GML-NoBuildingPart.gml");
+		Path inWKT = tempDirectory.resolve("polygon.wkt");
+		try (BufferedWriter wkt = Files.newBufferedWriter(inWKT)) {
+			wkt.write(wktPolygon);
+		}
+		assertFalse(Files.exists(outGML));
+		new CommandLine(new RegionChooserCLI()).execute("--input=" + citygml1 + "," + citygml2,
+				"--output=" + outGML, "--wkt=" + inWKT, "--epsg=31463", "--local");
+		assertTrue(Files.exists(outGML));
+		assertTrue(Files.size(outGML) > 600_000);
+		assertEquals(20, countBuildings(outGML));
+	}
+
+
+	@Test
+	void testExtractRegionFromTwoCitygmlsInWGS84() throws IOException {
+		String wktPolygon = "POLYGON((9.18991539528286 48.79067456353269, 9.195580220722313 48.7873947720947, 9.172062612079735 48.78241812742368, 9.170946813129538 48.78569824412753, 9.18991539528286 48.79067456353269))";
+		Path citygml1 = TEST_REPOSITORY.resolve("Stuttgart.proj/Stuttgart_LOD0_LOD1_small.gml");
+		Path citygml2 = TEST_REPOSITORY.resolve("Stuttgart.proj/Stöckach_überarbeitete GML-NoBuildingPart.gml");
+		Path inWKT = tempDirectory.resolve("polygon.wkt");
+		try (BufferedWriter wkt = Files.newBufferedWriter(inWKT)) {
+			wkt.write(wktPolygon);
+		}
+		assertFalse(Files.exists(outGML));
+		new CommandLine(new RegionChooserCLI()).execute("--input=" + citygml1 + "," + citygml2,
+				"--output=" + outGML, "--wkt=" + inWKT);
+		String expectedLog = "EPSG:31463";
+		assertTrue(err.toString().contains(expectedLog), err.toString() + " should contain " + expectedLog);
+		assertTrue(Files.exists(outGML));
+		assertTrue(Files.size(outGML) > 300_000);
+		assertEquals(22, countBuildings(outGML));
+	}
+
+	@Test
+	void testExtractRegionWithStandardInput() throws IOException {
+		String wktPolygon = "POLYGON((-73.9959209576448 40.73286384885367, -73.996317924579 40.732359794090684, -73.9947515145143 40.7315061442504, -73.99422580154739 40.73214841515045, -73.9959209576448 40.73286384885367))";
+		Path citygml = TEST_REPOSITORY.resolve("NewYork.proj/ManhattanSmall.gml");
+		InputStream stdin = new ByteArrayInputStream(wktPolygon.getBytes(StandardCharsets.UTF_8));
+		System.setIn(stdin);
+		assertFalse(Files.exists(outGML));
+		new CommandLine(new RegionChooserCLI()).execute("--input=" + citygml, "--output=" + outGML,
+				"--wkt=-");
+		String expectedLog = "EPSG:32118";
+		assertTrue(err.toString().contains(expectedLog), err.toString() + " should contain " + expectedLog);
+		assertTrue(Files.exists(outGML));
+		assertEquals(2, countBuildings(outGML));
+	}
+
+	@Test
+	void testExtractRegionWithStandardInputAndStandardOutput() throws IOException {
+		String wktPolygon = "POLYGON((-73.99325421344473 40.730897087489666, -73.99359753619864 40.7304702545556, -73.99287870418264 40.7300800049056, -73.99244955074026 40.730592207101864, -73.99325421344473 40.730897087489666))";
+		Path citygml = TEST_REPOSITORY.resolve("NewYork.proj/ManhattanSmall.gml");
+		InputStream stdin = new ByteArrayInputStream(wktPolygon.getBytes(StandardCharsets.UTF_8));
+		System.setIn(stdin);
+		Path noOutput = Paths.get("-");
+		Files.deleteIfExists(noOutput);
+		assertFalse(Files.exists(noOutput));
+		new CommandLine(new RegionChooserCLI()).execute("--input=" + citygml, "--output=-", "--wkt=-");
+		String expectedLog = "EPSG:32118";
+		assertTrue(err.toString().contains(expectedLog), err.toString() + " should contain " + expectedLog);
+		String expectedBuilding = "uuid_0547df65-ae80-459e-bb15-c839c1a2e566";
+		assertTrue(out.toString().contains(expectedBuilding), out.toString() + " should contain " + expectedBuilding);
+		//TODO: Check if footer is here too
+		assertFalse(Files.exists(noOutput));
+	}
+
+	@Test
+	void testExtractRegionWithMissingInput() throws IOException {
+		String wktPolygon = "POLYGON((-73.9959209576448 40.73286384885367, -73.996317924579 40.732359794090684, -73.9947515145143 40.7315061442504, -73.99422580154739 40.73214841515045, -73.9959209576448 40.73286384885367))";
+		Path citygml = TEST_REPOSITORY.resolve("NewYork.proj/ManhattanSmall.gml");
+		Path inWKT = tempDirectory.resolve("polygon.wkt");
+		try (BufferedWriter wkt = Files.newBufferedWriter(inWKT)) {
+			wkt.write(wktPolygon);
+		}
+		new CommandLine(new RegionChooserCLI()).execute("--input=" + citygml, "--output=" + outGML,
+				"--wkt=-");
+		String expectedLog = "EPSG:32118";
+		assertTrue(err.toString().contains(expectedLog), err.toString() + " should contain " + expectedLog);
+		expectedLog = "Please provide \"POLYGON((x1 y1, x2 y2, ...))\" to standard input.";
+		assertTrue(err.toString().contains(expectedLog), err.toString() + " should contain " + expectedLog);
+		assertFalse(Files.exists(outGML));
+	}
+
+	private long countBuildings(Path outGML) throws IOException {
+		return Files.readAllLines(outGML).stream().filter(line -> line.contains("bldg:Building gml:id=")).count();
+	}
+}
+
diff --git a/src/test/java/eu/simstadt/regionchooser/RegionExtractorTests.java b/src/test/java/eu/simstadt/regionchooser/RegionExtractorTests.java
index bbac7f07ea45297ce215965f0d16d1e8ccaff5e9..ada5e84ca5d0ef745fa5251640ad0a9c4aa31cc5 100644
--- a/src/test/java/eu/simstadt/regionchooser/RegionExtractorTests.java
+++ b/src/test/java/eu/simstadt/regionchooser/RegionExtractorTests.java
@@ -3,6 +3,7 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.StringWriter;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.regex.Matcher;
@@ -35,8 +36,10 @@ void testExtract3BuildingsFromGSK3Model() throws Throwable {
 		//NOTE: Small region around Martinskirche in Grünbühl
 		String wktPolygon = "POLYGON((3515848.896028535 5415823.108586172,3515848.9512289143 5415803.590347393,3515829.0815150724 5415803.338023346,3515830.9784850604 5415793.437034622,3515842.0946056456 5415793.272282251,3515843.3515515197 5415766.204935087,3515864.1064344468 5415766.557899496,3515876.489172751 5415805.433782301,3515876.343844858 5415822.009293416,3515848.896028535 5415823.108586172))";
 		Path citygmlPath = TEST_REPOSITORY.resolve("Gruenbuehl.proj/20140218_Gruenbuehl_LOD2.gml");
-		String churchGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, "EPSG:31467", citygmlPath)
-				.toString();
+		StringWriter gmlWriter = new StringWriter();
+		int count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, "EPSG:31467", gmlWriter, citygmlPath);
+		String churchGMLString = gmlWriter.toString();
+		assertEquals(3, count);
 		assertEquals(3, countRegexMatches(churchGMLString, CITY_OBJECT_MEMBER_REGEX));
 		assertTrue(churchGMLString.contains("Donaustr"));
 		assertTrue(churchGMLString.contains("DEBW_LOD2_203056"));
@@ -53,30 +56,29 @@ void testExtract3BuildingsFromGSK3Model() throws Throwable {
 	}
 
 	@Test
-	void testExtractBuildingsWithoutCommentsInBetween() throws Throwable {
-		//NOTE: Small region around WashingtonSquare
+	void testExtractBuildingsWithCommentsInBetween() throws Throwable {
 		String wktPolygon = "POLYGON((300259.78663489706 62835.835907766595,300230.33294975647 62792.0482567884,300213.5667431851 62770.83143720031,300183.6592861123 62730.20347659383,300252.9947486632 62676.938468840905,300273.3862256562 62701.767105345614,300257.5250407747 62715.760413539596,300308.2754543957 62805.14198211394,300259.78663489706 62835.835907766595))";
 		Path citygmlPath = TEST_REPOSITORY.resolve("NewYork.proj/ManhattanSmall.gml");
-		String archGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, EPSG_32118, citygmlPath)
-				.toString();
-		assertEquals(countRegexMatches(archGMLString, CITY_OBJECT_MEMBER_REGEX), 2);
+		StringWriter gmlWriter = new StringWriter();
+		int count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, EPSG_32118, gmlWriter, citygmlPath);
+		String archGMLString = gmlWriter.toString();
+		assertEquals(2, count);
+		assertEquals(2, countRegexMatches(archGMLString, CITY_OBJECT_MEMBER_REGEX));
 		assertTrue(archGMLString.contains("WASHINGTON SQUARE"));
 		assertTrue(archGMLString.contains("uuid_c0980a6e-05ea-4d09-bc83-efab226945a1"));
 		assertTrue(archGMLString.contains("uuid_0985cebb-922d-4b3e-95e5-15dc6089cd28"));
 		assertTrue(archGMLString.contains(CITY_MODEL_HEADER));
 		assertTrue(archGMLString.contains(CITY_MODEL_FOOTER));
-		assertFalse(archGMLString.contains("comment between buildings"),
-				"Comments between buildings shouldn't be extracted");
-		assertFalse(archGMLString.contains("comment after last building"),
-				"Comments after buildings shouldn't be extracted");
 	}
 
 	@Test
 	void testExtractBuildingsAndChangeEnvelope() throws Throwable {
 		String wktPolygon = "POLYGON((299761.8123557725 61122.68126771413,299721.46983062755 61058.11626595352,299780.84627343423 61021.99295737501,299823.9079725632 61083.3979344517,299761.8123557725 61122.68126771413))";
 		Path citygmlPath = TEST_REPOSITORY.resolve("NewYork.proj/FamilyCourt_LOD2_with_PLUTO_attributes.gml");
-		String familyCourtBuilding = RegionExtractor
-				.selectRegionDirectlyFromCityGML(wktPolygon, EPSG_32118, citygmlPath).toString();
+		StringWriter gmlWriter = new StringWriter();
+		int count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, EPSG_32118, gmlWriter, citygmlPath);
+		String familyCourtBuilding = gmlWriter.toString();
+		assertEquals(1, count);
 		assertEquals(1, countRegexMatches(familyCourtBuilding, CITY_OBJECT_MEMBER_REGEX));
 		assertTrue(familyCourtBuilding.contains("Bldg_12210021066"));
 		assertFalse(
@@ -100,8 +102,10 @@ void testExtract0BuildingsWithWrongCoordinates() throws Throwable {
 		//NOTE: Small region, far away from NYC
 		String wktPolygon = "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))";
 		Path citygmlPath = TEST_REPOSITORY.resolve("NewYork.proj/ManhattanSmall.gml");
-		String emptyGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, EPSG_32118, citygmlPath)
-				.toString();
+		StringWriter gmlWriter = new StringWriter();
+		int count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, EPSG_32118, gmlWriter, citygmlPath);
+		String emptyGMLString = gmlWriter.toString();
+		assertEquals(0, count);
 		assertEquals(0, countRegexMatches(emptyGMLString, CITY_OBJECT_MEMBER_REGEX));
 		assertTrue(emptyGMLString.contains(CITY_MODEL_HEADER));
 		assertTrue(emptyGMLString.contains(CITY_MODEL_FOOTER));
@@ -112,8 +116,10 @@ void testExtract0BuildingsFromEmptyGML() throws Throwable {
 		//NOTE: Small region, with too many spaces between coordinates
 		String wktPolygon = "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))";
 		Path citygmlPath = TEST_REPOSITORY.resolve("NewYork.proj/empty_model.gml");
-		String emptyGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, EPSG_32118, citygmlPath)
-				.toString();
+		StringWriter gmlWriter = new StringWriter();
+		int count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, EPSG_32118, gmlWriter, citygmlPath);
+		String emptyGMLString = gmlWriter.toString();
+		assertEquals(0, count);
 		assertEquals(0, countRegexMatches(emptyGMLString, CITY_OBJECT_MEMBER_REGEX));
 		assertTrue(emptyGMLString.contains(CORE_CITY_MODEL_HEADER));
 		assertTrue(emptyGMLString.contains(CORE_CITY_MODEL_FOOTER));
@@ -124,8 +130,10 @@ void testExtract0BuildingsFromWeirdGML() throws Throwable {
 		//NOTE: Small region, with too many spaces between coordinates
 		String wktPolygon = "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))";
 		Path citygmlPath = TEST_REPOSITORY.resolve("NewYork.proj/broken_nyc_lod2.gml");
-		String emptyGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, EPSG_32118, citygmlPath)
-				.toString();
+		StringWriter gmlWriter = new StringWriter();
+		int count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, EPSG_32118, gmlWriter, citygmlPath);
+		String emptyGMLString = gmlWriter.toString();
+		assertEquals(0, count);
 		assertEquals(0, countRegexMatches(emptyGMLString, CITY_OBJECT_MEMBER_REGEX));
 		assertTrue(emptyGMLString.contains(CORE_CITY_MODEL_HEADER));
 		assertTrue(emptyGMLString.contains(CORE_CITY_MODEL_FOOTER));
@@ -135,9 +143,11 @@ void testExtract0BuildingsFromWeirdGML() throws Throwable {
 	void testExtractBuildingsFromCitygmlWithoutZinEnvelope() throws Throwable {
 		String wktPolygon = "POLYGON((3512683.1280912133 5404783.732132129,3512719.1608604863 5404714.627650777,3512831.40076119 5404768.344155442,3512790.239106708 5404838.614891164,3512683.1280912133 5404783.732132129))";
 		Path citygmlPath = TEST_REPOSITORY.resolve("Stuttgart.proj/Stuttgart_LOD0_LOD1_small.gml");
-		String emptyGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, "EPSG:31463", citygmlPath)
-				.toString();
-		assertEquals(2, countRegexMatches(emptyGMLString, "<bldg:Building gml:id"));
+		StringWriter gmlWriter = new StringWriter();
+		int count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, "EPSG:31463", gmlWriter, citygmlPath);
+		String twoBuildings = gmlWriter.toString();
+		assertEquals(2, count);
+		assertEquals(2, countRegexMatches(twoBuildings, "<bldg:Building gml:id"));
 	}
 
 	@Test
@@ -146,9 +156,12 @@ void testExtractBuildingsFrom2Citygmls() throws Throwable {
 
 		Path citygml1 = TEST_REPOSITORY.resolve("Stuttgart.proj/Stuttgart_LOD0_LOD1_small.gml");
 		Path citygml2 = TEST_REPOSITORY.resolve("Stuttgart.proj/Stöckach_überarbeitete GML-NoBuildingPart.gml");
-		String emptyGMLString = RegionExtractor
-				.selectRegionDirectlyFromCityGML(wktPolygon, "EPSG:31463", citygml1, citygml2).toString();
-		assertEquals(17 + 3, countRegexMatches(emptyGMLString, "<bldg:Building gml:id"));
+		StringWriter gmlWriter = new StringWriter();
+		int count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, "EPSG:31463", gmlWriter, citygml1,
+				citygml2);
+		String gmlFromTwoGMLs = gmlWriter.toString();
+		assertEquals(17 + 3, count);
+		assertEquals(17 + 3, countRegexMatches(gmlFromTwoGMLs, "<bldg:Building gml:id"));
 	}
 
 
diff --git a/src/test/java/eu/simstadt/regionchooser/RegionExtractorWithDifferentInputTests.java b/src/test/java/eu/simstadt/regionchooser/RegionExtractorWithDifferentInputTests.java
index 47bd64717d5db996940dfda49901ff1abe8ba5d3..9f9b8c3eb6aa87d02b4701cad731f9f4a186107b 100644
--- a/src/test/java/eu/simstadt/regionchooser/RegionExtractorWithDifferentInputTests.java
+++ b/src/test/java/eu/simstadt/regionchooser/RegionExtractorWithDifferentInputTests.java
@@ -2,30 +2,25 @@
 
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.io.IOException;
+import java.io.StringWriter;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.stream.Collectors;
 import org.junit.jupiter.api.Test;
-import org.locationtech.jts.geom.Polygon;
 import org.locationtech.jts.io.ParseException;
-import org.locationtech.jts.io.WKTReader;
-import org.locationtech.jts.io.WKTWriter;
 import org.osgeo.proj4j.CoordinateReferenceSystem;
 import com.ximpleware.NavException;
 import com.ximpleware.XPathEvalException;
 import com.ximpleware.XPathParseException;
 
 
-public class RegionExtractorWithDifferentInputTests
+class RegionExtractorWithDifferentInputTests
 {
-	private static final WKTReader WKT_READER = new WKTReader();
-	private static final WKTWriter WKT_WRITER = new WKTWriter();
 	private static final Path TEST_REPOSITORY = Paths.get("src/test/resources/testdata/");
 
-	//NOTE: This test can be adapted to download a region which is too large for the server. Here with local coordinates
 	@Test
-	public void testExtractRegionWithLocalCRS()
+	void testExtractRegionWithLocalCRS()
 			throws IOException, XPathParseException, NavException, XPathEvalException, ParseException {
 		String citygml = "DA13_DA14_3D_Buildings_Port_Morris.gml";
 		String projectName = "NewYork";
@@ -34,14 +29,14 @@ public void testExtractRegionWithLocalCRS()
 		Path citygmlPath = project.resolve(citygml);
 
 		CoordinateReferenceSystem localCRS = RegionChooserUtils.crsFromCityGMLHeader(citygmlPath);
-		StringBuilder sb = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, localCRS.getName(), citygmlPath);
-		assertTrue(sb.toString().contains("gml_ZVHMQQ6BZGRT0O3Q6RGXF12BDOV49QIZ58XB"),
+		StringWriter gmlWriter = new StringWriter();
+		RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, localCRS.getName(), gmlWriter, citygmlPath);
+		assertTrue(gmlWriter.toString().contains("gml_ZVHMQQ6BZGRT0O3Q6RGXF12BDOV49QIZ58XB"),
 				"One weird shaped roof should be inside the region");
 	}
 
-	//NOTE: This test can be adapted to download a region which is too large for the server. Here with WGS84 coordinates
 	@Test
-	public void testExtractRegionWithWGS84()
+	void testExtractRegionWithWGS84()
 			throws ParseException, IOException, XPathParseException, NavException, XPathEvalException {
 		String wgs84WktPolygon = "POLYGON((-73.91140940026597 40.804246732157196,-73.91424181298568 40.80025100302325,-73.90934946374252 40.79755456207104,-73.90561582879378 40.80116062104605,-73.90960695580794 40.80340212653638,-73.91140940026597 40.804246732157196))";
 		String citygml = "DA13_DA14_3D_Buildings_Port_Morris.gml";
@@ -49,19 +44,16 @@ public void testExtractRegionWithWGS84()
 		Path project = TEST_REPOSITORY.resolve(projectName + ".proj");
 		Path citygmlPath = project.resolve(citygml);
 
-		Polygon wgs84Polygon = (Polygon) WKT_READER.read(wgs84WktPolygon);
 		CoordinateReferenceSystem localCRS = RegionChooserUtils.crsFromCityGMLHeader(citygmlPath);
-		String localWktPolygon = WKT_WRITER
-				.write(RegionChooserUtils.changePolygonCRS(wgs84Polygon, RegionChooserUtils.WGS84, localCRS));
-		StringBuilder sb = RegionExtractor.selectRegionDirectlyFromCityGML(localWktPolygon, localCRS.getName(),
-				citygmlPath);
-		assertTrue(sb.toString().contains("gml_ZVHMQQ6BZGRT0O3Q6RGXF12BDOV49QIZ58XB"),
+		String localWktPolygon = RegionChooserUtils.wktPolygonToLocalCRS(wgs84WktPolygon, localCRS);
+		StringWriter gmlWriter = new StringWriter();
+		RegionExtractor.selectRegionDirectlyFromCityGML(localWktPolygon, localCRS.getName(), gmlWriter, citygmlPath);
+		assertTrue(gmlWriter.toString().contains("gml_ZVHMQQ6BZGRT0O3Q6RGXF12BDOV49QIZ58XB"),
 				"One weird shaped roof should be inside the region");
 	}
 
-	//NOTE: This test can be adapted to download a region which is too large for the server. Here with old coordinates from WebSimstadt
 	@Test
-	public void testExtractRegionWithOldCoordinates()
+	void testExtractRegionWithOldCoordinates()
 			throws ParseException, IOException, XPathParseException, NavException, XPathEvalException {
 		String oldFormatPolygon = "(40.81173171854368,-73.93268437431763)\r\n" +
 				"(40.81069231965162,-73.93068165999941)\r\n" +
@@ -98,16 +90,12 @@ public void testExtractRegionWithOldCoordinates()
 		Path project = TEST_REPOSITORY.resolve(projectName + ".proj");
 		Path citygmlPath = project.resolve(citygml);
 
-		Polygon wgs84Polygon = (Polygon) WKT_READER.read(wgs84WktPolygon);
 		CoordinateReferenceSystem localCRS = RegionChooserUtils.crsFromCityGMLHeader(citygmlPath);
-		String localWktPolygon = WKT_WRITER
-				.write(RegionChooserUtils.changePolygonCRS(wgs84Polygon, RegionChooserUtils.WGS84, localCRS));
-		StringBuilder sb = RegionExtractor.selectRegionDirectlyFromCityGML(localWktPolygon, localCRS.getName(),
-				citygmlPath);
+		String localWktPolygon = RegionChooserUtils.wktPolygonToLocalCRS(wgs84WktPolygon, localCRS);
+		StringWriter gmlWriter = new StringWriter();
+		RegionExtractor.selectRegionDirectlyFromCityGML(localWktPolygon, localCRS.getName(), gmlWriter, citygmlPath);
 
-		assertTrue(sb.toString().contains("gml_ZVHMQQ6BZGRT0O3Q6RGXF12BDOV49QIZ58XB"),
+		assertTrue(gmlWriter.toString().contains("gml_ZVHMQQ6BZGRT0O3Q6RGXF12BDOV49QIZ58XB"),
 				"One weird shaped roof should be inside the region");
 	}
-
-	//TODO: Write a method to merge RegionChooser results from multiple gmls
 }