acdc4e8f3db8c8cb77c3a9e2026f8f5a9328c19e
[lhc/web/www.git] / www / plugins / gis / lib / leaflet / plugins / KML.js
1 L.KML = L.FeatureGroup.extend({
2 options: {
3 async: true
4 },
5
6 initialize: function(kml, options) {
7 L.Util.setOptions(this, options);
8 this._kml = kml;
9 this._layers = {};
10
11 if (kml) {
12 this.addKML(kml, options, this.options.async);
13 }
14 },
15
16 loadXML: function(url, cb, options, async) {
17 if (async === undefined) async = this.options.async;
18 if (options === undefined) options = this.options;
19
20 var req = new window.XMLHttpRequest();
21
22 // Check for IE8 and IE9 Fix Cors for those browsers
23 if (req.withCredentials === undefined && typeof window.XDomainRequest !== 'undefined') {
24 var xdr = new window.XDomainRequest();
25 xdr.open('GET', url, async);
26 xdr.onprogress = function () { };
27 xdr.ontimeout = function () { };
28 xdr.onerror = function () { };
29 xdr.onload = function () {
30 if (xdr.responseText) {
31 var xml = new window.ActiveXObject('Microsoft.XMLDOM');
32 xml.loadXML(xdr.responseText);
33 cb(xml, options);
34 }
35 };
36 setTimeout(function () { xdr.send(); }, 0);
37 } else {
38 req.open('GET', url, async);
39 try {
40 req.overrideMimeType('text/xml'); // unsupported by IE
41 } catch (e) { }
42 req.onreadystatechange = function () {
43 if (req.readyState !== 4) return;
44 if (req.status === 200) cb(req.responseXML, options);
45 };
46 req.send(null);
47 }
48 },
49
50 addKML: function(url, options, async) {
51 var _this = this;
52 var cb = function(gpx, options) { _this._addKML(gpx, options); };
53 this.loadXML(url, cb, options, async);
54 },
55
56 _addKML: function(xml, options) {
57 var layers = L.KML.parseKML(xml);
58 if (!layers || !layers.length) return;
59 for (var i = 0; i < layers.length; i++) {
60 this.fire('addlayer', {
61 layer: layers[i]
62 });
63 this.addLayer(layers[i]);
64 }
65 this.latLngs = L.KML.getLatLngs(xml);
66 this.fire('loaded');
67 },
68
69 latLngs: []
70 });
71
72 L.Util.extend(L.KML, {
73
74 parseKML: function (xml) {
75 var style = this.parseStyles(xml);
76 this.parseStyleMap(xml, style);
77 var el = xml.getElementsByTagName('Folder');
78 var layers = [], l;
79 for (var i = 0; i < el.length; i++) {
80 if (!this._check_folder(el[i])) { continue; }
81 l = this.parseFolder(el[i], style);
82 if (l) { layers.push(l); }
83 }
84 el = xml.getElementsByTagName('Placemark');
85 for (var j = 0; j < el.length; j++) {
86 if (!this._check_folder(el[j])) { continue; }
87 l = this.parsePlacemark(el[j], xml, style);
88 if (l) { layers.push(l); }
89 }
90 el = xml.getElementsByTagName('GroundOverlay');
91 for (var k = 0; k < el.length; k++) {
92 l = this.parseGroundOverlay(el[k]);
93 if (l) { layers.push(l); }
94 }
95 return layers;
96 },
97
98 // Return false if e's first parent Folder is not [folder]
99 // - returns true if no parent Folders
100 _check_folder: function (e, folder) {
101 e = e.parentNode;
102 while (e && e.tagName !== 'Folder')
103 {
104 e = e.parentNode;
105 }
106 return !e || e === folder;
107 },
108
109 parseStyles: function(xml) {
110 var styles = {};
111 var sl = xml.getElementsByTagName('Style');
112 for (var i=0, len=sl.length; i<len; i++) {
113 var style = this.parseStyle(sl[i]);
114 if (style) {
115 var styleName = '#' + style.id;
116 styles[styleName] = style;
117 }
118 }
119 return styles;
120 },
121
122 parseStyle: function (xml) {
123 var style = {}, poptions = {}, ioptions = {}, el, id;
124
125 var attributes = { color: true, width: true, Icon: true, href: true, hotSpot: true };
126
127 function _parse(xml) {
128 var options = {};
129 for (var i = 0; i < xml.childNodes.length; i++) {
130 var e = xml.childNodes[i];
131 var key = e.tagName;
132 if (!attributes[key]) { continue; }
133 if (key === 'hotSpot')
134 {
135 for (var j = 0; j < e.attributes.length; j++) {
136 options[e.attributes[j].name] = e.attributes[j].nodeValue;
137 }
138 } else {
139 var value = e.childNodes[0].nodeValue;
140 if (key === 'color') {
141 options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
142 options.color = '#' + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
143 } else if (key === 'width') {
144 options.weight = value;
145 } else if (key === 'Icon') {
146 ioptions = _parse(e);
147 if (ioptions.href) { options.href = ioptions.href; }
148 } else if (key === 'href') {
149 options.href = value;
150 }
151 }
152 }
153 return options;
154 }
155
156 el = xml.getElementsByTagName('LineStyle');
157 if (el && el[0]) { style = _parse(el[0]); }
158 el = xml.getElementsByTagName('PolyStyle');
159 if (el && el[0]) { poptions = _parse(el[0]); }
160 if (poptions.color) { style.fillColor = poptions.color; }
161 if (poptions.opacity) { style.fillOpacity = poptions.opacity; }
162 el = xml.getElementsByTagName('IconStyle');
163 if (el && el[0]) { ioptions = _parse(el[0]); }
164 if (ioptions.href) {
165 style.icon = new L.KMLIcon({
166 iconUrl: ioptions.href,
167 shadowUrl: null,
168 anchorRef: {x: ioptions.x, y: ioptions.y},
169 anchorType: {x: ioptions.xunits, y: ioptions.yunits}
170 });
171 }
172
173 id = xml.getAttribute('id');
174 if (id && style) {
175 style.id = id;
176 }
177
178 return style;
179 },
180
181 parseStyleMap: function (xml, existingStyles) {
182 var sl = xml.getElementsByTagName('StyleMap');
183
184 for (var i = 0; i < sl.length; i++) {
185 var e = sl[i], el;
186 var smKey, smStyleUrl;
187
188 el = e.getElementsByTagName('key');
189 if (el && el[0]) { smKey = el[0].textContent; }
190 el = e.getElementsByTagName('styleUrl');
191 if (el && el[0]) { smStyleUrl = el[0].textContent; }
192
193 if (smKey === 'normal')
194 {
195 existingStyles['#' + e.getAttribute('id')] = existingStyles[smStyleUrl];
196 }
197 }
198
199 return;
200 },
201
202 parseFolder: function (xml, style) {
203 var el, layers = [], l;
204 el = xml.getElementsByTagName('Folder');
205 for (var i = 0; i < el.length; i++) {
206 if (!this._check_folder(el[i], xml)) { continue; }
207 l = this.parseFolder(el[i], style);
208 if (l) { layers.push(l); }
209 }
210 el = xml.getElementsByTagName('Placemark');
211 for (var j = 0; j < el.length; j++) {
212 if (!this._check_folder(el[j], xml)) { continue; }
213 l = this.parsePlacemark(el[j], xml, style);
214 if (l) { layers.push(l); }
215 }
216 el = xml.getElementsByTagName('GroundOverlay');
217 for (var k = 0; k < el.length; k++) {
218 if (!this._check_folder(el[k], xml)) { continue; }
219 l = this.parseGroundOverlay(el[k]);
220 if (l) { layers.push(l); }
221 }
222 if (!layers.length) { return; }
223 if (layers.length === 1) { return layers[0]; }
224 return new L.FeatureGroup(layers);
225 },
226
227 parsePlacemark: function (place, xml, style) {
228 var h, i, j, k, el, il, options = {};
229
230 var multi = ['MultiGeometry', 'MultiTrack', 'gx:MultiTrack'];
231 for (h in multi) {
232 el = place.getElementsByTagName(multi[h]);
233 for (i = 0; i < el.length; i++) {
234 return this.parsePlacemark(el[i], xml, style);
235 }
236 }
237
238 el = place.getElementsByTagName('styleUrl');
239 for (i = 0; i < el.length; i++) {
240 var url = el[i].childNodes[0].nodeValue;
241 for (var a in style[url]) {
242 options[a] = style[url][a];
243 }
244 }
245
246 il = place.getElementsByTagName('Style')[0];
247 if (il) {
248 var inlineStyle = this.parseStyle(place);
249 if (inlineStyle) {
250 for (k in inlineStyle) {
251 options[k] = inlineStyle[k];
252 }
253 }
254 }
255
256 var layers = [];
257
258 var parse = ['LineString', 'Polygon', 'Point', 'Track', 'gx:Track'];
259 for (j in parse) {
260 var tag = parse[j];
261 el = place.getElementsByTagName(tag);
262 for (i = 0; i < el.length; i++) {
263 var l = this['parse' + tag.replace(/gx:/, '')](el[i], xml, options);
264 if (l) { layers.push(l); }
265 }
266 }
267
268 if (!layers.length) {
269 return;
270 }
271 var layer = layers[0];
272 if (layers.length > 1) {
273 layer = new L.FeatureGroup(layers);
274 }
275
276 var name, descr = '';
277 el = place.getElementsByTagName('name');
278 if (el.length && el[0].childNodes.length) {
279 name = el[0].childNodes[0].nodeValue;
280 }
281 el = place.getElementsByTagName('description');
282 for (i = 0; i < el.length; i++) {
283 for (j = 0; j < el[i].childNodes.length; j++) {
284 descr = descr + el[i].childNodes[j].nodeValue;
285 }
286 }
287
288 if (name) {
289 layer.on('add', function(e) {
290 layer.bindPopup('<h2>' + name + '</h2>' + descr);
291 });
292 }
293
294 return layer;
295 },
296
297 parseCoords: function (xml) {
298 var el = xml.getElementsByTagName('coordinates');
299 return this._read_coords(el[0]);
300 },
301
302 parseLineString: function (line, xml, options) {
303 var coords = this.parseCoords(line);
304 if (!coords.length) { return; }
305 return new L.Polyline(coords, options);
306 },
307
308 parseTrack: function (line, xml, options) {
309 var el = xml.getElementsByTagName('gx:coord');
310 if (el.length === 0) { el = xml.getElementsByTagName('coord'); }
311 var coords = [];
312 for (var j = 0; j < el.length; j++) {
313 coords = coords.concat(this._read_gxcoords(el[j]));
314 }
315 if (!coords.length) { return; }
316 return new L.Polyline(coords, options);
317 },
318
319 parsePoint: function (line, xml, options) {
320 var el = line.getElementsByTagName('coordinates');
321 if (!el.length) {
322 return;
323 }
324 var ll = el[0].childNodes[0].nodeValue.split(',');
325 return new L.KMLMarker(new L.LatLng(ll[1], ll[0]), options);
326 },
327
328 parsePolygon: function (line, xml, options) {
329 var el, polys = [], inner = [], i, coords;
330 el = line.getElementsByTagName('outerBoundaryIs');
331 for (i = 0; i < el.length; i++) {
332 coords = this.parseCoords(el[i]);
333 if (coords) {
334 polys.push(coords);
335 }
336 }
337 el = line.getElementsByTagName('innerBoundaryIs');
338 for (i = 0; i < el.length; i++) {
339 coords = this.parseCoords(el[i]);
340 if (coords) {
341 inner.push(coords);
342 }
343 }
344 if (!polys.length) {
345 return;
346 }
347 if (options.fillColor) {
348 options.fill = true;
349 }
350 if (polys.length === 1) {
351 return new L.Polygon(polys.concat(inner), options);
352 }
353 return new L.MultiPolygon(polys, options);
354 },
355
356 getLatLngs: function (xml) {
357 var el = xml.getElementsByTagName('coordinates');
358 var coords = [];
359 for (var j = 0; j < el.length; j++) {
360 // text might span many childNodes
361 coords = coords.concat(this._read_coords(el[j]));
362 }
363 return coords;
364 },
365
366 _read_coords: function (el) {
367 var text = '', coords = [], i;
368 for (i = 0; i < el.childNodes.length; i++) {
369 text = text + el.childNodes[i].nodeValue;
370 }
371 text = text.split(/[\s\n]+/);
372 for (i = 0; i < text.length; i++) {
373 var ll = text[i].split(',');
374 if (ll.length < 2) {
375 continue;
376 }
377 coords.push(new L.LatLng(ll[1], ll[0]));
378 }
379 return coords;
380 },
381
382 _read_gxcoords: function (el) {
383 var text = '', coords = [];
384 text = el.firstChild.nodeValue.split(' ');
385 coords.push(new L.LatLng(text[1], text[0]));
386 return coords;
387 },
388
389 parseGroundOverlay: function (xml) {
390 var latlonbox = xml.getElementsByTagName('LatLonBox')[0];
391 var bounds = new L.LatLngBounds(
392 [
393 latlonbox.getElementsByTagName('south')[0].childNodes[0].nodeValue,
394 latlonbox.getElementsByTagName('west')[0].childNodes[0].nodeValue
395 ],
396 [
397 latlonbox.getElementsByTagName('north')[0].childNodes[0].nodeValue,
398 latlonbox.getElementsByTagName('east')[0].childNodes[0].nodeValue
399 ]
400 );
401 var attributes = {Icon: true, href: true, color: true};
402 function _parse(xml) {
403 var options = {}, ioptions = {};
404 for (var i = 0; i < xml.childNodes.length; i++) {
405 var e = xml.childNodes[i];
406 var key = e.tagName;
407 if (!attributes[key]) { continue; }
408 var value = e.childNodes[0].nodeValue;
409 if (key === 'Icon') {
410 ioptions = _parse(e);
411 if (ioptions.href) { options.href = ioptions.href; }
412 } else if (key === 'href') {
413 options.href = value;
414 } else if (key === 'color') {
415 options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
416 options.color = '#' + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
417 }
418 }
419 return options;
420 }
421 var options = {};
422 options = _parse(xml);
423 if (latlonbox.getElementsByTagName('rotation')[0] !== undefined) {
424 var rotation = latlonbox.getElementsByTagName('rotation')[0].childNodes[0].nodeValue;
425 options.rotation = parseFloat(rotation);
426 }
427 return new L.RotatedImageOverlay(options.href, bounds, {opacity: options.opacity, angle: options.rotation});
428 }
429
430 });
431
432 L.KMLIcon = L.Icon.extend({
433 _setIconStyles: function (img, name) {
434 L.Icon.prototype._setIconStyles.apply(this, [img, name]);
435 var options = this.options;
436 this.options.popupAnchor = [0,(-0.83*img.height)];
437 if (options.anchorType.x === 'fraction')
438 img.style.marginLeft = (-options.anchorRef.x * img.width) + 'px';
439 if (options.anchorType.y === 'fraction')
440 img.style.marginTop = ((-(1 - options.anchorRef.y) * img.height) + 1) + 'px';
441 if (options.anchorType.x === 'pixels')
442 img.style.marginLeft = (-options.anchorRef.x) + 'px';
443 if (options.anchorType.y === 'pixels')
444 img.style.marginTop = (options.anchorRef.y - img.height + 1) + 'px';
445 }
446 });
447
448
449 L.KMLMarker = L.Marker.extend({
450 options: {
451 icon: new L.KMLIcon.Default()
452 }
453 });
454
455 // Inspired by https://github.com/bbecquet/Leaflet.PolylineDecorator/tree/master/src
456 L.RotatedImageOverlay = L.ImageOverlay.extend({
457 options: {
458 angle: 0
459 },
460 _reset: function () {
461 L.ImageOverlay.prototype._reset.call(this);
462 this._rotate();
463 },
464 _animateZoom: function (e) {
465 L.ImageOverlay.prototype._animateZoom.call(this, e);
466 this._rotate();
467 },
468 _rotate: function () {
469 if (L.DomUtil.TRANSFORM) {
470 // use the CSS transform rule if available
471 this._image.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)';
472 } else if(L.Browser.ie) {
473 // fallback for IE6, IE7, IE8
474 var rad = this.options.angle * (Math.PI / 180),
475 costheta = Math.cos(rad),
476 sintheta = Math.sin(rad);
477 this._image.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' +
478 costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')';
479 }
480 },
481 getBounds: function() {
482 return this._bounds;
483 }
484 });
485