simstadt_openlayers.js 12.6 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);
7
8
9
10
11
12
	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
13
		document.getElementById("select_repository").style.visibility = "visible";
14
	}
15
16
	
	if (fromJavaFX){
Eric Duminil's avatar
Eric Duminil committed
17
		document.documentElement.className = 'wait';
18
	}
19
20
21

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

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

35
36
37
38
39
40
41
42
43
44
45
46
	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
47

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

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

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

84
	var geoJsonFormat = new ol.format.GeoJSON();
85
	var kmlFormat = new ol.format.KML({extractStyles: false});
86
87
88
89
90
91
92

	kml_layer.addEventListener("change", function() {
		map.getView().fitExtent(kml_source.getExtent(), (map.getSize()));
	});

	function updateGMLPolygons() {
		kml_source.forEachFeature(function(feature) {
93
			feature["geoJSON"] = geoJsonFormat.writeFeatureObject(feature);
94
			feature["area"] = feature.getGeometry().getArea();
Eric Duminil's avatar
Eric Duminil committed
95
96
			feature["project"] = feature.get("project");
			feature["name"] = feature.get("name");
97
98
99
100
101
102
103
104
105
106
			feature["source"] = "CityGML";
		});
	}

	// 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
107
			fill : new ol.style.Fill({
108
109
110
111
112
113
114
115
116
117
118
				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
119
120
121
			})
		})
	});
122
123
	featureOverlay.setMap(map);

Eric Duminil's avatar
Eric Duminil committed
124
	//TODO: Rename to Javascript naming convention (CamelCase).
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
	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
151

152
153
154
155
156
	draw.on('drawstart', function(evt) {
		sketch = evt.feature;
		updateGMLPolygons();
	});
	var sourceProj = map.getView().getProjection();
157
158
159
160
161
162
163
164
	
	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);
		intersections.addFeature(intersection);
		var link = '<li>'
Eric Duminil's avatar
Notes    
Eric Duminil committed
165
        // TODO: If possible, highlight the corresponding polygon when hovering above a name.
166
		link += '<input type="checkbox" id="citygml_' + feature.getId() + '" class="select_citygml" onclick="regionChooser.isDownloadPossible();"><label for="citygml_' + feature.getId() + '">' +  feature['name'] + '</label>';
167
168
169
170
171
172
173
174

		link += " (" + citygml_percentage + "%";
		if (sketch_percentage == 100) {
			link += ", all inside";
		}
		dataPanel.append(link + ")\n");
	}
	
175
	publicScope.isDownloadPossible = function(){
176
177
		var checkedBoxes = Array.from(document.querySelectorAll("input.select_citygml")).filter(c => c.checked);
		document.getElementById("download_region_button").disabled = (checkedBoxes.length == 0);
Eric Duminil's avatar
Eric Duminil committed
178
179
	}
	
180
	function findIntersection(feature, polygon) {
181
		try {
182
			return turf.intersect(polygon, feature["geoJSON"]);
183
184
185
186
		} catch (err) {
			console.log(feature.get('name') + " - " + err);
		}
	}
187
188

	function findIntersections() {
189
190
		var polygon = geoJsonFormat.writeFeatureObject(sketch);
		var polygonArea = sketch.getGeometry().getArea();
191
192
		var intersection_found = false;
		intersections.clear();
193
		//NOTE: getFeatures seems to not be sorted anymore. :-/
194
		features_by_project = groupBy(kml_source.getFeatures(), "project");
195
		
196
		Object.keys(features_by_project).forEach(function(project) {
197
		    features = features_by_project[project];
198
199
200
		    features_and_intersections = features.map(f=> [f, findIntersection(f,polygon)]).filter(l => l[1] !== undefined);
		    if (features_and_intersections.length > 0){
		    	intersection_found = true;
201
				dataPanel.append("<h2 class='info'>" + project);
202
203
		    	features_and_intersections.forEach(l => showLinkToDownload(l[0], l[1], polygonArea));
		    }
204
		});
205
		
206
		if (intersection_found) {
207
			document.getElementById("download_region").style.visibility = 'visible';
208
209
		}
		else {
210
			document.getElementById("download_region").style.visibility = 'hidden';
211
			dataPanel.append("No intersection found with any CityGML file.<br/>\n");
eric.duminil's avatar
eric.duminil committed
212
213
		}
	}
214

215
216
217
218
	publicScope.display = function(text){
		dataPanel.append(text + "<br/>\n");
	}
	
219
	publicScope.downloadRegionFromCityGMLs = function(checkbox_ids) {
220
221
222
223
224
225
		var features = checkbox_ids.map(checkbox_id => {
			var i = Number(checkbox_id.replace("citygml_", ""));
			return kml_source.getFeatureById(i);
		})
		
		var project = features[0].get("project"); //TODO: Check all the same
226
		var srsName = features[0].get("srsName"); //TODO: Check all the same
227
		var citygmlNames = features.map(f => f.get("name"));
228
229
230
231
		// Waiting 100ms in order to let the cursor change
		setTimeout(function() {
			var start = new Date().getTime();
			if (proj4.defs(srsName)){
Eric Duminil's avatar
Eric Duminil committed
232
				document.documentElement.className = 'wait';
233
				console.log("Selected region is written in " + srsName + " coordinate system.");
234
				try {
235
					fxapp.downloadRegionFromCityGMLs(sketchAsWKT(srsName), project, citygmlNames.join(";"), srsName);
236
237
					dataPanel.append("<h2 class='ok'>Done!</h2><br/>\n");
				} catch (e) {
238
					console.warning("ERROR : " + e);
239
240
					dataPanel.append("<h2 class='error'>Some problem occured!</h2><br/>\n");
				}
241
242
				var end = new Date().getTime();
				var time = end - start;
Eric Duminil's avatar
Eric Duminil committed
243
				console.log('Download Execution time: ' + (time / 1000).toFixed(3) + 's');
244
				setTimeout(function() {
Eric Duminil's avatar
Eric Duminil committed
245
					resetDrawing();
246
					document.getElementById("download_region_button").disabled = false;
Eric Duminil's avatar
Eric Duminil committed
247
					document.documentElement.className = ''; // Stop waiting
248
249
250
251
252
253
254
				}, 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
255
256
	}

257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
	function displayInfo() {
		dataPanel.empty();
		var geom = /** @type {ol.geom.Polygon} */
		(sketch.getGeometry().clone().transform(sourceProj, 'EPSG:4326'));
		var coordinates = geom.getLinearRing(0).getCoordinates();
		var area = Math.abs(wgs84Sphere.geodesicArea(coordinates));
		var coords = geom.getLinearRing(0).getCoordinates();
		if (!fromJavaFX) {
			var wgs84_coords = "";
			var n = coords.length;
			for (var i = 0; i < n; i++) {
				var wgs84_coord = coords[i];
				wgs84_coords += "(" + wgs84_coord[1] + "," + wgs84_coord[0] + ")<br/>";

			}
			dataPanel.append("WGS84 Coordinates<br/>");
			dataPanel.append(wgs84_coords + "<br/>\n");
eric.duminil's avatar
eric.duminil committed
274
		}
275
		//NOTE: Could show m², ha or km² depending on magnitude
276
		dataPanel.append("<h3 class='clean'>Area : " + (area / 10000).toFixed(1) + " ha\n");
277
278
279
280
		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
281
			'</div>\n');
282
		findIntersections();
eric.duminil's avatar
eric.duminil committed
283
	}
284
285
286
287
288

	draw.on('drawend', function() {
		displayInfo();
		draw.setActive(false);
	});
289
290
291
292
293
294
295
296
	
	// Pressing ESCAPE or DELETE resets the drawing.
	// With OpenLayers 3.9, draw_interaction.removeLastPoint(); might be better.
	document.addEventListener('keydown', function(e) {
		if (e.which == 27 || e.which == 46){
			resetDrawing();
		}
	});
297

298
299
	function resetDrawing(){
		console.log("Reset drawing");
300
301
302
		try {
			draw.finishDrawing();
		} finally {
Eric Duminil's avatar
Eric Duminil committed
303
			displayHelp();
Eric Duminil's avatar
Eric Duminil committed
304
			document.documentElement.className = ''; // Stop waiting
305
306
307
308
309
			draw.setActive(true);
			featureOverlay.getFeatures().clear();
			intersections.clear();
			focusOnMap();
		}
310
	}
311
	
312
313
314
315
316
317
318
	function sketchAsWKT(srsName) {
		var wktFormat = new ol.format.WKT();
		return wktFormat.writeFeature(sketch, {
			dataProjection : ol.proj.get(srsName),
			featureProjection : ol.proj.get('EPSG:3857')
		});
	}
319
	
320
	function focusOnMap() {
Eric Duminil's avatar
Eric Duminil committed
321
		document.getElementById("map").focus();
322
323
	}
	
324
325
326
327
	var fxapp = undefined;
	
	publicScope.setFxApp = function(app){
		fxapp = app;
328
329
330
		console.log = function(message){
			fxapp.log(message);
		}
331
332
333
334
		
		console.warning = function(message){
			fxapp.warning(message);
		}
335
	}
Eric Duminil's avatar
Eric Duminil committed
336
	
337
338
339
340
341
342
343
	groupBy = function(xs, key) {
		  return xs.reduce(function(rv, x) {
			(rv[x[key]] = rv[x[key]] || []).push(x);
			return rv;
		  }, {});
		};
	
Eric Duminil's avatar
Eric Duminil committed
344
345
	function displayHelp(){
		dataPanel.empty();
346
		dataPanel.append("<h2 class='info'>Welcome to Region Chooser!<br><br>\n");
Eric Duminil's avatar
Eric Duminil committed
347
348
349
350
		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");
351
352
353
		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
354
355
356
		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
357
	}
358

359
360
	// Executed by JavaFX when whole page is loaded.
	publicScope.ready = function() {
361
		updateGMLPolygons();
Eric Duminil's avatar
Eric Duminil committed
362
		displayHelp();
Eric Duminil's avatar
Eric Duminil committed
363
		document.documentElement.className = ''; // Stop waiting
364
		console.log("READY!");
365
	}
366
	
367
	publicScope.downloadFromSelectedCityGMLs = function() {
368
		var checkedBoxes = Array.from(document.querySelectorAll("input.select_citygml")).filter(c => c.checked);
Eric Duminil's avatar
Eric Duminil committed
369
		if (checkedBoxes.length === 0){
Eric Duminil's avatar
Eric Duminil committed
370
371
			console.log("You should select at least one citygml, though.");
		} else{
372
			document.getElementById("download_region_button").disabled = true;
373
			publicScope.downloadRegionFromCityGMLs(checkedBoxes.map(c => c.id));
Eric Duminil's avatar
Eric Duminil committed
374
		}
375
	}
376
	
Eric Duminil's avatar
Eric Duminil committed
377
378
	publicScope.checkCityGMLS = function(allOrNone) {
		document.querySelectorAll("input.select_citygml").forEach(c => c.checked = allOrNone);
379
		publicScope.isDownloadPossible();
Eric Duminil's avatar
Eric Duminil committed
380
381
	}
	
382
	publicScope.selectRepository = function() {
383
		fxapp.selectRepository();
384
	}
Eric Duminil's avatar
Eric Duminil committed
385
386
387
388
	
	publicScope.showRepositoryName = function(path) {
		document.getElementById("repo_path").textContent = path;
	}
389

390
	focusOnMap();
Eric Duminil's avatar
Eric Duminil committed
391
392
	//var regionChooser = publicScope; //NOTE: In order to open closure. For debugging
	return publicScope;
393
})();