diff --git a/src/main/java/eu/simstadt/regionchooser/RegionChooserBrowser.java b/src/main/java/eu/simstadt/regionchooser/RegionChooserBrowser.java index 753f7215a2bdc3f67947b732dc5ef7cc5fc1c43f..8e59d50aea9bdf9e0bf0810509b896b0a1cc18fa 100644 --- a/src/main/java/eu/simstadt/regionchooser/RegionChooserBrowser.java +++ b/src/main/java/eu/simstadt/regionchooser/RegionChooserBrowser.java @@ -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 * 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? Task<Void> task = new Task<Void>() { @Override public Void call() throws IOException { @@ -76,27 +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) - throws IOException, ParseException, XPathParseException, NavException { + public void downloadRegionFromCityGMLs(String wktPolygon, String project, String csvCitygmls, String srsName) { // 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); - String proposedName = csvCitygmls.replace(";", "_").replace(".gml", "") + ".gml"; - File outputFile = selectSaveFileWithDialog(project, proposedName, "part"); - if (outputFile == null) { - return -1; + return; } - int count; - try (BufferedWriter gmlWriter = Files.newBufferedWriter(outputFile.toPath())) { - count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, srsName, gmlWriter, paths); - } - LOGGER.info(outputFile + " has been written"); - return count; + 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; + } + }; + + downloadTask.setOnRunning(e -> jsApp.call("downloadStart")); + + downloadTask.setOnSucceeded(e -> jsApp.call("downloadFinished", e.getSource().getValue())); + + new Thread(downloadTask).start(); } diff --git a/src/main/java/eu/simstadt/regionchooser/RegionExtractor.java b/src/main/java/eu/simstadt/regionchooser/RegionExtractor.java index 665c32d96abddf0db163949119f214f55795b3ff..c9c460fcdae6c090ea4c920df8c475338da0e82c 100644 --- a/src/main/java/eu/simstadt/regionchooser/RegionExtractor.java +++ b/src/main/java/eu/simstadt/regionchooser/RegionExtractor.java @@ -88,7 +88,7 @@ static int selectRegionDirectlyFromCityGML(String wktPolygon, String srsName, Wr 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> sb.append(citygml.getFooter()); return foundBuildingsCount; 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 ad50814e81be9bea3cade61530ce3c6231639ced..99f3b8ec6d26bf18b7eda47648fa7bd81dff42ce 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 @@ -11,11 +11,9 @@ const regionChooser = (function(){ const dataPanel = $('#dataPanel'); const wgs84Sphere = new ol.Sphere(6378137); var features_by_project; - 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"; } @@ -49,7 +47,6 @@ const regionChooser = (function(){ publicScope.addCityGmlHull = function(kmlString) { options = {featureProjection: ol.proj.get('EPSG:3857')}; feature = kmlFormat.readFeature(kmlString, options); - feature.setId(gmlId++); kml_source.addFeature(feature); dataPanel.append('.'); srsName = feature.get("srsName"); @@ -151,26 +148,40 @@ const regionChooser = (function(){ var intersectionArea = intersection.getGeometry().getArea(); var citygml_percentage = Math.round(intersectionArea / feature["area"] * 100); var sketch_percentage = Math.round(intersectionArea / polygonArea * 100); - var id = feature.getId(); intersections.addFeature(intersection); - var link = '<li onmouseover="regionChooser.highlightPolygon(' + id + ')" onmouseout="regionChooser.resetHighlight(' + id +')">'; - 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 + "%"; + + li = document.createElement('li'); + li.feature = feature; + + 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) { - 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(i) { - var feature = kml_source.getFeatureById(i); + publicScope.highlightPolygon = function(feature) { feature.setStyle(styles.highlighted); } - publicScope.resetHighlight = function(i) { - var feature = kml_source.getFeatureById(i); + publicScope.resetHighlight = function(feature) { refreshStyle(feature); } @@ -184,13 +195,15 @@ const regionChooser = (function(){ publicScope.isDownloadPossible = function(){ kml_source.getFeatures().forEach(f => refreshStyle(f, "original")); - //TODO: Dry - var checkedBoxes = Array.from(document.querySelectorAll("input.select_citygml")).filter(c => c.checked); - var checkbox_ids = checkedBoxes.map(c => c.id); - var features = getCheckedPolygons(checkbox_ids); - features.forEach(f => refreshStyle(f, "selected")); + selectedFeatures = getSelectedGMLs(); - document.getElementById("download_region_button").disabled = (checkedBoxes.length == 0); + 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) { @@ -230,17 +243,28 @@ const regionChooser = (function(){ dataPanel.append(text + "<br/>\n"); } - getCheckedPolygons = function(checkbox_ids){ - return checkbox_ids.map(checkbox_id => { - var i = Number(checkbox_id.replace("citygml_", "")); - return kml_source.getFeatureById(i); - }) + publicScope.downloadStart = function(){ + document.getElementById("download_region_button").disabled = true; + document.documentElement.className = 'wait'; + dataPanel.prepend("<h2 id='download_start' class='ok'>Starting to extract region...</h2><br/>\n"); } - publicScope.downloadRegionFromCityGMLs = function(checkbox_ids) { - //FIXME: Somehow, no feedback comes after large files are downloaded. :( - var features = getCheckedPolygons(checkbox_ids); - + 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 srsName = features[0].get("srsName"); @@ -253,38 +277,15 @@ const regionChooser = (function(){ 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(); - if (proj4.defs(srsName)){ - console.log("Selected region is written in " + srsName + " coordinate system."); - try { - var count = fxapp.downloadRegionFromCityGMLs(sketchAsWKT(srsName), project, citygmlNames.join(";"), srsName); - if (count == -1){ - console.log("No output file has been selected."); - } else { - 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); + if (proj4.defs(srsName)){ + console.log("Selected region is written in " + srsName + " coordinate system."); + fxapp.downloadRegionFromCityGMLs(sketchAsWKT(srsName), project, citygmlNames.join(";"), srsName); + } else { + var msg = "ERROR : Unknown coordinate system : \"" + srsName + "\". Cannot extract any region"; + console.log(msg); + dataPanel.append(msg + "<br/>\n"); + } } function displayInfo() { @@ -328,6 +329,7 @@ const regionChooser = (function(){ } finally { displayHelp(); document.documentElement.className = ''; // Stop waiting + kml_source.getFeatures().forEach(f => refreshStyle(f, "original")); draw.setActive(true); drawnLayer.getFeatures().clear(); intersections.clear(); @@ -383,19 +385,6 @@ const regionChooser = (function(){ 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. - - var checkbox_ids = checkedBoxes.map(c => c.id); - var features = getCheckedPolygons(checkbox_ids); - - features.forEach(f => f.setStyle(utils.polygon_style("#ffff00", 0.8))); - - publicScope.downloadRegionFromCityGMLs(checkedBoxes.map(c => c.id)); - } - publicScope.selectAllOrNone = function(allOrNone) { document.querySelectorAll("input.select_citygml").forEach(c => c.checked = allOrNone); publicScope.isDownloadPossible();