simstadt_openlayers.js 13.9 KB
Newer Older
Eric Duminil's avatar
Eric Duminil committed
1
var regionChooser = (function(){
Eric Duminil's avatar
Eric Duminil committed
2
	//TODO: Somehow split in classes. This file is getting too big and mixed
3
	var publicScope = {};
Eric Duminil's avatar
const    
Eric Duminil committed
4
5
6
	const fromJavaFX = navigator.userAgent.indexOf('JavaFX') !== -1;
	const dataPanel = $('#dataPanel');
	const wgs84Sphere = new ol.Sphere(6378137);
Eric Duminil's avatar
Eric Duminil committed
7
	var features_by_project;
8
9
10
11
12
13
	var gmlId;
	
	publicScope.init = function(){
		//NOTE: Only called from JavaFX. At startup, or when Repo has been changed.
		gmlId = 0;
		kml_source.clear();
Eric Duminil's avatar
Eric Duminil committed
14
		document.getElementById("select_repository").style.visibility = "visible";
15
	}
16
17
	
	if (fromJavaFX){
Eric Duminil's avatar
Eric Duminil committed
18
		document.documentElement.className = 'wait';
19
	}
20
21
22

	var osm_layer = new ol.layer.Tile({
		source: new ol.source.OSM()
eric.duminil's avatar
eric.duminil committed
23
24
	});

Eric Duminil's avatar
Eric Duminil committed
25
	var kml_source = utils.read_kml(fromJavaFX ? undefined : 'data/citygml_hulls.kml');
eric.duminil's avatar
eric.duminil committed
26

27
28
	var kml_layer = new ol.layer.Vector({
		source : kml_source,
Eric Duminil's avatar
Eric Duminil committed
29
		style : utils.polygon_style('#447744', 0.2)
30
31
32
33
34
35
36
37
38
39
	});

	var intersections = new ol.source.Vector();

	var intersections_layer = new ol.layer.Vector({
		source : intersections,
		style : new ol.style.Style({
			fill : new ol.style.Fill({
				color : 'rgba(255, 155, 51, 0.2)'
			})
eric.duminil's avatar
eric.duminil committed
40
41
		})
	});
42

43
	publicScope.addCityGmlHull = function(kmlString) {
44
		options = {featureProjection: ol.proj.get('EPSG:3857')};
45
		feature = kmlFormat.readFeature(kmlString, options);
46
		feature.setId(gmlId++);
47
48
		kml_source.addFeature(feature);
		dataPanel.append('.');
49
		srsName = feature.get("srsName");
50
		if (proj4.defs(srsName) === undefined){
Eric Duminil's avatar
Eric Duminil committed
51
			console.warn(srsName + " isn't defined by Proj4js!")
52
		}
53
54
	};
	
55
56
	var map = new ol.Map({
		target : 'map',
57
		layers : [ osm_layer, kml_layer, intersections_layer ],
58
59
60
61
62
		interactions : ol.interaction.defaults({
			keyboard : true
		})
	});

Eric Duminil's avatar
const    
Eric Duminil committed
63
64
	const geoJsonFormat = new ol.format.GeoJSON();
	const kmlFormat = new ol.format.KML({extractStyles: false});
65

66
	kml_source.addEventListener("addfeature", function() {
67
68
69
70
71
		map.getView().fitExtent(kml_source.getExtent(), (map.getSize()));
	});

	function updateGMLPolygons() {
		kml_source.forEachFeature(function(feature) {
72
			feature["geoJSON"] = geoJsonFormat.writeFeatureObject(feature);
73
			feature["area"] = feature.getGeometry().getArea();
Eric Duminil's avatar
Eric Duminil committed
74
75
			feature["project"] = feature.get("project");
			feature["name"] = feature.get("name");
76
			feature["source"] = "CityGML";
Eric Duminil's avatar
Eric Duminil committed
77
			feature["originalStyle"] = feature.getStyle();
78
		});
Eric Duminil's avatar
Eric Duminil committed
79
		
Eric Duminil's avatar
Eric Duminil committed
80
81
82
		var features = Array.from(kml_source.getFeatures());
		// Sort projects
		features.sort((a, b) => a.project.localeCompare(b.project));
Eric Duminil's avatar
Eric Duminil committed
83
		features_by_project = utils.groupBy(features, "project");
Eric Duminil's avatar
Eric Duminil committed
84
85
		// Sort CityGMLs inside each project
		Object.values(features_by_project).forEach(features => features.sort((a, b) => a.name.localeCompare(b.name)));
86
87
88
89
90
91
92
93
	}

	// The features are not added to a regular vector layer/source,
	// but to a feature overlay which holds a collection of features.
	// This collection is passed to the modify and also the draw
	// interaction, so that both can add or modify features.
	var featureOverlay = new ol.FeatureOverlay({
		style : new ol.style.Style({
eric.duminil's avatar
eric.duminil committed
94
			fill : new ol.style.Fill({
95
96
97
98
99
100
101
102
103
104
105
				color : 'rgba(255, 155, 51, 0.5)'
			}),
			stroke : new ol.style.Stroke({
				color : '#ffcc33',
				width : 4
			}),
			image : new ol.style.Circle({
				radius : 5,
				fill : new ol.style.Fill({
					color : '#ffcc33'
				})
eric.duminil's avatar
eric.duminil committed
106
107
108
			})
		})
	});
109
110
	featureOverlay.setMap(map);

Eric Duminil's avatar
Eric Duminil committed
111
	//TODO: Rename to Javascript naming convention (CamelCase).
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
	var selected_features = featureOverlay.getFeatures();
	selected_features.on('add', function(event) {
		var feature = event.element;
		feature.on("change", function() {
			displayInfo();
		});
	});

	var modify = new ol.interaction.Modify({
		features : featureOverlay.getFeatures(),
		// the SHIFT key must be pressed to delete vertices, so
		// that new vertices can be drawn at the same position
		// of existing vertices
		deleteCondition : function(event) {
			return ol.events.condition.shiftKeyOnly(event) && ol.events.condition.singleClick(event);
		}
	});
	map.addInteraction(modify);

	var draw = new ol.interaction.Draw({
		features : featureOverlay.getFeatures(),
		type : 'Polygon'
	});
	map.addInteraction(draw);

	var sketch;
eric.duminil's avatar
eric.duminil committed
138

139
140
141
142
143
	draw.on('drawstart', function(evt) {
		sketch = evt.feature;
		updateGMLPolygons();
	});
	var sourceProj = map.getView().getProjection();
144
145
146
147
148
149
	
	function showLinkToDownload(feature, jsonIntersection, polygonArea){
		var intersection = geoJsonFormat.readFeature(jsonIntersection);
		var intersectionArea = intersection.getGeometry().getArea();
		var citygml_percentage = Math.round(intersectionArea / feature["area"] * 100);
		var sketch_percentage = Math.round(intersectionArea / polygonArea * 100);
150
		var id = feature.getId();
151
		intersections.addFeature(intersection);
152
153
154
		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>';
155
156
157
158
159
160
161
162

		link += " (" + citygml_percentage + "%";
		if (sketch_percentage == 100) {
			link += ", all inside";
		}
		dataPanel.append(link + ")\n");
	}
	
163
164
	publicScope.highlightPolygon = function(i) {
		var feature = kml_source.getFeatureById(i);
Eric Duminil's avatar
Eric Duminil committed
165
		feature.setStyle(utils.polygon_style("#ff44a2", 0.7));
166
167
168
169
	}
	
	publicScope.resetHighlight = function(i) {
		var feature = kml_source.getFeatureById(i);
Eric Duminil's avatar
Eric Duminil committed
170
		feature.setStyle(feature.originalStyle);
171
172
	}
	
173
	publicScope.isDownloadPossible = function(){
Eric Duminil's avatar
Eric Duminil committed
174
175
176
		kml_source.getFeatures().forEach(f => f.setStyle(f.originalStyle));
		
		//TODO: Dry
177
		var checkedBoxes = Array.from(document.querySelectorAll("input.select_citygml")).filter(c => c.checked);
Eric Duminil's avatar
Eric Duminil committed
178
179
		var checkbox_ids = checkedBoxes.map(c => c.id);
		var features = getCheckedPolygons(checkbox_ids);
Eric Duminil's avatar
Eric Duminil committed
180
		features.forEach(f => f.setStyle(utils.polygon_style("#ffff00", 0.8)));
Eric Duminil's avatar
Eric Duminil committed
181
		
182
		document.getElementById("download_region_button").disabled = (checkedBoxes.length == 0);
Eric Duminil's avatar
Eric Duminil committed
183
184
	}
	
185
	function findIntersection(feature, polygon) {
186
		try {
187
			return turf.intersect(polygon, feature["geoJSON"]);
188
189
190
191
		} catch (err) {
			console.log(feature.get('name') + " - " + err);
		}
	}
192
193

	function findIntersections() {
194
195
		var polygon = geoJsonFormat.writeFeatureObject(sketch);
		var polygonArea = sketch.getGeometry().getArea();
196
197
		var intersection_found = false;
		intersections.clear();
198
		
199
		Object.keys(features_by_project).forEach(function(project) {
200
201
202
203
			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;
204
				dataPanel.append("<h2 class='info'>" + project);
205
206
				features_and_intersections.forEach(l => showLinkToDownload(l[0], l[1], polygonArea));
			}
207
		});
208
		
209
		if (intersection_found) {
210
			document.getElementById("download_region").style.visibility = 'visible';
211
212
		}
		else {
213
			document.getElementById("download_region").style.visibility = 'hidden';
214
			dataPanel.append("No intersection found with any CityGML file.<br/>\n");
eric.duminil's avatar
eric.duminil committed
215
216
		}
	}
217

218
219
220
221
	publicScope.display = function(text){
		dataPanel.append(text + "<br/>\n");
	}
	
Eric Duminil's avatar
Eric Duminil committed
222
223
	getCheckedPolygons = function(checkbox_ids){
		return checkbox_ids.map(checkbox_id => {
224
225
226
			var i = Number(checkbox_id.replace("citygml_", ""));
			return kml_source.getFeatureById(i);
		})
Eric Duminil's avatar
Eric Duminil committed
227
228
229
230
	}
	
	publicScope.downloadRegionFromCityGMLs = function(checkbox_ids) {
		var features = getCheckedPolygons(checkbox_ids);
231
		
Eric Duminil's avatar
Eric Duminil committed
232
233
234
235
		var project = features[0].get("project");
		var srsName = features[0].get("srsName");
		
		if (!features.every( f => f.get("project") === project)){
236
			dataPanel.prepend("<h2 class='error'>Sorry, the CityGML files should all belong to the same project.</h2><br/>\n");
Eric Duminil's avatar
Eric Duminil committed
237
238
239
240
			return;
		}
		
		if (!features.every( f => f.get("srsName") === srsName)){
241
			dataPanel.prepend("<h2 class='error'>Sorry, the CityGML files should all be written with the same coordinate system.</h2><br/>\n");
Eric Duminil's avatar
Eric Duminil committed
242
243
		}
		
Eric Duminil's avatar
Eric Duminil committed
244
245
		document.documentElement.className = 'wait';
		
246
		var citygmlNames = features.map(f => f.get("name"));
247
248
249
250
251
		// 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.");
252
				try {
Eric Duminil's avatar
Eric Duminil committed
253
					var count = fxapp.downloadRegionFromCityGMLs(sketchAsWKT(srsName), project, citygmlNames.join(";"), srsName);
Eric Duminil's avatar
Eric Duminil committed
254
255
256
257
258
					if (count == -1){
						console.log("No output file has been selected.");
					} else {
						dataPanel.prepend("<h2 class='ok'>Done! (" + count + " buildings found) </h2><br/>\n");
					}
259
				} catch (e) {
Eric Duminil's avatar
Eric Duminil committed
260
					console.warn("ERROR : " + e);
261
					dataPanel.prepend("<h2 class='error'>Some problem occured!</h2><br/>\n");
262
				}
263
264
				var end = new Date().getTime();
				var time = end - start;
Eric Duminil's avatar
Eric Duminil committed
265
				console.log('Download Execution time: ' + (time / 1000).toFixed(3) + 's');
266
				setTimeout(function() {
267
					document.getElementById("download_region_button").disabled = false;
Eric Duminil's avatar
Eric Duminil committed
268
					document.documentElement.className = ''; // Stop waiting
269
270
271
272
273
274
275
				}, 100);
			} else {
				var msg = "ERROR : Unknown coordinate system : \"" + srsName + "\". Cannot extract any region";
				console.log(msg);
				dataPanel.append(msg + "<br/>\n");
			}
		}, 100);
eric.duminil's avatar
eric.duminil committed
276
277
	}

278
279
	function displayInfo() {
		dataPanel.empty();
280
		var geom = sketch.getGeometry().clone().transform(sourceProj, 'EPSG:4326');
281
282
		var coordinates = geom.getLinearRing(0).getCoordinates();
		var area = Math.abs(wgs84Sphere.geodesicArea(coordinates));
283
		//NOTE: Could show m², ha or km² depending on magnitude
284
		dataPanel.append("<h3 class='clean'>Area : " + (area / 10000).toFixed(1) + " ha\n");
285
286
		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' +
Eric Duminil's avatar
rename    
Eric Duminil committed
287
288
			'<a href="#" onclick="regionChooser.selectAllOrNone(true);">(Select All)</a>\n' +
			'<a href="#" onclick="regionChooser.selectAllOrNone(false);">(Select None)</a>\n'+
Eric Duminil's avatar
Eric Duminil committed
289
			'</div>\n');
290
		findIntersections();
Eric Duminil's avatar
Eric Duminil committed
291
		dataPanel.append('<button type="button" onclick="regionChooser.copyCoordinatesToClipboard()" id="get_wgs84">Copy coordinates</button><br/>\n')
eric.duminil's avatar
eric.duminil committed
292
	}
293
294
295
296
297

	draw.on('drawend', function() {
		displayInfo();
		draw.setActive(false);
	});
298
299
300
	
	// With OpenLayers 3.9, draw_interaction.removeLastPoint(); might be better.
	document.addEventListener('keydown', function(e) {
Eric Duminil's avatar
Eric Duminil committed
301
302
		//NOTE: e.key isn't defined in JavaFX Browser
		if (e.which == 27 || e.which == 46){ // ESCAPE or DELETE.
303
304
			resetDrawing();
		}
Eric Duminil's avatar
Eric Duminil committed
305
306
307
308
309
		if (e.which == 116 && fromJavaFX){ // F5 for refresh
			dataPanel.prepend("<h2 class='ok'>Refreshing repository...</h2><br/>\n");
			document.documentElement.className = 'wait';
			fxapp.refreshHulls();
		}
310
	});
311

312
313
	function resetDrawing(){
		console.log("Reset drawing");
314
315
316
		try {
			draw.finishDrawing();
		} finally {
Eric Duminil's avatar
Eric Duminil committed
317
			displayHelp();
Eric Duminil's avatar
Eric Duminil committed
318
			document.documentElement.className = ''; // Stop waiting
319
320
321
322
323
			draw.setActive(true);
			featureOverlay.getFeatures().clear();
			intersections.clear();
			focusOnMap();
		}
324
	}
325
	
326
327
328
329
330
331
332
	function sketchAsWKT(srsName) {
		var wktFormat = new ol.format.WKT();
		return wktFormat.writeFeature(sketch, {
			dataProjection : ol.proj.get(srsName),
			featureProjection : ol.proj.get('EPSG:3857')
		});
	}
333
	
334
	function focusOnMap() {
Eric Duminil's avatar
Eric Duminil committed
335
		document.getElementById("map").focus();
336
337
	}
	
338
339
340
341
	var fxapp = undefined;
	
	publicScope.setFxApp = function(app){
		fxapp = app;
342
343
344
		console.log = function(message){
			fxapp.log(message);
		}
345
		
Eric Duminil's avatar
Eric Duminil committed
346
		console.warn = function(message){
347
348
			fxapp.warning(message);
		}
349
	}
Eric Duminil's avatar
Eric Duminil committed
350
351
352
	
	function displayHelp(){
		dataPanel.empty();
353
		dataPanel.append("<h2 class='info'>Welcome to Region Chooser!<br><br>\n");
Eric Duminil's avatar
Eric Duminil committed
354
355
356
357
		dataPanel.append("You can draw a polygon on the map by clicking.<br>\n");
		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");
358
359
360
		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");
Eric Duminil's avatar
Eric Duminil committed
361
362
363
		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");
Eric Duminil's avatar
Eric Duminil committed
364
	}
365

366
367
	// Executed by JavaFX when whole page is loaded.
	publicScope.ready = function() {
368
		updateGMLPolygons();
Eric Duminil's avatar
Eric Duminil committed
369
		displayHelp();
Eric Duminil's avatar
Eric Duminil committed
370
		document.documentElement.className = ''; // Stop waiting
Eric Duminil's avatar
Eric Duminil committed
371
		console.log("Ready!");
372
	}
373
	
374
	publicScope.downloadFromSelectedCityGMLs = function() {
Eric Duminil's avatar
Eric Duminil committed
375
		document.getElementById("download_region_button").disabled = true;
376
		var checkedBoxes = Array.from(document.querySelectorAll("input.select_citygml")).filter(c => c.checked);
Eric Duminil's avatar
Eric Duminil committed
377
		// CheckBoxes isn't empty, because otherwise the button cannot be clicked.
Eric Duminil's avatar
Eric Duminil committed
378
379
380
381
		
		var checkbox_ids = checkedBoxes.map(c => c.id);
		var features = getCheckedPolygons(checkbox_ids);
		
Eric Duminil's avatar
Eric Duminil committed
382
		features.forEach(f => f.setStyle(utils.polygon_style("#ffff00", 0.8)));
Eric Duminil's avatar
Eric Duminil committed
383

Eric Duminil's avatar
Eric Duminil committed
384
		publicScope.downloadRegionFromCityGMLs(checkedBoxes.map(c => c.id));
385
	}
386
	
Eric Duminil's avatar
rename    
Eric Duminil committed
387
	publicScope.selectAllOrNone = function(allOrNone) {
Eric Duminil's avatar
Eric Duminil committed
388
		document.querySelectorAll("input.select_citygml").forEach(c => c.checked = allOrNone);
389
		publicScope.isDownloadPossible();
Eric Duminil's avatar
Eric Duminil committed
390
391
	}
	
392
	publicScope.selectRepository = function() {
393
		fxapp.selectRepository();
394
	}
Eric Duminil's avatar
Eric Duminil committed
395
	
396
397
398
399
400
	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(", ");
Eric Duminil's avatar
Eric Duminil committed
401
		utils.copyToClipboard(wktPolygon + "))", dataPanel);
402
403
	}
	
Eric Duminil's avatar
Eric Duminil committed
404
405
406
	publicScope.showRepositoryName = function(path) {
		document.getElementById("repo_path").textContent = path;
	}
407

408
	focusOnMap();
Eric Duminil's avatar
Eric Duminil committed
409
410
	//var regionChooser = publicScope; //NOTE: In order to open closure. For debugging
	return publicScope;
411
})();