RegionChooserBrowser.java 5.16 KB
Newer Older
eric.duminil's avatar
eric.duminil committed
1
2
3
4
5
6
7
8
package eu.simstadt.regionchooser;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Eric Duminil's avatar
Eric Duminil committed
9
import java.util.logging.Logger;
eric.duminil's avatar
eric.duminil committed
10
import java.util.prefs.Preferences;
11
import org.locationtech.jts.io.ParseException;
eric.duminil's avatar
eric.duminil committed
12
13
import com.ximpleware.NavException;
import com.ximpleware.XPathParseException;
14
import eu.simstadt.regionchooser.fast_xml_parser.ConvexHullCalculator;
15
import javafx.application.Platform;
eric.duminil's avatar
eric.duminil committed
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.Worker.State;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.layout.Region;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import netscape.javascript.JSObject;


public class RegionChooserBrowser extends Region
{
Eric Duminil's avatar
Eric Duminil committed
31
	private static final Logger LOGGER = Logger.getLogger(RegionChooserBrowser.class.getName());
32
	private static final int BUFFER = 1024;
Eric Duminil's avatar
Eric Duminil committed
33

eric.duminil's avatar
eric.duminil committed
34
35
36
37
38
39
40
41
42
	/**
	 * JavaFX Backend for RegionChooser. Inside simstadt_openlayers.js frontend, this class is available as `fxapp`.
	 */
	public class JavaScriptFXBridge
	{
		private Path repo;

		public JavaScriptFXBridge() {
			Preferences userPrefs = Preferences.userRoot().node("/eu/simstadt/desktop");
Eric Duminil's avatar
Eric Duminil committed
43
44
			String repoString = userPrefs.get("RECENT_REPOSITORY", "../TestRepository");
			repo = Paths.get(repoString);
eric.duminil's avatar
eric.duminil committed
45
46
		}

47
48
49
50
		/**
		 * 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.
		 * 
Eric Duminil's avatar
Eric Duminil committed
51
52
		 * NOTE: To be very honest, I don't really understand concurrency in JavaFX. Eric
		 * 
53
54
55
56
57
58
59
		 */
		public void refreshHulls() {
			//NOTE: Could add progress bar?
			Task<Void> task = new Task<Void>() {
				@Override
				public Void call() throws IOException {
					ConvexHullCalculator.extractHullsForEveryCityGML(repo,
Eric Duminil's avatar
Eric Duminil committed
60
							hullKML -> Platform.runLater(() -> jsApp.call("addCityGmlHull", hullKML)));
61
62
63
64
					return null;
				}
			};

Eric Duminil's avatar
Eric Duminil committed
65
			task.setOnRunning(e -> jsApp.call("display", "Importing citgyml. Please wait."));
66

Eric Duminil's avatar
Eric Duminil committed
67
			task.setOnSucceeded(e -> jsApp.call("ready"));
68
69

			new Thread(task).start();
70
71
		}

eric.duminil's avatar
eric.duminil committed
72
		public void downloadRegionFromCityGML(String wktPolygon, String project, String citygml, String srsName)
73
				throws IOException, ParseException, XPathParseException, NavException {
Eric Duminil's avatar
Eric Duminil committed
74
75
			StringBuilder sb = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, srsName,
					citygmlPath(project, citygml));
eric.duminil's avatar
eric.duminil committed
76
77
78

			File buildingIdsFile = selectSaveFileWithDialog(project, citygml, "selected_region");
			if (buildingIdsFile != null) {
Eric Duminil's avatar
Eric Duminil committed
79
				try (BufferedWriter writer = Files.newBufferedWriter(buildingIdsFile.toPath())) {
80
81
82
83
84
85
					char[] chars = new char[BUFFER];
					for (int aPosStart = 0; aPosStart < sb.length(); aPosStart += BUFFER) {
						int chunk = Math.min(BUFFER, sb.length() - aPosStart);
						sb.getChars(aPosStart, aPosStart + chunk, chars, 0);
						writer.write(chars, 0, chunk);
					}
Eric Duminil's avatar
Eric Duminil committed
86
				}
eric.duminil's avatar
eric.duminil committed
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
			}
		}

		private File selectSaveFileWithDialog(String project, String citygml, String suffix) {
			Stage mainStage = (Stage) RegionChooserBrowser.this.getScene().getWindow();
			FileChooser fileChooser = new FileChooser();
			fileChooser.setTitle("Save CITYGML ids");
			if (project != null) {
				fileChooser.setInitialDirectory(repo.resolve(project + ".proj").toFile());
			} else {
				fileChooser.setInitialDirectory(repo.toFile());
			}
			if (suffix.isEmpty()) {
				fileChooser.setInitialFileName(citygml);
			} else {
				fileChooser.setInitialFileName(citygml.replace(".", "_" + suffix + "."));
			}
			FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("GML files (*.gml)", "*.gml");
			fileChooser.getExtensionFilters().add(extFilter);
			return fileChooser.showSaveDialog(mainStage);
		}

		public void log(String text) {
Eric Duminil's avatar
Eric Duminil committed
110
			LOGGER.info(text);
eric.duminil's avatar
eric.duminil committed
111
112
		}

113
114
115
116
		public void warning(String text) {
			LOGGER.warning(text);
		}

eric.duminil's avatar
eric.duminil committed
117
118
119
120
121
122
123
124
		private Path citygmlPath(String project, String citygml) {
			return repo.resolve(project + ".proj").resolve(citygml);
		}

	}

	final WebView browser = new WebView();
	final WebEngine webEngine = browser.getEngine();
125
	final JavaScriptFXBridge fxapp = new JavaScriptFXBridge();
126
	private JSObject jsApp;
eric.duminil's avatar
eric.duminil committed
127
128
129
130
131
132
133
134
135
136

	public RegionChooserBrowser() {
		//apply the styles
		getStyleClass().add("browser");
		String url = RegionChooserFX.class.getResource("website/index.html").toExternalForm();
		webEngine.load(url); // load the web page
		// process page loading
		webEngine.getLoadWorker().stateProperty().addListener(
				(ObservableValue<? extends State> ov, State oldState, State newState) -> {
					if (newState == State.SUCCEEDED) {
137
138
						jsApp = (JSObject) webEngine.executeScript("regionChooser");
						jsApp.call("setFxApp", fxapp);
139
						fxapp.refreshHulls();
eric.duminil's avatar
eric.duminil committed
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
					}
				});
		//add the web view to the scene
		getChildren().add(browser);
	}

	@Override
	protected void layoutChildren() {
		double w = getWidth();
		double h = getHeight();
		layoutInArea(browser, 0, 0, w, h, 0, HPos.CENTER, VPos.CENTER);
	}

	@Override
	protected double computePrefWidth(double height) {
155
		return 1024;
eric.duminil's avatar
eric.duminil committed
156
157
158
159
	}

	@Override
	protected double computePrefHeight(double width) {
160
		return 720;
eric.duminil's avatar
eric.duminil committed
161
162
	}
}