simstadt_openlayers.js 14.3 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 = {};
4
	var fromJavaFX = navigator.userAgent.indexOf('JavaFX') !== -1;
5
6
	var dataPanel = $('#dataPanel');
	var 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
	});

25
26
27
28
29
30
31
32
33
	function read_kml(url){
		return new ol.source.KML({
			projection : ol.proj.get('EPSG:3857'),
			url : url,
			extractAttributes : false,
			extractStyles : false
		});
	}
	
34
	var kml_source = read_kml(fromJavaFX ? undefined : 'data/citygml_hulls.kml');
eric.duminil's avatar
eric.duminil committed
35

36
37
38
39
40
41
42
43
44
45
46
47
	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 ]
			}),
		});
	}
eric.duminil's avatar
eric.duminil committed
48

49
50
	var kml_layer = new ol.layer.Vector({
		source : kml_source,
51
		style : polygon_style('#447744', 0.2)
52
53
54
55
56
57
58
59
60
61
	});

	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
62
63
		})
	});
64

65
	publicScope.addCityGmlHull = function(kmlString) {
66
		options = {featureProjection: ol.proj.get('EPSG:3857')};
67
		feature = kmlFormat.readFeature(kmlString, options);
68
		feature.setId(gmlId++);
69
70
		kml_source.addFeature(feature);
		dataPanel.append('.');
71
		srsName = feature.get("srsName");
72
		if (proj4.defs(srsName) === undefined){
Eric Duminil's avatar
Eric Duminil committed
73
			console.warn(srsName + " isn't defined by Proj4js!")
74
		}
75
76
	};
	
77
78
	var map = new ol.Map({
		target : 'map',
79
		layers : [ osm_layer, kml_layer, intersections_layer ],
80
81
82
83
84
		interactions : ol.interaction.defaults({
			keyboard : true
		})
	});

85
	var geoJsonFormat = new ol.format.GeoJSON();
86
	var kmlFormat = new ol.format.KML({extractStyles: false});
87

88
	kml_source.addEventListener("addfeature", function() {
89
90
91
92
93
		map.getView().fitExtent(kml_source.getExtent(), (map.getSize()));
	});

	function updateGMLPolygons() {
		kml_source.forEachFeature(function(feature) {
94
			feature["geoJSON"] = geoJsonFormat.writeFeatureObject(feature);
95
			feature["area"] = feature.getGeometry().getArea();
Eric Duminil's avatar
Eric Duminil committed
96
97
			feature["project"] = feature.get("project");
			feature["name"] = feature.get("name");
98
			feature["source"] = "CityGML";
Eric Duminil's avatar
Eric Duminil committed
99
			feature["originalStyle"] = feature.getStyle();
100
		});
Eric Duminil's avatar
Eric Duminil committed
101
		
Eric Duminil's avatar
Eric Duminil committed
102
103
104
		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
105
		features_by_project = utils.groupBy(features, "project");
Eric Duminil's avatar
Eric Duminil committed
106
107
		// Sort CityGMLs inside each project
		Object.values(features_by_project).forEach(features => features.sort((a, b) => a.name.localeCompare(b.name)));
108
109
110
111
112
113
114
115
	}

	// 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
116
			fill : new ol.style.Fill({
117
118
119
120
121
122
123
124
125
126
127
				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
128
129
130
			})
		})
	});
131
132
	featureOverlay.setMap(map);

Eric Duminil's avatar
Eric Duminil committed
133
	//TODO: Rename to Javascript naming convention (CamelCase).
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
	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
160

161
162
163
164
165
	draw.on('drawstart', function(evt) {
		sketch = evt.feature;
		updateGMLPolygons();
	});
	var sourceProj = map.getView().getProjection();
166
167
168
169
170
171
	
	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);
172
		var id = feature.getId();
173
		intersections.addFeature(intersection);
174
175
176
		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>';
177
178
179
180
181
182
183
184

		link += " (" + citygml_percentage + "%";
		if (sketch_percentage == 100) {
			link += ", all inside";
		}
		dataPanel.append(link + ")\n");
	}
	
185
186
187
188
189
190
191
	publicScope.highlightPolygon = function(i) {
		var feature = kml_source.getFeatureById(i);
		feature.setStyle(polygon_style("#ff44a2", 0.7));
	}
	
	publicScope.resetHighlight = function(i) {
		var feature = kml_source.getFeatureById(i);
Eric Duminil's avatar
Eric Duminil committed
192
		feature.setStyle(feature.originalStyle);
193
194
	}
	
195
	publicScope.isDownloadPossible = function(){
Eric Duminil's avatar
Eric Duminil committed
196
197
198
		kml_source.getFeatures().forEach(f => f.setStyle(f.originalStyle));
		
		//TODO: Dry
199
		var checkedBoxes = Array.from(document.querySelectorAll("input.select_citygml")).filter(c => c.checked);
Eric Duminil's avatar
Eric Duminil committed
200
201
202
203
		var checkbox_ids = checkedBoxes.map(c => c.id);
		var features = getCheckedPolygons(checkbox_ids);
		features.forEach(f => f.setStyle(polygon_style("#ffff00", 0.8)));
		
204
		document.getElementById("download_region_button").disabled = (checkedBoxes.length == 0);
Eric Duminil's avatar
Eric Duminil committed
205
206
	}
	
207
	function findIntersection(feature, polygon) {
208
		try {
209
			return turf.intersect(polygon, feature["geoJSON"]);
210
211
212
213
		} catch (err) {
			console.log(feature.get('name') + " - " + err);
		}
	}
214
215

	function findIntersections() {
216
217
		var polygon = geoJsonFormat.writeFeatureObject(sketch);
		var polygonArea = sketch.getGeometry().getArea();
218
219
		var intersection_found = false;
		intersections.clear();
220
		
221
		Object.keys(features_by_project).forEach(function(project) {
222
223
224
225
			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;
226
				dataPanel.append("<h2 class='info'>" + project);
227
228
				features_and_intersections.forEach(l => showLinkToDownload(l[0], l[1], polygonArea));
			}
229
		});
230
		
231
		if (intersection_found) {
232
			document.getElementById("download_region").style.visibility = 'visible';
233
234
		}
		else {
235
			document.getElementById("download_region").style.visibility = 'hidden';
236
			dataPanel.append("No intersection found with any CityGML file.<br/>\n");
eric.duminil's avatar
eric.duminil committed
237
238
		}
	}
239

240
241
242
243
	publicScope.display = function(text){
		dataPanel.append(text + "<br/>\n");
	}
	
Eric Duminil's avatar
Eric Duminil committed
244
245
	getCheckedPolygons = function(checkbox_ids){
		return checkbox_ids.map(checkbox_id => {
246
247
248
			var i = Number(checkbox_id.replace("citygml_", ""));
			return kml_source.getFeatureById(i);
		})
Eric Duminil's avatar
Eric Duminil committed
249
250
251
252
	}
	
	publicScope.downloadRegionFromCityGMLs = function(checkbox_ids) {
		var features = getCheckedPolygons(checkbox_ids);
253
		
Eric Duminil's avatar
Eric Duminil committed
254
255
256
257
		var project = features[0].get("project");
		var srsName = features[0].get("srsName");
		
		if (!features.every( f => f.get("project") === project)){
258
			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
259
260
261
262
			return;
		}
		
		if (!features.every( f => f.get("srsName") === srsName)){
263
			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
264
265
		}
		
Eric Duminil's avatar
Eric Duminil committed
266
267
		document.documentElement.className = 'wait';
		
268
		var citygmlNames = features.map(f => f.get("name"));
269
270
271
272
273
		// 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.");
274
				try {
Eric Duminil's avatar
Eric Duminil committed
275
					var count = fxapp.downloadRegionFromCityGMLs(sketchAsWKT(srsName), project, citygmlNames.join(";"), srsName);
Eric Duminil's avatar
Eric Duminil committed
276
277
278
279
280
					if (count == -1){
						console.log("No output file has been selected.");
					} else {
						dataPanel.prepend("<h2 class='ok'>Done! (" + count + " buildings found) </h2><br/>\n");
					}
281
				} catch (e) {
Eric Duminil's avatar
Eric Duminil committed
282
					console.warn("ERROR : " + e);
283
					dataPanel.prepend("<h2 class='error'>Some problem occured!</h2><br/>\n");
284
				}
285
286
				var end = new Date().getTime();
				var time = end - start;
Eric Duminil's avatar
Eric Duminil committed
287
				console.log('Download Execution time: ' + (time / 1000).toFixed(3) + 's');
288
				setTimeout(function() {
289
					document.getElementById("download_region_button").disabled = false;
Eric Duminil's avatar
Eric Duminil committed
290
					document.documentElement.className = ''; // Stop waiting
291
292
293
294
295
296
297
				}, 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
298
299
	}

300
301
	function displayInfo() {
		dataPanel.empty();
302
		var geom = sketch.getGeometry().clone().transform(sourceProj, 'EPSG:4326');
303
304
		var coordinates = geom.getLinearRing(0).getCoordinates();
		var area = Math.abs(wgs84Sphere.geodesicArea(coordinates));
305
		//NOTE: Could show m², ha or km² depending on magnitude
306
		dataPanel.append("<h3 class='clean'>Area : " + (area / 10000).toFixed(1) + " ha\n");
307
308
309
310
		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'+
Eric Duminil's avatar
Eric Duminil committed
311
			'</div>\n');
312
		findIntersections();
Eric Duminil's avatar
Eric Duminil committed
313
		dataPanel.append('<button type="button" onclick="regionChooser.copyCoordinatesToClipboard()" id="get_wgs84">Copy coordinates</button><br/>\n')
eric.duminil's avatar
eric.duminil committed
314
	}
315
316
317
318
319

	draw.on('drawend', function() {
		displayInfo();
		draw.setActive(false);
	});
320
321
322
	
	// With OpenLayers 3.9, draw_interaction.removeLastPoint(); might be better.
	document.addEventListener('keydown', function(e) {
Eric Duminil's avatar
Eric Duminil committed
323
324
		//NOTE: e.key isn't defined in JavaFX Browser
		if (e.which == 27 || e.which == 46){ // ESCAPE or DELETE.
325
326
			resetDrawing();
		}
Eric Duminil's avatar
Eric Duminil committed
327
328
329
330
331
		if (e.which == 116 && fromJavaFX){ // F5 for refresh
			dataPanel.prepend("<h2 class='ok'>Refreshing repository...</h2><br/>\n");
			document.documentElement.className = 'wait';
			fxapp.refreshHulls();
		}
332
	});
333

334
335
	function resetDrawing(){
		console.log("Reset drawing");
336
337
338
		try {
			draw.finishDrawing();
		} finally {
Eric Duminil's avatar
Eric Duminil committed
339
			displayHelp();
Eric Duminil's avatar
Eric Duminil committed
340
			document.documentElement.className = ''; // Stop waiting
341
342
343
344
345
			draw.setActive(true);
			featureOverlay.getFeatures().clear();
			intersections.clear();
			focusOnMap();
		}
346
	}
347
	
348
349
350
351
352
353
354
	function sketchAsWKT(srsName) {
		var wktFormat = new ol.format.WKT();
		return wktFormat.writeFeature(sketch, {
			dataProjection : ol.proj.get(srsName),
			featureProjection : ol.proj.get('EPSG:3857')
		});
	}
355
	
356
	function focusOnMap() {
Eric Duminil's avatar
Eric Duminil committed
357
		document.getElementById("map").focus();
358
359
	}
	
360
361
362
363
	var fxapp = undefined;
	
	publicScope.setFxApp = function(app){
		fxapp = app;
364
365
366
		console.log = function(message){
			fxapp.log(message);
		}
367
		
Eric Duminil's avatar
Eric Duminil committed
368
		console.warn = function(message){
369
370
			fxapp.warning(message);
		}
371
	}
Eric Duminil's avatar
Eric Duminil committed
372
373
374
	
	function displayHelp(){
		dataPanel.empty();
375
		dataPanel.append("<h2 class='info'>Welcome to Region Chooser!<br><br>\n");
Eric Duminil's avatar
Eric Duminil committed
376
377
378
379
		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");
380
381
382
		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
383
384
385
		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
386
	}
387

388
389
	// Executed by JavaFX when whole page is loaded.
	publicScope.ready = function() {
390
		updateGMLPolygons();
Eric Duminil's avatar
Eric Duminil committed
391
		displayHelp();
Eric Duminil's avatar
Eric Duminil committed
392
		document.documentElement.className = ''; // Stop waiting
Eric Duminil's avatar
Eric Duminil committed
393
		console.log("Ready!");
394
	}
395
	
396
	publicScope.downloadFromSelectedCityGMLs = function() {
Eric Duminil's avatar
Eric Duminil committed
397
		document.getElementById("download_region_button").disabled = true;
398
		var checkedBoxes = Array.from(document.querySelectorAll("input.select_citygml")).filter(c => c.checked);
Eric Duminil's avatar
Eric Duminil committed
399
		// CheckBoxes isn't empty, because otherwise the button cannot be clicked.
Eric Duminil's avatar
Eric Duminil committed
400
401
402
403
404
405
		
		var checkbox_ids = checkedBoxes.map(c => c.id);
		var features = getCheckedPolygons(checkbox_ids);
		
		features.forEach(f => f.setStyle(polygon_style("#ffff00", 0.8)));

Eric Duminil's avatar
Eric Duminil committed
406
		publicScope.downloadRegionFromCityGMLs(checkedBoxes.map(c => c.id));
407
	}
408
	
Eric Duminil's avatar
Eric Duminil committed
409
410
	publicScope.checkCityGMLS = function(allOrNone) {
		document.querySelectorAll("input.select_citygml").forEach(c => c.checked = allOrNone);
411
		publicScope.isDownloadPossible();
Eric Duminil's avatar
Eric Duminil committed
412
413
	}
	
414
	publicScope.selectRepository = function() {
415
		fxapp.selectRepository();
416
	}
Eric Duminil's avatar
Eric Duminil committed
417
	
418
419
420
421
422
	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
423
		utils.copyToClipboard(wktPolygon + "))", dataPanel);
424
425
	}
	
Eric Duminil's avatar
Eric Duminil committed
426
427
428
	publicScope.showRepositoryName = function(path) {
		document.getElementById("repo_path").textContent = path;
	}
429

430
	focusOnMap();
Eric Duminil's avatar
Eric Duminil committed
431
432
	//var regionChooser = publicScope; //NOTE: In order to open closure. For debugging
	return publicScope;
433
})();