Commits (33)
MIT License MIT License
Copyright (c) 2019 University of Applied Science Stuttgart Copyright (c) 2022 University of Applied Science Stuttgart
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
......
java -classpath lib/* -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX @ECHO OFF
REM TODO: Update, similar to RegionChooser.sh REM Change code page in order to display UTF-8 symbols in Windows console
\ No newline at end of file chcp 65001 >nul
cd /D "%~dp0"
REM Example : RegionChooser.bat --input=%UserProfile%\Desktop\ManhattanSmall.gml --output=%UserProfile%\Desktop\out.gml --wkt=%UserProfile%\Desktop\polygon.wkt
if "%~1" == "" (
ECHO No parameter, launching RegionChooser GUI 1>&2
java -classpath lib/*; eu.simstadt.regionchooser.RegionChooserFX
) ELSE (
ECHO Launching RegionChooser CLI %* 1>&2
java -classpath lib/*; eu.simstadt.regionchooser.RegionChooserCLI %*
)
\ No newline at end of file
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<groupId>eu.simstadt</groupId> <groupId>eu.simstadt</groupId>
<artifactId>region-chooser</artifactId> <artifactId>region-chooser</artifactId>
<version>0.2.9-SNAPSHOT</version> <version>0.3.0-SNAPSHOT</version>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
......
...@@ -49,12 +49,8 @@ public JavaScriptFXBridge() { ...@@ -49,12 +49,8 @@ public JavaScriptFXBridge() {
/** /**
* Launches a background thread in which the hull gets extracted for every CityGML file. The hull gets sent back * 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. * 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() { public void refreshHulls() {
//NOTE: Could add progress bar?
Task<Void> task = new Task<Void>() { Task<Void> task = new Task<Void>() {
@Override @Override
public Void call() throws IOException { public Void call() throws IOException {
...@@ -76,22 +72,35 @@ public Void call() throws IOException { ...@@ -76,22 +72,35 @@ public Void call() throws IOException {
} }
/** /**
* This method is called from Javascript, with a prepared wktPolygon written in local coordinates. * This method is called from Javascript, with a prepared wktPolygon written in local coordinates. Executes it in
* the background to avoid freezing the GUI
*/ */
public int downloadRegionFromCityGMLs(String wktPolygon, String project, String csvCitygmls, String srsName) public void 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 ; // 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); Path[] paths = Stream.of(csvCitygmls.split(";")).map(s -> citygmlPath(project, s)).toArray(Path[]::new);
String proposedName = csvCitygmls.replace(";", "_").replace(".gml", "") + ".gml";
File outputFile = selectSaveFileWithDialog(project, proposedName, "part");
if (outputFile == null) {
return;
}
Task<Integer> downloadTask = new Task<Integer>() {
@Override
public Integer call() throws IOException, XPathParseException, NavException, ParseException {
int count = -1;
try (BufferedWriter gmlWriter = Files.newBufferedWriter(outputFile.toPath())) {
count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, srsName, gmlWriter, paths);
}
LOGGER.info(outputFile + " has been written");
return count;
}
};
File outputFile = selectSaveFileWithDialog(project, downloadTask.setOnRunning(e -> jsApp.call("downloadStart"));
csvCitygmls.replace(";", "_").replace(".gml", ""), "selected_region");
int count; downloadTask.setOnSucceeded(e -> jsApp.call("downloadFinished", e.getSource().getValue()));
try (BufferedWriter gmlWriter = Files.newBufferedWriter(outputFile.toPath())) {
count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, srsName, gmlWriter, paths); new Thread(downloadTask).start();
}
return count;
} }
......
...@@ -9,8 +9,10 @@ ...@@ -9,8 +9,10 @@
import java.util.Scanner; import java.util.Scanner;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.osgeo.proj4j.CoordinateReferenceSystem; import org.osgeo.proj4j.CoordinateReferenceSystem;
import eu.simstadt.regionchooser.RegionChooserCLI.GetVersion;
import picocli.CommandLine; import picocli.CommandLine;
import picocli.CommandLine.Command; import picocli.CommandLine.Command;
import picocli.CommandLine.IVersionProvider;
import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option; import picocli.CommandLine.Option;
import picocli.CommandLine.Spec; import picocli.CommandLine.Spec;
...@@ -41,17 +43,14 @@ ...@@ -41,17 +43,14 @@
// --wkt ./grombuhl.txt // --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) @Command(name = "region_chooser", mixinStandardHelpOptions = true, description = "Extracts a region from one or more citygmls.", sortOptions = false, versionProvider = GetVersion.class)
class RegionChooserCLI implements Callable<Integer> class RegionChooserCLI implements Callable<Integer>
{ {
@Spec @Spec
CommandSpec spec; CommandSpec spec;
//TODO: Add --gui?
@Option(names = { "-i", @Option(names = { "-i",
"--input" }, required = true, split = ",", description = "Citygml files to extract from", paramLabel = "input.gml") "--input" }, required = true, split = ",", description = "Citygml files to extract from", paramLabel = "input.gml")
//TODO: Allow folders too?
Path[] citygmls; Path[] citygmls;
@Option(names = { "-o", @Option(names = { "-o",
...@@ -122,6 +121,13 @@ public Integer call() throws Exception { ...@@ -122,6 +121,13 @@ public Integer call() throws Exception {
return 0; 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) { private void logInfo(String message) {
spec.commandLine().getErr().println(message); spec.commandLine().getErr().println(message);
} }
...@@ -132,10 +138,12 @@ private static String getInput() { ...@@ -132,10 +138,12 @@ private static String getInput() {
} }
} }
// this example implements Callable, so parsing, error handling and handling user static class GetVersion implements IVersionProvider
// requests for usage help or version help can be done with one line of code. {
public static void main(String... args) { @Override
int exitCode = new CommandLine(new RegionChooserCLI()).execute(args); public String[] getVersion() throws Exception {
System.exit(exitCode); return new String[] { RegionChooserUtils.getApplicationVersion() };
}
} }
} }
\ No newline at end of file
...@@ -28,7 +28,6 @@ ...@@ -28,7 +28,6 @@
private static final Pattern srsNamePattern = Pattern.compile("(?i)(?<=srsName=[\"'])[^\"']+(?=[\"'])"); private static final Pattern srsNamePattern = Pattern.compile("(?i)(?<=srsName=[\"'])[^\"']+(?=[\"'])");
private static final int CITYGML_HEADER_LENGTH = 50; private static final int CITYGML_HEADER_LENGTH = 50;
private static final String EPSG = "EPSG:"; private static final String EPSG = "EPSG:";
private static final int BUFFER = 1024;
private RegionChooserUtils() { private RegionChooserUtils() {
// only static use // only static use
......
...@@ -59,6 +59,7 @@ static int selectRegionDirectlyFromCityGML(String wktPolygon, String srsName, Wr ...@@ -59,6 +59,7 @@ static int selectRegionDirectlyFromCityGML(String wktPolygon, String srsName, Wr
for (int i = 0; i < citygmlPaths.length; i++) { for (int i = 0; i < citygmlPaths.length; i++) {
Path citygmlPath = citygmlPaths[i]; Path citygmlPath = citygmlPaths[i];
LOGGER.info("Parsing " + citygmlPath); LOGGER.info("Parsing " + citygmlPath);
//TODO: Allow citygmlPath for folders too, and iterate over gmls?
citygml = new CityGmlIterator(citygmlPath); citygml = new CityGmlIterator(citygmlPath);
for (BuildingXmlNode buildingXmlNode : citygml) { for (BuildingXmlNode buildingXmlNode : citygml) {
if (buildingsCount == 0) { if (buildingsCount == 0) {
...@@ -87,7 +88,7 @@ static int selectRegionDirectlyFromCityGML(String wktPolygon, String srsName, Wr ...@@ -87,7 +88,7 @@ static int selectRegionDirectlyFromCityGML(String wktPolygon, String srsName, Wr
LOGGER.warning("No building found in the selected region."); LOGGER.warning("No building found in the selected region.");
} }
LOGGER.info("Buildings found in selected region " + foundBuildingsCount); LOGGER.info("Buildings found in selected region : " + foundBuildingsCount);
//NOTE: This could be a problem if header starts with <core:CityModel> and footer ends with </CityModel> //NOTE: This could be a problem if header starts with <core:CityModel> and footer ends with </CityModel>
sb.append(citygml.getFooter()); sb.append(citygml.getFooter());
return foundBuildingsCount; return foundBuildingsCount;
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
<div id="side"> <div id="side">
<div id="dataPanel" ></div> <div id="dataPanel" ></div>
</div> </div>
<script src="script/utils.js" type="text/javascript"></script>
<script src="script/simstadt_openlayers.js" type="text/javascript"></script> <script src="script/simstadt_openlayers.js" type="text/javascript"></script>
</body> </body>
</html> </html>
var regionChooser = (function(){ const styles = {};
styles.original = utils.polygon_style('#447744', 0.2);
styles.highlighted = utils.polygon_style("#ff44a2", 0.7);
styles.selected = utils.polygon_style("#ffff00", 0.8);
const regionChooser = (function(){
//TODO: Somehow split in classes. This file is getting too big and mixed //TODO: Somehow split in classes. This file is getting too big and mixed
var publicScope = {}; var publicScope = {};
var fromJavaFX = navigator.userAgent.indexOf('JavaFX') !== -1; const fromJavaFX = navigator.userAgent.indexOf('JavaFX') !== -1;
var dataPanel = $('#dataPanel'); const dataPanel = $('#dataPanel');
var wgs84Sphere = new ol.Sphere(6378137); const wgs84Sphere = new ol.Sphere(6378137);
var gmlId; var features_by_project;
publicScope.init = function(){ publicScope.init = function(){
//NOTE: Only called from JavaFX. At startup, or when Repo has been changed. //NOTE: Only called from JavaFX. At startup, or when Repo has been changed.
gmlId = 0;
kml_source.clear(); kml_source.clear();
document.getElementById("select_repository").style.visibility = "visible"; document.getElementById("select_repository").style.visibility = "visible";
} }
...@@ -21,33 +26,11 @@ var regionChooser = (function(){ ...@@ -21,33 +26,11 @@ var regionChooser = (function(){
source: new ol.source.OSM() source: new ol.source.OSM()
}); });
function read_kml(url){ var kml_source = utils.read_kml(fromJavaFX ? undefined : 'data/citygml_hulls.kml');
return new ol.source.KML({
projection : ol.proj.get('EPSG:3857'),
url : url,
extractAttributes : false,
extractStyles : false
});
}
var kml_source = read_kml(fromJavaFX ? undefined : 'data/citygml_hulls.kml');
function polygon_style(color, alpha) {
return new ol.style.Style({
fill : new ol.style.Fill({
color : 'rgba(255, 255, 255,' + alpha + ')'
}),
stroke : new ol.style.Stroke({
color : color,
width : 2,
lineDash : [ 5, 10 ]
}),
});
}
var kml_layer = new ol.layer.Vector({ var kml_layer = new ol.layer.Vector({
source : kml_source, source : kml_source,
style : polygon_style('#447744', 0.2) style : styles.original
}); });
var intersections = new ol.source.Vector(); var intersections = new ol.source.Vector();
...@@ -64,7 +47,6 @@ var regionChooser = (function(){ ...@@ -64,7 +47,6 @@ var regionChooser = (function(){
publicScope.addCityGmlHull = function(kmlString) { publicScope.addCityGmlHull = function(kmlString) {
options = {featureProjection: ol.proj.get('EPSG:3857')}; options = {featureProjection: ol.proj.get('EPSG:3857')};
feature = kmlFormat.readFeature(kmlString, options); feature = kmlFormat.readFeature(kmlString, options);
feature.setId(gmlId++);
kml_source.addFeature(feature); kml_source.addFeature(feature);
dataPanel.append('.'); dataPanel.append('.');
srsName = feature.get("srsName"); srsName = feature.get("srsName");
...@@ -81,10 +63,10 @@ var regionChooser = (function(){ ...@@ -81,10 +63,10 @@ var regionChooser = (function(){
}) })
}); });
var geoJsonFormat = new ol.format.GeoJSON(); const geoJsonFormat = new ol.format.GeoJSON();
var kmlFormat = new ol.format.KML({extractStyles: false}); const kmlFormat = new ol.format.KML({extractStyles: false});
kml_layer.addEventListener("change", function() { kml_source.addEventListener("addfeature", function() {
map.getView().fitExtent(kml_source.getExtent(), (map.getSize())); map.getView().fitExtent(kml_source.getExtent(), (map.getSize()));
}); });
...@@ -95,14 +77,22 @@ var regionChooser = (function(){ ...@@ -95,14 +77,22 @@ var regionChooser = (function(){
feature["project"] = feature.get("project"); feature["project"] = feature.get("project");
feature["name"] = feature.get("name"); feature["name"] = feature.get("name");
feature["source"] = "CityGML"; feature["source"] = "CityGML";
feature["status"] = "original";
}); });
var features = Array.from(kml_source.getFeatures());
// Sort projects
features.sort((a, b) => a.project.localeCompare(b.project));
features_by_project = utils.groupBy(features, "project");
// Sort CityGMLs inside each project
Object.values(features_by_project).forEach(features => features.sort((a, b) => a.name.localeCompare(b.name)));
} }
// The features are not added to a regular vector layer/source, // The features are not added to a regular vector layer/source,
// but to a feature overlay which holds a collection of features. // but to a feature overlay which holds a collection of features.
// This collection is passed to the modify and also the draw // This collection is passed to the modify and also the draw
// interaction, so that both can add or modify features. // interaction, so that both can add or modify features.
var featureOverlay = new ol.FeatureOverlay({ var drawnLayer = new ol.FeatureOverlay({
style : new ol.style.Style({ style : new ol.style.Style({
fill : new ol.style.Fill({ fill : new ol.style.Fill({
color : 'rgba(255, 155, 51, 0.5)' color : 'rgba(255, 155, 51, 0.5)'
...@@ -119,11 +109,9 @@ var regionChooser = (function(){ ...@@ -119,11 +109,9 @@ var regionChooser = (function(){
}) })
}) })
}); });
featureOverlay.setMap(map); drawnLayer.setMap(map);
//TODO: Rename to Javascript naming convention (CamelCase). drawnLayer.getFeatures().on('add', function(event) {
var selected_features = featureOverlay.getFeatures();
selected_features.on('add', function(event) {
var feature = event.element; var feature = event.element;
feature.on("change", function() { feature.on("change", function() {
displayInfo(); displayInfo();
...@@ -131,7 +119,7 @@ var regionChooser = (function(){ ...@@ -131,7 +119,7 @@ var regionChooser = (function(){
}); });
var modify = new ol.interaction.Modify({ var modify = new ol.interaction.Modify({
features : featureOverlay.getFeatures(), features : drawnLayer.getFeatures(),
// the SHIFT key must be pressed to delete vertices, so // the SHIFT key must be pressed to delete vertices, so
// that new vertices can be drawn at the same position // that new vertices can be drawn at the same position
// of existing vertices // of existing vertices
...@@ -142,7 +130,7 @@ var regionChooser = (function(){ ...@@ -142,7 +130,7 @@ var regionChooser = (function(){
map.addInteraction(modify); map.addInteraction(modify);
var draw = new ol.interaction.Draw({ var draw = new ol.interaction.Draw({
features : featureOverlay.getFeatures(), features : drawnLayer.getFeatures(),
type : 'Polygon' type : 'Polygon'
}); });
map.addInteraction(draw); map.addInteraction(draw);
...@@ -161,20 +149,61 @@ var regionChooser = (function(){ ...@@ -161,20 +149,61 @@ var regionChooser = (function(){
var citygml_percentage = Math.round(intersectionArea / feature["area"] * 100); var citygml_percentage = Math.round(intersectionArea / feature["area"] * 100);
var sketch_percentage = Math.round(intersectionArea / polygonArea * 100); var sketch_percentage = Math.round(intersectionArea / polygonArea * 100);
intersections.addFeature(intersection); intersections.addFeature(intersection);
var link = '<li>'
// TODO: If possible, highlight the corresponding polygon when hovering above a name. li = document.createElement('li');
link += '<input type="checkbox" id="citygml_' + feature.getId() + '" class="select_citygml" onclick="regionChooser.isDownloadPossible();"><label for="citygml_' + feature.getId() + '">' + feature['name'] + '</label>'; li.feature = feature;
link += " (" + citygml_percentage + "%"; li.onmouseover = function(){ regionChooser.highlightPolygon(this.feature) };
li.onmouseout = function(){ regionChooser.resetHighlight(this.feature) };
let label = li.appendChild(document.createElement('label'));
var text = feature.name;
let checkbox = document.createElement('input');
checkbox.type = 'checkbox'
checkbox.className = "select_citygml";
checkbox.feature = feature;
checkbox.setAttribute('onclick', "regionChooser.isDownloadPossible()");
text += " (" + citygml_percentage + "%";
if (sketch_percentage == 100) { if (sketch_percentage == 100) {
link += ", all inside"; text += ", all inside";
} }
dataPanel.append(link + ")\n");
label.textContent = text + ")\n";
label.prepend(checkbox);
// append to DOM element, not to jQuery object
dataPanel[0].appendChild(li);
}
publicScope.highlightPolygon = function(feature) {
feature.setStyle(styles.highlighted);
}
publicScope.resetHighlight = function(feature) {
refreshStyle(feature);
}
refreshStyle = function(feature, status){
if (status){
feature.status = status;
}
feature.setStyle(styles[feature.status]);
} }
publicScope.isDownloadPossible = function(){ publicScope.isDownloadPossible = function(){
var checkedBoxes = Array.from(document.querySelectorAll("input.select_citygml")).filter(c => c.checked); kml_source.getFeatures().forEach(f => refreshStyle(f, "original"));
document.getElementById("download_region_button").disabled = (checkedBoxes.length == 0);
selectedFeatures = getSelectedGMLs();
selectedFeatures.forEach(f => refreshStyle(f, "selected"));
document.getElementById("download_region_button").disabled = (selectedFeatures.length == 0);
}
function getSelectedGMLs(){
return Array.from(document.querySelectorAll("input.select_citygml")).filter(c => c.checked).map(c => c.feature);
} }
function findIntersection(feature, polygon) { function findIntersection(feature, polygon) {
...@@ -190,8 +219,6 @@ var regionChooser = (function(){ ...@@ -190,8 +219,6 @@ var regionChooser = (function(){
var polygonArea = sketch.getGeometry().getArea(); var polygonArea = sketch.getGeometry().getArea();
var intersection_found = false; var intersection_found = false;
intersections.clear(); intersections.clear();
//NOTE: getFeatures seems to not be sorted anymore. :-/
features_by_project = groupBy(kml_source.getFeatures(), "project");
Object.keys(features_by_project).forEach(function(project) { Object.keys(features_by_project).forEach(function(project) {
features = features_by_project[project]; features = features_by_project[project];
...@@ -216,12 +243,28 @@ var regionChooser = (function(){ ...@@ -216,12 +243,28 @@ var regionChooser = (function(){
dataPanel.append(text + "<br/>\n"); dataPanel.append(text + "<br/>\n");
} }
publicScope.downloadRegionFromCityGMLs = function(checkbox_ids) { publicScope.downloadStart = function(){
var features = checkbox_ids.map(checkbox_id => { document.getElementById("download_region_button").disabled = true;
var i = Number(checkbox_id.replace("citygml_", "")); document.documentElement.className = 'wait';
return kml_source.getFeatureById(i); dataPanel.prepend("<h2 id='download_start' class='ok'>Extracting region...</h2><br/>\n");
}) }
publicScope.downloadFinished = function(count){
document.documentElement.className = ''; // Stop waiting
document.getElementById("download_start").remove();
if (count > 0){
dataPanel.prepend("<h2 class='ok'>Done! (" + count + " buildings found) </h2><br/>\n");
} else {
dataPanel.prepend("<h2 class='error'>No building has been found in this region</h2><br/>\n");
}
var button = document.getElementById("download_region_button");
if (button){ // Region might have been modified since download start
button.disabled = false;
}
}
publicScope.downloadFromSelectedCityGMLs = function() {
var features = getSelectedGMLs();
var project = features[0].get("project"); var project = features[0].get("project");
var srsName = features[0].get("srsName"); var srsName = features[0].get("srsName");
...@@ -234,34 +277,15 @@ var regionChooser = (function(){ ...@@ -234,34 +277,15 @@ var regionChooser = (function(){
dataPanel.prepend("<h2 class='error'>Sorry, the CityGML files should all be written with the same coordinate system.</h2><br/>\n"); 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")); var citygmlNames = features.map(f => f.get("name"));
// Waiting 100ms in order to let the cursor change if (proj4.defs(srsName)){
setTimeout(function() { console.log("Selected region is written in " + srsName + " coordinate system.");
var start = new Date().getTime(); fxapp.downloadRegionFromCityGMLs(sketchAsWKT(srsName), project, citygmlNames.join(";"), srsName);
if (proj4.defs(srsName)){ } else {
console.log("Selected region is written in " + srsName + " coordinate system."); var msg = "ERROR : Unknown coordinate system : \"" + srsName + "\". Cannot extract any region";
try { console.log(msg);
var count = fxapp.downloadRegionFromCityGMLs(sketchAsWKT(srsName), project, citygmlNames.join(";"), srsName); dataPanel.append(msg + "<br/>\n");
dataPanel.prepend("<h2 class='ok'>Done! (" + count + " buildings found) </h2><br/>\n"); }
} catch (e) {
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() {
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";
console.log(msg);
dataPanel.append(msg + "<br/>\n");
}
}, 100);
} }
function displayInfo() { function displayInfo() {
...@@ -273,8 +297,8 @@ var regionChooser = (function(){ ...@@ -273,8 +297,8 @@ var regionChooser = (function(){
dataPanel.append("<h3 class='clean'>Area : " + (area / 10000).toFixed(1) + " ha\n"); dataPanel.append("<h3 class='clean'>Area : " + (area / 10000).toFixed(1) + " ha\n");
dataPanel.append('<div style="visibility:hidden" id="download_region">' + 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' + '<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.selectAllOrNone(true);">(Select All)</a>\n' +
'<a href="#" onclick="regionChooser.checkCityGMLS(false);">(Select None)</a>\n'+ '<a href="#" onclick="regionChooser.selectAllOrNone(false);">(Select None)</a>\n'+
'</div>\n'); '</div>\n');
findIntersections(); findIntersections();
dataPanel.append('<button type="button" onclick="regionChooser.copyCoordinatesToClipboard()" id="get_wgs84">Copy coordinates</button><br/>\n') dataPanel.append('<button type="button" onclick="regionChooser.copyCoordinatesToClipboard()" id="get_wgs84">Copy coordinates</button><br/>\n')
...@@ -305,8 +329,9 @@ var regionChooser = (function(){ ...@@ -305,8 +329,9 @@ var regionChooser = (function(){
} finally { } finally {
displayHelp(); displayHelp();
document.documentElement.className = ''; // Stop waiting document.documentElement.className = ''; // Stop waiting
kml_source.getFeatures().forEach(f => refreshStyle(f, "original"));
draw.setActive(true); draw.setActive(true);
featureOverlay.getFeatures().clear(); drawnLayer.getFeatures().clear();
intersections.clear(); intersections.clear();
focusOnMap(); focusOnMap();
} }
...@@ -337,13 +362,6 @@ var regionChooser = (function(){ ...@@ -337,13 +362,6 @@ var regionChooser = (function(){
} }
} }
groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
function displayHelp(){ function displayHelp(){
dataPanel.empty(); dataPanel.empty();
dataPanel.append("<h2 class='info'>Welcome to Region Chooser!<br><br>\n"); dataPanel.append("<h2 class='info'>Welcome to Region Chooser!<br><br>\n");
...@@ -364,17 +382,10 @@ var regionChooser = (function(){ ...@@ -364,17 +382,10 @@ var regionChooser = (function(){
updateGMLPolygons(); updateGMLPolygons();
displayHelp(); displayHelp();
document.documentElement.className = ''; // Stop waiting document.documentElement.className = ''; // Stop waiting
console.log("READY!"); 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) { publicScope.selectAllOrNone = function(allOrNone) {
document.querySelectorAll("input.select_citygml").forEach(c => c.checked = allOrNone); document.querySelectorAll("input.select_citygml").forEach(c => c.checked = allOrNone);
publicScope.isDownloadPossible(); publicScope.isDownloadPossible();
} }
...@@ -387,43 +398,10 @@ var regionChooser = (function(){ ...@@ -387,43 +398,10 @@ var regionChooser = (function(){
var geom = sketch.getGeometry().clone().transform(sourceProj, 'EPSG:4326'); var geom = sketch.getGeometry().clone().transform(sourceProj, 'EPSG:4326');
var wgs84Coords = geom.getLinearRing(0).getCoordinates(); var wgs84Coords = geom.getLinearRing(0).getCoordinates();
var wktPolygon = "POLYGON(("; var wktPolygon = "POLYGON((";
wktPolygon += wgs84Coords.map(lonLat => lonLat.join(" ")).join(", "); var precision = 6; // ~ 10 cm precision
publicScope.copyToClipboard(wktPolygon + "))"); wktPolygon += wgs84Coords.map(([lon, lat]) => lon.toFixed(precision) + " " + lat.toFixed(precision)).join(", ");
} utils.copyToClipboard(wktPolygon + "))", dataPanel);
// 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) { publicScope.showRepositoryName = function(path) {
document.getElementById("repo_path").textContent = path; document.getElementById("repo_path").textContent = path;
......
var utils = {};
utils.groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
}
// 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
utils.copyToClipboard = function(text, log) {
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.
log.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);
}
}
}
utils.read_kml = function(url){
return new ol.source.KML({
projection : ol.proj.get('EPSG:3857'),
url : url,
extractAttributes : false,
extractStyles : false
});
}
utils.polygon_style = function(color, alpha) {
return new ol.style.Style({
fill : new ol.style.Fill({
color : 'rgba(255, 255, 255,' + alpha + ')'
}),
stroke : new ol.style.Stroke({
color : color,
width : 2,
lineDash : [ 5, 10 ]
}),
});
}
...@@ -57,6 +57,13 @@ void testNoInput() { ...@@ -57,6 +57,13 @@ void testNoInput() {
assertTrue(err.toString().contains(expectedErr), err.toString() + " should contain " + expectedErr); assertTrue(err.toString().contains(expectedErr), err.toString() + " should contain " + expectedErr);
} }
@Test
void testGetVersion() {
new CommandLine(new RegionChooserCLI()).execute("--version");
String expectedVersion = RegionChooserUtils.getApplicationVersion();
assertTrue(out.toString().contains(expectedVersion), out.toString() + " should contain " + expectedVersion);
}
@Test @Test
void testExtractRegionFromTwoCitygmls() throws IOException { 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))"; 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))";
......