simstadt_openlayers.js 12.1 KB
Newer Older
Eric Duminil's avatar
Eric Duminil committed
1
var regionChooser = (function(){
2
	var publicScope = {};
3
	var fromJavaFX = navigator.userAgent.indexOf('JavaFX') !== -1;
4
	//NOTE: Could do without jQuery
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
71
72
73
		srsName = feature.get("srsName") || "EPSG:31467";
		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.
Eric Duminil's avatar
Eric Duminil committed
166
		link += '<input type="checkbox" id="citygml_' + feature.getId() + '" class="select_citygml"><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
	function findIntersection(feature, polygon) {
176
		try {
177
			return turf.intersect(polygon, feature["geoJSON"]);
178
179
180
181
		} catch (err) {
			console.log(feature.get('name') + " - " + err);
		}
	}
182
183

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

210
211
212
213
	publicScope.display = function(text){
		dataPanel.append(text + "<br/>\n");
	}
	
214
	publicScope.downloadRegionFromCityGMLs = function(checkbox_ids) {
215
216
		// TODO: Disable all links
		// TODO: DRY
217
218
219
220
221
222
223
224
		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
		var srsName = features[0].get("srsName") || "EPSG:31467"; //TODO: Check all the same
		var citygmlNames = features.map(f => f.get("name"));
225
226
227
228
		// 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
229
				document.documentElement.className = 'wait';
230
				console.log("Selected region is written in " + srsName + " coordinate system.");
231
				try {
232
					fxapp.downloadRegionFromCityGMLs(sketchAsWKT(srsName), project, citygmlNames.join(";"), srsName);
233
234
					dataPanel.append("<h2 class='ok'>Done!</h2><br/>\n");
				} catch (e) {
235
					console.warning("ERROR : " + e);
236
237
					dataPanel.append("<h2 class='error'>Some problem occured!</h2><br/>\n");
				}
238
239
				var end = new Date().getTime();
				var time = end - start;
Eric Duminil's avatar
Eric Duminil committed
240
				console.log('Download Execution time: ' + (time / 1000).toFixed(3) + 's');
241
				setTimeout(function() {
Eric Duminil's avatar
Eric Duminil committed
242
					document.documentElement.className = ''; // Stop waiting
243
244
245
246
247
248
249
				}, 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
250
251
	}

252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
	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
269
		}
270
		dataPanel.append("<h3 class='clean'>Area : " + (area / 10000).toFixed(1) + " ha\n");
Eric Duminil's avatar
Eric Duminil committed
271
		//TODO: Hide button if empty
Eric Duminil's avatar
Eric Duminil committed
272
273
274
		dataPanel.append('<button type="button" onclick="regionChooser.downloadFromSelectedCityGMLs()" id="download" style="visibility:hidden">Download Region</button><br/>\n');
		dataPanel.append('<a href="#" onclick="regionChooser.checkCityGMLS(true);">(Select All)</a>');
		dataPanel.append('<a href="#" onclick="regionChooser.checkCityGMLS(false);">(Select None)</a>');
275
		dataPanel.append('<br/>\n');
276
		findIntersections();
eric.duminil's avatar
eric.duminil committed
277
	}
278
279
280
281
282

	draw.on('drawend', function() {
		displayInfo();
		draw.setActive(false);
	});
283
284
285
286
287
288
289
290
	
	// 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();
		}
	});
291

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

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

383
	focusOnMap();
Eric Duminil's avatar
Eric Duminil committed
384
385
	//var regionChooser = publicScope; //NOTE: In order to open closure. For debugging
	return publicScope;
386
})();