1 | /* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for |
---|
2 | * full list of contributors). Published under the Clear BSD license. |
---|
3 | * See http://svn.openlayers.org/trunk/openlayers/license.txt for the |
---|
4 | * full text of the license. */ |
---|
5 | |
---|
6 | /** |
---|
7 | * @requires OpenLayers/Format/XML.js |
---|
8 | * @requires OpenLayers/Feature/Vector.js |
---|
9 | * @requires OpenLayers/Geometry/Point.js |
---|
10 | * @requires OpenLayers/Geometry/LineString.js |
---|
11 | * @requires OpenLayers/Geometry/Polygon.js |
---|
12 | * @requires OpenLayers/Geometry/Collection.js |
---|
13 | * @requires OpenLayers/Request/XMLHttpRequest.js |
---|
14 | * @requires OpenLayers/Console.js |
---|
15 | * @requires OpenLayers/Projection.js |
---|
16 | */ |
---|
17 | |
---|
18 | /** |
---|
19 | * Class: OpenLayers.Format.KML |
---|
20 | * Read/Write KML. Create a new instance with the <OpenLayers.Format.KML> |
---|
21 | * constructor. |
---|
22 | * |
---|
23 | * Inherits from: |
---|
24 | * - <OpenLayers.Format.XML> |
---|
25 | */ |
---|
26 | OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, { |
---|
27 | |
---|
28 | /** |
---|
29 | * Property: namespaces |
---|
30 | * {Object} Mapping of namespace aliases to namespace URIs. |
---|
31 | */ |
---|
32 | namespaces: { |
---|
33 | kml: "http://www.opengis.net/kml/2.2", |
---|
34 | gx: "http://www.google.com/kml/ext/2.2" |
---|
35 | }, |
---|
36 | |
---|
37 | /** |
---|
38 | * APIProperty: kmlns |
---|
39 | * {String} KML Namespace to use. Defaults to 2.0 namespace. |
---|
40 | */ |
---|
41 | kmlns: "http://earth.google.com/kml/2.0", |
---|
42 | |
---|
43 | /** |
---|
44 | * APIProperty: placemarksDesc |
---|
45 | * {String} Name of the placemarks. Default is "No description available". |
---|
46 | */ |
---|
47 | placemarksDesc: "No description available", |
---|
48 | |
---|
49 | /** |
---|
50 | * APIProperty: foldersName |
---|
51 | * {String} Name of the folders. Default is "OpenLayers export". |
---|
52 | * If set to null, no name element will be created. |
---|
53 | */ |
---|
54 | foldersName: "OpenLayers export", |
---|
55 | |
---|
56 | /** |
---|
57 | * APIProperty: foldersDesc |
---|
58 | * {String} Description of the folders. Default is "Exported on [date]." |
---|
59 | * If set to null, no description element will be created. |
---|
60 | */ |
---|
61 | foldersDesc: "Exported on " + new Date(), |
---|
62 | |
---|
63 | /** |
---|
64 | * APIProperty: extractAttributes |
---|
65 | * {Boolean} Extract attributes from KML. Default is true. |
---|
66 | * Extracting styleUrls requires this to be set to true |
---|
67 | */ |
---|
68 | extractAttributes: true, |
---|
69 | |
---|
70 | /** |
---|
71 | * Property: extractStyles |
---|
72 | * {Boolean} Extract styles from KML. Default is false. |
---|
73 | * Extracting styleUrls also requires extractAttributes to be |
---|
74 | * set to true |
---|
75 | */ |
---|
76 | extractStyles: false, |
---|
77 | |
---|
78 | /** |
---|
79 | * APIProperty: extractTracks |
---|
80 | * {Boolean} Extract gx:Track elements from Placemark elements. Default |
---|
81 | * is false. If true, features will be generated for all points in |
---|
82 | * all gx:Track elements. Features will have a when (Date) attribute |
---|
83 | * based on when elements in the track. If tracks include angle |
---|
84 | * elements, features will have heading, tilt, and roll attributes. |
---|
85 | * If track point coordinates have three values, features will have |
---|
86 | * an altitude attribute with the third coordinate value. |
---|
87 | */ |
---|
88 | extractTracks: false, |
---|
89 | |
---|
90 | /** |
---|
91 | * APIProperty: trackAttributes |
---|
92 | * {Array} If <extractTracks> is true, points within gx:Track elements will |
---|
93 | * be parsed as features with when, heading, tilt, and roll attributes. |
---|
94 | * Any additional attribute names can be provided in <trackAttributes>. |
---|
95 | */ |
---|
96 | trackAttributes: null, |
---|
97 | |
---|
98 | /** |
---|
99 | * Property: internalns |
---|
100 | * {String} KML Namespace to use -- defaults to the namespace of the |
---|
101 | * Placemark node being parsed, but falls back to kmlns. |
---|
102 | */ |
---|
103 | internalns: null, |
---|
104 | |
---|
105 | /** |
---|
106 | * Property: features |
---|
107 | * {Array} Array of features |
---|
108 | * |
---|
109 | */ |
---|
110 | features: null, |
---|
111 | |
---|
112 | /** |
---|
113 | * Property: styles |
---|
114 | * {Object} Storage of style objects |
---|
115 | * |
---|
116 | */ |
---|
117 | styles: null, |
---|
118 | |
---|
119 | /** |
---|
120 | * Property: styleBaseUrl |
---|
121 | * {String} |
---|
122 | */ |
---|
123 | styleBaseUrl: "", |
---|
124 | |
---|
125 | /** |
---|
126 | * Property: fetched |
---|
127 | * {Object} Storage of KML URLs that have been fetched before |
---|
128 | * in order to prevent reloading them. |
---|
129 | */ |
---|
130 | fetched: null, |
---|
131 | |
---|
132 | /** |
---|
133 | * APIProperty: maxDepth |
---|
134 | * {Integer} Maximum depth for recursive loading external KML URLs |
---|
135 | * Defaults to 0: do no external fetching |
---|
136 | */ |
---|
137 | maxDepth: 0, |
---|
138 | |
---|
139 | /** |
---|
140 | * Constructor: OpenLayers.Format.KML |
---|
141 | * Create a new parser for KML. |
---|
142 | * |
---|
143 | * Parameters: |
---|
144 | * options - {Object} An optional object whose properties will be set on |
---|
145 | * this instance. |
---|
146 | */ |
---|
147 | initialize: function(options) { |
---|
148 | // compile regular expressions once instead of every time they are used |
---|
149 | this.regExes = { |
---|
150 | trimSpace: (/^\s*|\s*$/g), |
---|
151 | removeSpace: (/\s*/g), |
---|
152 | splitSpace: (/\s+/), |
---|
153 | trimComma: (/\s*,\s*/g), |
---|
154 | kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/), |
---|
155 | kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/), |
---|
156 | straightBracket: (/\$\[(.*?)\]/g) |
---|
157 | }; |
---|
158 | // KML coordinates are always in longlat WGS84 |
---|
159 | this.externalProjection = new OpenLayers.Projection("EPSG:4326"); |
---|
160 | |
---|
161 | OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); |
---|
162 | }, |
---|
163 | |
---|
164 | /** |
---|
165 | * APIMethod: read |
---|
166 | * Read data from a string, and return a list of features. |
---|
167 | * |
---|
168 | * Parameters: |
---|
169 | * data - {String} or {DOMElement} data to read/parse. |
---|
170 | * |
---|
171 | * Returns: |
---|
172 | * {Array(<OpenLayers.Feature.Vector>)} List of features. |
---|
173 | */ |
---|
174 | read: function(data) { |
---|
175 | this.features = []; |
---|
176 | this.styles = {}; |
---|
177 | this.fetched = {}; |
---|
178 | |
---|
179 | // Set default options |
---|
180 | var options = { |
---|
181 | depth: 0, |
---|
182 | styleBaseUrl: this.styleBaseUrl |
---|
183 | }; |
---|
184 | |
---|
185 | return this.parseData(data, options); |
---|
186 | }, |
---|
187 | |
---|
188 | /** |
---|
189 | * Method: parseData |
---|
190 | * Read data from a string, and return a list of features. |
---|
191 | * |
---|
192 | * Parameters: |
---|
193 | * data - {String} or {DOMElement} data to read/parse. |
---|
194 | * options - {Object} Hash of options |
---|
195 | * |
---|
196 | * Returns: |
---|
197 | * {Array(<OpenLayers.Feature.Vector>)} List of features. |
---|
198 | */ |
---|
199 | parseData: function(data, options) { |
---|
200 | if(typeof data == "string") { |
---|
201 | data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); |
---|
202 | } |
---|
203 | |
---|
204 | // Loop throught the following node types in this order and |
---|
205 | // process the nodes found |
---|
206 | var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"]; |
---|
207 | for(var i=0, len=types.length; i<len; ++i) { |
---|
208 | var type = types[i]; |
---|
209 | |
---|
210 | var nodes = this.getElementsByTagNameNS(data, "*", type); |
---|
211 | |
---|
212 | // skip to next type if no nodes are found |
---|
213 | if(nodes.length == 0) { |
---|
214 | continue; |
---|
215 | } |
---|
216 | |
---|
217 | switch (type.toLowerCase()) { |
---|
218 | |
---|
219 | // Fetch external links |
---|
220 | case "link": |
---|
221 | case "networklink": |
---|
222 | this.parseLinks(nodes, options); |
---|
223 | break; |
---|
224 | |
---|
225 | // parse style information |
---|
226 | case "style": |
---|
227 | if (this.extractStyles) { |
---|
228 | this.parseStyles(nodes, options); |
---|
229 | } |
---|
230 | break; |
---|
231 | case "stylemap": |
---|
232 | if (this.extractStyles) { |
---|
233 | this.parseStyleMaps(nodes, options); |
---|
234 | } |
---|
235 | break; |
---|
236 | |
---|
237 | // parse features |
---|
238 | case "placemark": |
---|
239 | this.parseFeatures(nodes, options); |
---|
240 | break; |
---|
241 | } |
---|
242 | } |
---|
243 | |
---|
244 | return this.features; |
---|
245 | }, |
---|
246 | |
---|
247 | /** |
---|
248 | * Method: parseLinks |
---|
249 | * Finds URLs of linked KML documents and fetches them |
---|
250 | * |
---|
251 | * Parameters: |
---|
252 | * nodes - {Array} of {DOMElement} data to read/parse. |
---|
253 | * options - {Object} Hash of options |
---|
254 | * |
---|
255 | */ |
---|
256 | parseLinks: function(nodes, options) { |
---|
257 | |
---|
258 | // Fetch external links <NetworkLink> and <Link> |
---|
259 | // Don't do anything if we have reached our maximum depth for recursion |
---|
260 | if (options.depth >= this.maxDepth) { |
---|
261 | return false; |
---|
262 | } |
---|
263 | |
---|
264 | // increase depth |
---|
265 | var newOptions = OpenLayers.Util.extend({}, options); |
---|
266 | newOptions.depth++; |
---|
267 | |
---|
268 | for(var i=0, len=nodes.length; i<len; i++) { |
---|
269 | var href = this.parseProperty(nodes[i], "*", "href"); |
---|
270 | if(href && !this.fetched[href]) { |
---|
271 | this.fetched[href] = true; // prevent reloading the same urls |
---|
272 | var data = this.fetchLink(href); |
---|
273 | if (data) { |
---|
274 | this.parseData(data, newOptions); |
---|
275 | } |
---|
276 | } |
---|
277 | } |
---|
278 | |
---|
279 | }, |
---|
280 | |
---|
281 | /** |
---|
282 | * Method: fetchLink |
---|
283 | * Fetches a URL and returns the result |
---|
284 | * |
---|
285 | * Parameters: |
---|
286 | * href - {String} url to be fetched |
---|
287 | * |
---|
288 | */ |
---|
289 | fetchLink: function(href) { |
---|
290 | var request = OpenLayers.Request.GET({url: href, async: false}); |
---|
291 | if (request) { |
---|
292 | return request.responseText; |
---|
293 | } |
---|
294 | }, |
---|
295 | |
---|
296 | /** |
---|
297 | * Method: parseStyles |
---|
298 | * Looks for <Style> nodes in the data and parses them |
---|
299 | * Also parses <StyleMap> nodes, but only uses the 'normal' key |
---|
300 | * |
---|
301 | * Parameters: |
---|
302 | * nodes - {Array} of {DOMElement} data to read/parse. |
---|
303 | * options - {Object} Hash of options |
---|
304 | * |
---|
305 | */ |
---|
306 | parseStyles: function(nodes, options) { |
---|
307 | for(var i=0, len=nodes.length; i<len; i++) { |
---|
308 | var style = this.parseStyle(nodes[i]); |
---|
309 | if(style) { |
---|
310 | var styleName = (options.styleBaseUrl || "") + "#" + style.id; |
---|
311 | |
---|
312 | this.styles[styleName] = style; |
---|
313 | } |
---|
314 | } |
---|
315 | }, |
---|
316 | |
---|
317 | /** |
---|
318 | * Method: parseKmlColor |
---|
319 | * Parses a kml color (in 'aabbggrr' format) and returns the corresponding |
---|
320 | * color and opacity or null if the color is invalid. |
---|
321 | * |
---|
322 | * Parameters: |
---|
323 | * kmlColor - {String} a kml formated color |
---|
324 | * |
---|
325 | * Returns: |
---|
326 | * {Object} |
---|
327 | */ |
---|
328 | parseKmlColor: function(kmlColor) { |
---|
329 | var color = null; |
---|
330 | if (kmlColor) { |
---|
331 | var matches = kmlColor.match(this.regExes.kmlColor); |
---|
332 | if (matches) { |
---|
333 | color = { |
---|
334 | color: '#' + matches[4] + matches[3] + matches[2], |
---|
335 | opacity: parseInt(matches[1], 16) / 255 |
---|
336 | }; |
---|
337 | } |
---|
338 | } |
---|
339 | return color; |
---|
340 | }, |
---|
341 | |
---|
342 | /** |
---|
343 | * Method: parseStyle |
---|
344 | * Parses the children of a <Style> node and builds the style hash |
---|
345 | * accordingly |
---|
346 | * |
---|
347 | * Parameters: |
---|
348 | * node - {DOMElement} <Style> node |
---|
349 | * |
---|
350 | */ |
---|
351 | parseStyle: function(node) { |
---|
352 | var style = {}; |
---|
353 | |
---|
354 | var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle", |
---|
355 | "LabelStyle"]; |
---|
356 | var type, nodeList, geometry, parser; |
---|
357 | for(var i=0, len=types.length; i<len; ++i) { |
---|
358 | type = types[i]; |
---|
359 | styleTypeNode = this.getElementsByTagNameNS(node, |
---|
360 | "*", type)[0]; |
---|
361 | if(!styleTypeNode) { |
---|
362 | continue; |
---|
363 | } |
---|
364 | |
---|
365 | // only deal with first geometry of this type |
---|
366 | switch (type.toLowerCase()) { |
---|
367 | case "linestyle": |
---|
368 | var kmlColor = this.parseProperty(styleTypeNode, "*", "color"); |
---|
369 | var color = this.parseKmlColor(kmlColor); |
---|
370 | if (color) { |
---|
371 | style["strokeColor"] = color.color; |
---|
372 | style["strokeOpacity"] = color.opacity; |
---|
373 | } |
---|
374 | |
---|
375 | var width = this.parseProperty(styleTypeNode, "*", "width"); |
---|
376 | if (width) { |
---|
377 | style["strokeWidth"] = width; |
---|
378 | } |
---|
379 | break; |
---|
380 | |
---|
381 | case "polystyle": |
---|
382 | var kmlColor = this.parseProperty(styleTypeNode, "*", "color"); |
---|
383 | var color = this.parseKmlColor(kmlColor); |
---|
384 | if (color) { |
---|
385 | style["fillOpacity"] = color.opacity; |
---|
386 | style["fillColor"] = color.color; |
---|
387 | } |
---|
388 | // Check if fill is disabled |
---|
389 | var fill = this.parseProperty(styleTypeNode, "*", "fill"); |
---|
390 | if (fill == "0") { |
---|
391 | style["fillColor"] = "none"; |
---|
392 | } |
---|
393 | // Check if outline is disabled |
---|
394 | var outline = this.parseProperty(styleTypeNode, "*", "outline"); |
---|
395 | if (outline == "0") { |
---|
396 | style["strokeWidth"] = "0"; |
---|
397 | } |
---|
398 | |
---|
399 | break; |
---|
400 | |
---|
401 | case "iconstyle": |
---|
402 | // set scale |
---|
403 | var scale = parseFloat(this.parseProperty(styleTypeNode, |
---|
404 | "*", "scale") || 1); |
---|
405 | |
---|
406 | // set default width and height of icon |
---|
407 | var width = 32 * scale; |
---|
408 | var height = 32 * scale; |
---|
409 | |
---|
410 | var iconNode = this.getElementsByTagNameNS(styleTypeNode, |
---|
411 | "*", |
---|
412 | "Icon")[0]; |
---|
413 | if (iconNode) { |
---|
414 | var href = this.parseProperty(iconNode, "*", "href"); |
---|
415 | if (href) { |
---|
416 | |
---|
417 | var w = this.parseProperty(iconNode, "*", "w"); |
---|
418 | var h = this.parseProperty(iconNode, "*", "h"); |
---|
419 | |
---|
420 | // Settings for Google specific icons that are 64x64 |
---|
421 | // We set the width and height to 64 and halve the |
---|
422 | // scale to prevent icons from being too big |
---|
423 | var google = "http://maps.google.com/mapfiles/kml"; |
---|
424 | if (OpenLayers.String.startsWith( |
---|
425 | href, google) && !w && !h) { |
---|
426 | w = 64; |
---|
427 | h = 64; |
---|
428 | scale = scale / 2; |
---|
429 | } |
---|
430 | |
---|
431 | // if only dimension is defined, make sure the |
---|
432 | // other one has the same value |
---|
433 | w = w || h; |
---|
434 | h = h || w; |
---|
435 | |
---|
436 | if (w) { |
---|
437 | width = parseInt(w) * scale; |
---|
438 | } |
---|
439 | |
---|
440 | if (h) { |
---|
441 | height = parseInt(h) * scale; |
---|
442 | } |
---|
443 | |
---|
444 | // support for internal icons |
---|
445 | // (/root://icons/palette-x.png) |
---|
446 | // x and y tell the position on the palette: |
---|
447 | // - in pixels |
---|
448 | // - starting from the left bottom |
---|
449 | // We translate that to a position in the list |
---|
450 | // and request the appropriate icon from the |
---|
451 | // google maps website |
---|
452 | var matches = href.match(this.regExes.kmlIconPalette); |
---|
453 | if (matches) { |
---|
454 | var palette = matches[1]; |
---|
455 | var file_extension = matches[2]; |
---|
456 | |
---|
457 | var x = this.parseProperty(iconNode, "*", "x"); |
---|
458 | var y = this.parseProperty(iconNode, "*", "y"); |
---|
459 | |
---|
460 | var posX = x ? x/32 : 0; |
---|
461 | var posY = y ? (7 - y/32) : 7; |
---|
462 | |
---|
463 | var pos = posY * 8 + posX; |
---|
464 | href = "http://maps.google.com/mapfiles/kml/pal" |
---|
465 | + palette + "/icon" + pos + file_extension; |
---|
466 | } |
---|
467 | |
---|
468 | style["graphicOpacity"] = 1; // fully opaque |
---|
469 | style["externalGraphic"] = href; |
---|
470 | } |
---|
471 | |
---|
472 | } |
---|
473 | |
---|
474 | |
---|
475 | // hotSpots define the offset for an Icon |
---|
476 | var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode, |
---|
477 | "*", |
---|
478 | "hotSpot")[0]; |
---|
479 | if (hotSpotNode) { |
---|
480 | var x = parseFloat(hotSpotNode.getAttribute("x")); |
---|
481 | var y = parseFloat(hotSpotNode.getAttribute("y")); |
---|
482 | |
---|
483 | var xUnits = hotSpotNode.getAttribute("xunits"); |
---|
484 | if (xUnits == "pixels") { |
---|
485 | style["graphicXOffset"] = -x * scale; |
---|
486 | } |
---|
487 | else if (xUnits == "insetPixels") { |
---|
488 | style["graphicXOffset"] = -width + (x * scale); |
---|
489 | } |
---|
490 | else if (xUnits == "fraction") { |
---|
491 | style["graphicXOffset"] = -width * x; |
---|
492 | } |
---|
493 | |
---|
494 | var yUnits = hotSpotNode.getAttribute("yunits"); |
---|
495 | if (yUnits == "pixels") { |
---|
496 | style["graphicYOffset"] = -height + (y * scale) + 1; |
---|
497 | } |
---|
498 | else if (yUnits == "insetPixels") { |
---|
499 | style["graphicYOffset"] = -(y * scale) + 1; |
---|
500 | } |
---|
501 | else if (yUnits == "fraction") { |
---|
502 | style["graphicYOffset"] = -height * (1 - y) + 1; |
---|
503 | } |
---|
504 | } |
---|
505 | |
---|
506 | style["graphicWidth"] = width; |
---|
507 | style["graphicHeight"] = height; |
---|
508 | break; |
---|
509 | |
---|
510 | case "balloonstyle": |
---|
511 | var balloonStyle = OpenLayers.Util.getXmlNodeValue( |
---|
512 | styleTypeNode); |
---|
513 | if (balloonStyle) { |
---|
514 | style["balloonStyle"] = balloonStyle.replace( |
---|
515 | this.regExes.straightBracket, "${$1}"); |
---|
516 | } |
---|
517 | break; |
---|
518 | case "labelstyle": |
---|
519 | var kmlColor = this.parseProperty(styleTypeNode, "*", "color"); |
---|
520 | var color = this.parseKmlColor(kmlColor); |
---|
521 | if (color) { |
---|
522 | style["fontColor"] = color.color; |
---|
523 | style["fontOpacity"] = color.opacity; |
---|
524 | } |
---|
525 | break; |
---|
526 | |
---|
527 | default: |
---|
528 | } |
---|
529 | } |
---|
530 | |
---|
531 | // Some polygons have no line color, so we use the fillColor for that |
---|
532 | if (!style["strokeColor"] && style["fillColor"]) { |
---|
533 | style["strokeColor"] = style["fillColor"]; |
---|
534 | } |
---|
535 | |
---|
536 | var id = node.getAttribute("id"); |
---|
537 | if (id && style) { |
---|
538 | style.id = id; |
---|
539 | } |
---|
540 | |
---|
541 | return style; |
---|
542 | }, |
---|
543 | |
---|
544 | /** |
---|
545 | * Method: parseStyleMaps |
---|
546 | * Looks for <Style> nodes in the data and parses them |
---|
547 | * Also parses <StyleMap> nodes, but only uses the 'normal' key |
---|
548 | * |
---|
549 | * Parameters: |
---|
550 | * nodes - {Array} of {DOMElement} data to read/parse. |
---|
551 | * options - {Object} Hash of options |
---|
552 | * |
---|
553 | */ |
---|
554 | parseStyleMaps: function(nodes, options) { |
---|
555 | // Only the default or "normal" part of the StyleMap is processed now |
---|
556 | // To do the select or "highlight" bit, we'd need to change lots more |
---|
557 | |
---|
558 | for(var i=0, len=nodes.length; i<len; i++) { |
---|
559 | var node = nodes[i]; |
---|
560 | var pairs = this.getElementsByTagNameNS(node, "*", |
---|
561 | "Pair"); |
---|
562 | |
---|
563 | var id = node.getAttribute("id"); |
---|
564 | for (var j=0, jlen=pairs.length; j<jlen; j++) { |
---|
565 | var pair = pairs[j]; |
---|
566 | // Use the shortcut in the SLD format to quickly retrieve the |
---|
567 | // value of a node. Maybe it's good to have a method in |
---|
568 | // Format.XML to do this |
---|
569 | var key = this.parseProperty(pair, "*", "key"); |
---|
570 | var styleUrl = this.parseProperty(pair, "*", "styleUrl"); |
---|
571 | |
---|
572 | if (styleUrl && key == "normal") { |
---|
573 | this.styles[(options.styleBaseUrl || "") + "#" + id] = |
---|
574 | this.styles[(options.styleBaseUrl || "") + styleUrl]; |
---|
575 | } |
---|
576 | |
---|
577 | if (styleUrl && key == "highlight") { |
---|
578 | // TODO: implement the "select" part |
---|
579 | } |
---|
580 | |
---|
581 | } |
---|
582 | } |
---|
583 | |
---|
584 | }, |
---|
585 | |
---|
586 | |
---|
587 | /** |
---|
588 | * Method: parseFeatures |
---|
589 | * Loop through all Placemark nodes and parse them. |
---|
590 | * Will create a list of features |
---|
591 | * |
---|
592 | * Parameters: |
---|
593 | * nodes - {Array} of {DOMElement} data to read/parse. |
---|
594 | * options - {Object} Hash of options |
---|
595 | * |
---|
596 | */ |
---|
597 | parseFeatures: function(nodes, options) { |
---|
598 | var features = []; |
---|
599 | for(var i=0, len=nodes.length; i<len; i++) { |
---|
600 | var featureNode = nodes[i]; |
---|
601 | var feature = this.parseFeature.apply(this,[featureNode]) ; |
---|
602 | if(feature) { |
---|
603 | |
---|
604 | // Create reference to styleUrl |
---|
605 | if (this.extractStyles && feature.attributes && |
---|
606 | feature.attributes.styleUrl) { |
---|
607 | feature.style = this.getStyle(feature.attributes.styleUrl, options); |
---|
608 | } |
---|
609 | |
---|
610 | if (this.extractStyles) { |
---|
611 | // Make sure that <Style> nodes within a placemark are |
---|
612 | // processed as well |
---|
613 | var inlineStyleNode = this.getElementsByTagNameNS(featureNode, |
---|
614 | "*", |
---|
615 | "Style")[0]; |
---|
616 | if (inlineStyleNode) { |
---|
617 | var inlineStyle= this.parseStyle(inlineStyleNode); |
---|
618 | if (inlineStyle) { |
---|
619 | feature.style = OpenLayers.Util.extend( |
---|
620 | feature.style, inlineStyle |
---|
621 | ); |
---|
622 | } |
---|
623 | } |
---|
624 | } |
---|
625 | |
---|
626 | // check if gx:Track elements should be parsed |
---|
627 | if (this.extractTracks) { |
---|
628 | var tracks = this.getElementsByTagNameNS( |
---|
629 | featureNode, this.namespaces.gx, "Track" |
---|
630 | ); |
---|
631 | if (tracks && tracks.length > 0) { |
---|
632 | var track = tracks[0]; |
---|
633 | var container = { |
---|
634 | features: [], |
---|
635 | feature: feature |
---|
636 | }; |
---|
637 | this.readNode(track, container); |
---|
638 | if (container.features.length > 0) { |
---|
639 | features.push.apply(features, container.features); |
---|
640 | } |
---|
641 | } |
---|
642 | } else { |
---|
643 | // add feature to list of features |
---|
644 | features.push(feature); |
---|
645 | } |
---|
646 | } else { |
---|
647 | throw "Bad Placemark: " + i; |
---|
648 | } |
---|
649 | } |
---|
650 | |
---|
651 | // add new features to existing feature list |
---|
652 | this.features = this.features.concat(features); |
---|
653 | }, |
---|
654 | |
---|
655 | /** |
---|
656 | * Property: readers |
---|
657 | * Contains public functions, grouped by namespace prefix, that will |
---|
658 | * be applied when a namespaced node is found matching the function |
---|
659 | * name. The function will be applied in the scope of this parser |
---|
660 | * with two arguments: the node being read and a context object passed |
---|
661 | * from the parent. |
---|
662 | */ |
---|
663 | readers: { |
---|
664 | "kml": { |
---|
665 | "when": function(node, container) { |
---|
666 | container.whens.push(OpenLayers.Date.parse( |
---|
667 | this.getChildValue(node) |
---|
668 | )); |
---|
669 | }, |
---|
670 | "_trackPointAttribute": function(node, container) { |
---|
671 | var name = node.nodeName.split(":").pop(); |
---|
672 | container.attributes[name].push(this.getChildValue(node)); |
---|
673 | } |
---|
674 | }, |
---|
675 | "gx": { |
---|
676 | "Track": function(node, container) { |
---|
677 | var obj = { |
---|
678 | whens: [], |
---|
679 | points: [], |
---|
680 | angles: [] |
---|
681 | }; |
---|
682 | if (this.trackAttributes) { |
---|
683 | var name; |
---|
684 | obj.attributes = {}; |
---|
685 | for (var i=0, ii=this.trackAttributes.length; i<ii; ++i) { |
---|
686 | name = this.trackAttributes[i]; |
---|
687 | obj.attributes[name] = []; |
---|
688 | if (!(name in this.readers.kml)) { |
---|
689 | this.readers.kml[name] = this.readers.kml._trackPointAttribute; |
---|
690 | } |
---|
691 | } |
---|
692 | } |
---|
693 | this.readChildNodes(node, obj); |
---|
694 | if (obj.whens.length !== obj.points.length) { |
---|
695 | throw new Error("gx:Track with unequal number of when (" + obj.whens.length + ") and gx:coord (" + obj.points.length + ") elements."); |
---|
696 | } |
---|
697 | var hasAngles = obj.angles.length > 0; |
---|
698 | if (hasAngles && obj.whens.length !== obj.angles.length) { |
---|
699 | throw new Error("gx:Track with unequal number of when (" + obj.whens.length + ") and gx:angles (" + obj.angles.length + ") elements."); |
---|
700 | } |
---|
701 | var feature, point, angles; |
---|
702 | for (var i=0, ii=obj.whens.length; i<ii; ++i) { |
---|
703 | feature = container.feature.clone(); |
---|
704 | feature.fid = container.feature.fid || container.feature.id; |
---|
705 | point = obj.points[i]; |
---|
706 | feature.geometry = point; |
---|
707 | if ("z" in point) { |
---|
708 | feature.attributes.altitude = point.z; |
---|
709 | } |
---|
710 | if (this.internalProjection && this.externalProjection) { |
---|
711 | feature.geometry.transform( |
---|
712 | this.externalProjection, this.internalProjection |
---|
713 | ); |
---|
714 | } |
---|
715 | if (this.trackAttributes) { |
---|
716 | for (var j=0, jj=this.trackAttributes.length; j<jj; ++j) { |
---|
717 | feature.attributes[name] = obj.attributes[this.trackAttributes[j]][i]; |
---|
718 | } |
---|
719 | } |
---|
720 | feature.attributes.when = obj.whens[i]; |
---|
721 | feature.attributes.trackId = container.feature.id; |
---|
722 | if (hasAngles) { |
---|
723 | angles = obj.angles[i]; |
---|
724 | feature.attributes.heading = parseFloat(angles[0]); |
---|
725 | feature.attributes.tilt = parseFloat(angles[1]); |
---|
726 | feature.attributes.roll = parseFloat(angles[2]); |
---|
727 | } |
---|
728 | container.features.push(feature); |
---|
729 | } |
---|
730 | }, |
---|
731 | "coord": function(node, container) { |
---|
732 | var str = this.getChildValue(node); |
---|
733 | var coords = str.replace(this.regExes.trimSpace, "").split(/\s+/); |
---|
734 | var point = new OpenLayers.Geometry.Point(coords[0], coords[1]); |
---|
735 | if (coords.length > 2) { |
---|
736 | point.z = parseFloat(coords[2]); |
---|
737 | } |
---|
738 | container.points.push(point); |
---|
739 | }, |
---|
740 | "angles": function(node, container) { |
---|
741 | var str = this.getChildValue(node); |
---|
742 | var parts = str.replace(this.regExes.trimSpace, "").split(/\s+/); |
---|
743 | container.angles.push(parts); |
---|
744 | } |
---|
745 | } |
---|
746 | }, |
---|
747 | |
---|
748 | /** |
---|
749 | * Method: parseFeature |
---|
750 | * This function is the core of the KML parsing code in OpenLayers. |
---|
751 | * It creates the geometries that are then attached to the returned |
---|
752 | * feature, and calls parseAttributes() to get attribute data out. |
---|
753 | * |
---|
754 | * Parameters: |
---|
755 | * node - {DOMElement} |
---|
756 | * |
---|
757 | * Returns: |
---|
758 | * {<OpenLayers.Feature.Vector>} A vector feature. |
---|
759 | */ |
---|
760 | parseFeature: function(node) { |
---|
761 | // only accept one geometry per feature - look for highest "order" |
---|
762 | var order = ["MultiGeometry", "Polygon", "LineString", "Point"]; |
---|
763 | var type, nodeList, geometry, parser; |
---|
764 | for(var i=0, len=order.length; i<len; ++i) { |
---|
765 | type = order[i]; |
---|
766 | this.internalns = node.namespaceURI ? |
---|
767 | node.namespaceURI : this.kmlns; |
---|
768 | nodeList = this.getElementsByTagNameNS(node, |
---|
769 | this.internalns, type); |
---|
770 | if(nodeList.length > 0) { |
---|
771 | // only deal with first geometry of this type |
---|
772 | var parser = this.parseGeometry[type.toLowerCase()]; |
---|
773 | if(parser) { |
---|
774 | geometry = parser.apply(this, [nodeList[0]]); |
---|
775 | if (this.internalProjection && this.externalProjection) { |
---|
776 | geometry.transform(this.externalProjection, |
---|
777 | this.internalProjection); |
---|
778 | } |
---|
779 | } else { |
---|
780 | OpenLayers.Console.error(OpenLayers.i18n( |
---|
781 | "unsupportedGeometryType", {'geomType':type})); |
---|
782 | } |
---|
783 | // stop looking for different geometry types |
---|
784 | break; |
---|
785 | } |
---|
786 | } |
---|
787 | |
---|
788 | // construct feature (optionally with attributes) |
---|
789 | var attributes; |
---|
790 | if(this.extractAttributes) { |
---|
791 | attributes = this.parseAttributes(node); |
---|
792 | } |
---|
793 | var feature = new OpenLayers.Feature.Vector(geometry, attributes); |
---|
794 | |
---|
795 | var fid = node.getAttribute("id") || node.getAttribute("name"); |
---|
796 | if(fid != null) { |
---|
797 | feature.fid = fid; |
---|
798 | } |
---|
799 | |
---|
800 | return feature; |
---|
801 | }, |
---|
802 | |
---|
803 | /** |
---|
804 | * Method: getStyle |
---|
805 | * Retrieves a style from a style hash using styleUrl as the key |
---|
806 | * If the styleUrl doesn't exist yet, we try to fetch it |
---|
807 | * Internet |
---|
808 | * |
---|
809 | * Parameters: |
---|
810 | * styleUrl - {String} URL of style |
---|
811 | * options - {Object} Hash of options |
---|
812 | * |
---|
813 | * Returns: |
---|
814 | * {Object} - (reference to) Style hash |
---|
815 | */ |
---|
816 | getStyle: function(styleUrl, options) { |
---|
817 | |
---|
818 | var styleBaseUrl = OpenLayers.Util.removeTail(styleUrl); |
---|
819 | |
---|
820 | var newOptions = OpenLayers.Util.extend({}, options); |
---|
821 | newOptions.depth++; |
---|
822 | newOptions.styleBaseUrl = styleBaseUrl; |
---|
823 | |
---|
824 | // Fetch remote Style URLs (if not fetched before) |
---|
825 | if (!this.styles[styleUrl] |
---|
826 | && !OpenLayers.String.startsWith(styleUrl, "#") |
---|
827 | && newOptions.depth <= this.maxDepth |
---|
828 | && !this.fetched[styleBaseUrl] ) { |
---|
829 | |
---|
830 | var data = this.fetchLink(styleBaseUrl); |
---|
831 | if (data) { |
---|
832 | this.parseData(data, newOptions); |
---|
833 | } |
---|
834 | |
---|
835 | } |
---|
836 | |
---|
837 | // return requested style |
---|
838 | var style = OpenLayers.Util.extend({}, this.styles[styleUrl]); |
---|
839 | return style; |
---|
840 | }, |
---|
841 | |
---|
842 | /** |
---|
843 | * Property: parseGeometry |
---|
844 | * Properties of this object are the functions that parse geometries based |
---|
845 | * on their type. |
---|
846 | */ |
---|
847 | parseGeometry: { |
---|
848 | |
---|
849 | /** |
---|
850 | * Method: parseGeometry.point |
---|
851 | * Given a KML node representing a point geometry, create an OpenLayers |
---|
852 | * point geometry. |
---|
853 | * |
---|
854 | * Parameters: |
---|
855 | * node - {DOMElement} A KML Point node. |
---|
856 | * |
---|
857 | * Returns: |
---|
858 | * {<OpenLayers.Geometry.Point>} A point geometry. |
---|
859 | */ |
---|
860 | point: function(node) { |
---|
861 | var nodeList = this.getElementsByTagNameNS(node, this.internalns, |
---|
862 | "coordinates"); |
---|
863 | var coords = []; |
---|
864 | if(nodeList.length > 0) { |
---|
865 | var coordString = nodeList[0].firstChild.nodeValue; |
---|
866 | coordString = coordString.replace(this.regExes.removeSpace, ""); |
---|
867 | coords = coordString.split(","); |
---|
868 | } |
---|
869 | |
---|
870 | var point = null; |
---|
871 | if(coords.length > 1) { |
---|
872 | // preserve third dimension |
---|
873 | if(coords.length == 2) { |
---|
874 | coords[2] = null; |
---|
875 | } |
---|
876 | point = new OpenLayers.Geometry.Point(coords[0], coords[1], |
---|
877 | coords[2]); |
---|
878 | } else { |
---|
879 | throw "Bad coordinate string: " + coordString; |
---|
880 | } |
---|
881 | return point; |
---|
882 | }, |
---|
883 | |
---|
884 | /** |
---|
885 | * Method: parseGeometry.linestring |
---|
886 | * Given a KML node representing a linestring geometry, create an |
---|
887 | * OpenLayers linestring geometry. |
---|
888 | * |
---|
889 | * Parameters: |
---|
890 | * node - {DOMElement} A KML LineString node. |
---|
891 | * |
---|
892 | * Returns: |
---|
893 | * {<OpenLayers.Geometry.LineString>} A linestring geometry. |
---|
894 | */ |
---|
895 | linestring: function(node, ring) { |
---|
896 | var nodeList = this.getElementsByTagNameNS(node, this.internalns, |
---|
897 | "coordinates"); |
---|
898 | var line = null; |
---|
899 | if(nodeList.length > 0) { |
---|
900 | var coordString = this.getChildValue(nodeList[0]); |
---|
901 | |
---|
902 | coordString = coordString.replace(this.regExes.trimSpace, |
---|
903 | ""); |
---|
904 | coordString = coordString.replace(this.regExes.trimComma, |
---|
905 | ","); |
---|
906 | var pointList = coordString.split(this.regExes.splitSpace); |
---|
907 | var numPoints = pointList.length; |
---|
908 | var points = new Array(numPoints); |
---|
909 | var coords, numCoords; |
---|
910 | for(var i=0; i<numPoints; ++i) { |
---|
911 | coords = pointList[i].split(","); |
---|
912 | numCoords = coords.length; |
---|
913 | if(numCoords > 1) { |
---|
914 | if(coords.length == 2) { |
---|
915 | coords[2] = null; |
---|
916 | } |
---|
917 | points[i] = new OpenLayers.Geometry.Point(coords[0], |
---|
918 | coords[1], |
---|
919 | coords[2]); |
---|
920 | } else { |
---|
921 | throw "Bad LineString point coordinates: " + |
---|
922 | pointList[i]; |
---|
923 | } |
---|
924 | } |
---|
925 | if(numPoints) { |
---|
926 | if(ring) { |
---|
927 | line = new OpenLayers.Geometry.LinearRing(points); |
---|
928 | } else { |
---|
929 | line = new OpenLayers.Geometry.LineString(points); |
---|
930 | } |
---|
931 | } else { |
---|
932 | throw "Bad LineString coordinates: " + coordString; |
---|
933 | } |
---|
934 | } |
---|
935 | |
---|
936 | return line; |
---|
937 | }, |
---|
938 | |
---|
939 | /** |
---|
940 | * Method: parseGeometry.polygon |
---|
941 | * Given a KML node representing a polygon geometry, create an |
---|
942 | * OpenLayers polygon geometry. |
---|
943 | * |
---|
944 | * Parameters: |
---|
945 | * node - {DOMElement} A KML Polygon node. |
---|
946 | * |
---|
947 | * Returns: |
---|
948 | * {<OpenLayers.Geometry.Polygon>} A polygon geometry. |
---|
949 | */ |
---|
950 | polygon: function(node) { |
---|
951 | var nodeList = this.getElementsByTagNameNS(node, this.internalns, |
---|
952 | "LinearRing"); |
---|
953 | var numRings = nodeList.length; |
---|
954 | var components = new Array(numRings); |
---|
955 | if(numRings > 0) { |
---|
956 | // this assumes exterior ring first, inner rings after |
---|
957 | var ring; |
---|
958 | for(var i=0, len=nodeList.length; i<len; ++i) { |
---|
959 | ring = this.parseGeometry.linestring.apply(this, |
---|
960 | [nodeList[i], true]); |
---|
961 | if(ring) { |
---|
962 | components[i] = ring; |
---|
963 | } else { |
---|
964 | throw "Bad LinearRing geometry: " + i; |
---|
965 | } |
---|
966 | } |
---|
967 | } |
---|
968 | return new OpenLayers.Geometry.Polygon(components); |
---|
969 | }, |
---|
970 | |
---|
971 | /** |
---|
972 | * Method: parseGeometry.multigeometry |
---|
973 | * Given a KML node representing a multigeometry, create an |
---|
974 | * OpenLayers geometry collection. |
---|
975 | * |
---|
976 | * Parameters: |
---|
977 | * node - {DOMElement} A KML MultiGeometry node. |
---|
978 | * |
---|
979 | * Returns: |
---|
980 | * {<OpenLayers.Geometry.Collection>} A geometry collection. |
---|
981 | */ |
---|
982 | multigeometry: function(node) { |
---|
983 | var child, parser; |
---|
984 | var parts = []; |
---|
985 | var children = node.childNodes; |
---|
986 | for(var i=0, len=children.length; i<len; ++i ) { |
---|
987 | child = children[i]; |
---|
988 | if(child.nodeType == 1) { |
---|
989 | var type = (child.prefix) ? |
---|
990 | child.nodeName.split(":")[1] : |
---|
991 | child.nodeName; |
---|
992 | var parser = this.parseGeometry[type.toLowerCase()]; |
---|
993 | if(parser) { |
---|
994 | parts.push(parser.apply(this, [child])); |
---|
995 | } |
---|
996 | } |
---|
997 | } |
---|
998 | return new OpenLayers.Geometry.Collection(parts); |
---|
999 | } |
---|
1000 | |
---|
1001 | }, |
---|
1002 | |
---|
1003 | /** |
---|
1004 | * Method: parseAttributes |
---|
1005 | * |
---|
1006 | * Parameters: |
---|
1007 | * node - {DOMElement} |
---|
1008 | * |
---|
1009 | * Returns: |
---|
1010 | * {Object} An attributes object. |
---|
1011 | */ |
---|
1012 | parseAttributes: function(node) { |
---|
1013 | var attributes = {}; |
---|
1014 | |
---|
1015 | // Extended Data is parsed first. |
---|
1016 | var edNodes = node.getElementsByTagName("ExtendedData"); |
---|
1017 | if (edNodes.length) { |
---|
1018 | attributes = this.parseExtendedData(edNodes[0]); |
---|
1019 | } |
---|
1020 | |
---|
1021 | // assume attribute nodes are type 1 children with a type 3 or 4 child |
---|
1022 | var child, grandchildren, grandchild; |
---|
1023 | var children = node.childNodes; |
---|
1024 | |
---|
1025 | for(var i=0, len=children.length; i<len; ++i) { |
---|
1026 | child = children[i]; |
---|
1027 | if(child.nodeType == 1) { |
---|
1028 | grandchildren = child.childNodes; |
---|
1029 | if(grandchildren.length >= 1 && grandchildren.length <= 3) { |
---|
1030 | var grandchild; |
---|
1031 | switch (grandchildren.length) { |
---|
1032 | case 1: |
---|
1033 | grandchild = grandchildren[0]; |
---|
1034 | break; |
---|
1035 | case 2: |
---|
1036 | var c1 = grandchildren[0]; |
---|
1037 | var c2 = grandchildren[1]; |
---|
1038 | grandchild = (c1.nodeType == 3 || c1.nodeType == 4) ? |
---|
1039 | c1 : c2; |
---|
1040 | break; |
---|
1041 | case 3: |
---|
1042 | default: |
---|
1043 | grandchild = grandchildren[1]; |
---|
1044 | break; |
---|
1045 | } |
---|
1046 | if(grandchild.nodeType == 3 || grandchild.nodeType == 4) { |
---|
1047 | var name = (child.prefix) ? |
---|
1048 | child.nodeName.split(":")[1] : |
---|
1049 | child.nodeName; |
---|
1050 | var value = OpenLayers.Util.getXmlNodeValue(grandchild); |
---|
1051 | if (value) { |
---|
1052 | value = value.replace(this.regExes.trimSpace, ""); |
---|
1053 | attributes[name] = value; |
---|
1054 | } |
---|
1055 | } |
---|
1056 | } |
---|
1057 | } |
---|
1058 | } |
---|
1059 | return attributes; |
---|
1060 | }, |
---|
1061 | |
---|
1062 | /** |
---|
1063 | * Method: parseExtendedData |
---|
1064 | * Parse ExtendedData from KML. Limited support for schemas/datatypes. |
---|
1065 | * See http://code.google.com/apis/kml/documentation/kmlreference.html#extendeddata |
---|
1066 | * for more information on extendeddata. |
---|
1067 | */ |
---|
1068 | parseExtendedData: function(node) { |
---|
1069 | var attributes = {}; |
---|
1070 | var i, len, data, key; |
---|
1071 | var dataNodes = node.getElementsByTagName("Data"); |
---|
1072 | for (i = 0, len = dataNodes.length; i < len; i++) { |
---|
1073 | data = dataNodes[i]; |
---|
1074 | key = data.getAttribute("name"); |
---|
1075 | var ed = {}; |
---|
1076 | var valueNode = data.getElementsByTagName("value"); |
---|
1077 | if (valueNode.length) { |
---|
1078 | ed['value'] = this.getChildValue(valueNode[0]); |
---|
1079 | } |
---|
1080 | var nameNode = data.getElementsByTagName("displayName"); |
---|
1081 | if (nameNode.length) { |
---|
1082 | ed['displayName'] = this.getChildValue(nameNode[0]); |
---|
1083 | } |
---|
1084 | attributes[key] = ed; |
---|
1085 | } |
---|
1086 | var simpleDataNodes = node.getElementsByTagName("SimpleData"); |
---|
1087 | for (i = 0, len = simpleDataNodes.length; i < len; i++) { |
---|
1088 | var ed = {}; |
---|
1089 | data = simpleDataNodes[i]; |
---|
1090 | key = data.getAttribute("name"); |
---|
1091 | ed['value'] = this.getChildValue(data); |
---|
1092 | ed['displayName'] = key; |
---|
1093 | attributes[key] = ed; |
---|
1094 | } |
---|
1095 | |
---|
1096 | return attributes; |
---|
1097 | }, |
---|
1098 | |
---|
1099 | /** |
---|
1100 | * Method: parseProperty |
---|
1101 | * Convenience method to find a node and return its value |
---|
1102 | * |
---|
1103 | * Parameters: |
---|
1104 | * xmlNode - {<DOMElement>} |
---|
1105 | * namespace - {String} namespace of the node to find |
---|
1106 | * tagName - {String} name of the property to parse |
---|
1107 | * |
---|
1108 | * Returns: |
---|
1109 | * {String} The value for the requested property (defaults to null) |
---|
1110 | */ |
---|
1111 | parseProperty: function(xmlNode, namespace, tagName) { |
---|
1112 | var value; |
---|
1113 | var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName); |
---|
1114 | try { |
---|
1115 | value = OpenLayers.Util.getXmlNodeValue(nodeList[0]); |
---|
1116 | } catch(e) { |
---|
1117 | value = null; |
---|
1118 | } |
---|
1119 | |
---|
1120 | return value; |
---|
1121 | }, |
---|
1122 | |
---|
1123 | /** |
---|
1124 | * APIMethod: write |
---|
1125 | * Accept Feature Collection, and return a string. |
---|
1126 | * |
---|
1127 | * Parameters: |
---|
1128 | * features - {Array(<OpenLayers.Feature.Vector>} An array of features. |
---|
1129 | * |
---|
1130 | * Returns: |
---|
1131 | * {String} A KML string. |
---|
1132 | */ |
---|
1133 | write: function(features) { |
---|
1134 | if(!(features instanceof Array)) { |
---|
1135 | features = [features]; |
---|
1136 | } |
---|
1137 | var kml = this.createElementNS(this.kmlns, "kml"); |
---|
1138 | var folder = this.createFolderXML(); |
---|
1139 | for(var i=0, len=features.length; i<len; ++i) { |
---|
1140 | folder.appendChild(this.createPlacemarkXML(features[i])); |
---|
1141 | } |
---|
1142 | kml.appendChild(folder); |
---|
1143 | return OpenLayers.Format.XML.prototype.write.apply(this, [kml]); |
---|
1144 | }, |
---|
1145 | |
---|
1146 | /** |
---|
1147 | * Method: createFolderXML |
---|
1148 | * Creates and returns a KML folder node |
---|
1149 | * |
---|
1150 | * Returns: |
---|
1151 | * {DOMElement} |
---|
1152 | */ |
---|
1153 | createFolderXML: function() { |
---|
1154 | // Folder |
---|
1155 | var folder = this.createElementNS(this.kmlns, "Folder"); |
---|
1156 | |
---|
1157 | // Folder name |
---|
1158 | if (this.foldersName) { |
---|
1159 | var folderName = this.createElementNS(this.kmlns, "name"); |
---|
1160 | var folderNameText = this.createTextNode(this.foldersName); |
---|
1161 | folderName.appendChild(folderNameText); |
---|
1162 | folder.appendChild(folderName); |
---|
1163 | } |
---|
1164 | |
---|
1165 | // Folder description |
---|
1166 | if (this.foldersDesc) { |
---|
1167 | var folderDesc = this.createElementNS(this.kmlns, "description"); |
---|
1168 | var folderDescText = this.createTextNode(this.foldersDesc); |
---|
1169 | folderDesc.appendChild(folderDescText); |
---|
1170 | folder.appendChild(folderDesc); |
---|
1171 | } |
---|
1172 | |
---|
1173 | return folder; |
---|
1174 | }, |
---|
1175 | |
---|
1176 | /** |
---|
1177 | * Method: createPlacemarkXML |
---|
1178 | * Creates and returns a KML placemark node representing the given feature. |
---|
1179 | * |
---|
1180 | * Parameters: |
---|
1181 | * feature - {<OpenLayers.Feature.Vector>} |
---|
1182 | * |
---|
1183 | * Returns: |
---|
1184 | * {DOMElement} |
---|
1185 | */ |
---|
1186 | createPlacemarkXML: function(feature) { |
---|
1187 | // Placemark name |
---|
1188 | var placemarkName = this.createElementNS(this.kmlns, "name"); |
---|
1189 | var name = feature.style && feature.style.label ? feature.style.label : |
---|
1190 | feature.attributes.name || feature.id; |
---|
1191 | placemarkName.appendChild(this.createTextNode(name)); |
---|
1192 | |
---|
1193 | // Placemark description |
---|
1194 | var placemarkDesc = this.createElementNS(this.kmlns, "description"); |
---|
1195 | var desc = feature.attributes.description || this.placemarksDesc; |
---|
1196 | placemarkDesc.appendChild(this.createTextNode(desc)); |
---|
1197 | |
---|
1198 | // Placemark |
---|
1199 | var placemarkNode = this.createElementNS(this.kmlns, "Placemark"); |
---|
1200 | if(feature.fid != null) { |
---|
1201 | placemarkNode.setAttribute("id", feature.fid); |
---|
1202 | } |
---|
1203 | placemarkNode.appendChild(placemarkName); |
---|
1204 | placemarkNode.appendChild(placemarkDesc); |
---|
1205 | |
---|
1206 | // Geometry node (Point, LineString, etc. nodes) |
---|
1207 | var geometryNode = this.buildGeometryNode(feature.geometry); |
---|
1208 | placemarkNode.appendChild(geometryNode); |
---|
1209 | |
---|
1210 | // TBD - deal with remaining (non name/description) attributes. |
---|
1211 | return placemarkNode; |
---|
1212 | }, |
---|
1213 | |
---|
1214 | /** |
---|
1215 | * Method: buildGeometryNode |
---|
1216 | * Builds and returns a KML geometry node with the given geometry. |
---|
1217 | * |
---|
1218 | * Parameters: |
---|
1219 | * geometry - {<OpenLayers.Geometry>} |
---|
1220 | * |
---|
1221 | * Returns: |
---|
1222 | * {DOMElement} |
---|
1223 | */ |
---|
1224 | buildGeometryNode: function(geometry) { |
---|
1225 | if (this.internalProjection && this.externalProjection) { |
---|
1226 | geometry = geometry.clone(); |
---|
1227 | geometry.transform(this.internalProjection, |
---|
1228 | this.externalProjection); |
---|
1229 | } |
---|
1230 | var className = geometry.CLASS_NAME; |
---|
1231 | var type = className.substring(className.lastIndexOf(".") + 1); |
---|
1232 | var builder = this.buildGeometry[type.toLowerCase()]; |
---|
1233 | var node = null; |
---|
1234 | if(builder) { |
---|
1235 | node = builder.apply(this, [geometry]); |
---|
1236 | } |
---|
1237 | return node; |
---|
1238 | }, |
---|
1239 | |
---|
1240 | /** |
---|
1241 | * Property: buildGeometry |
---|
1242 | * Object containing methods to do the actual geometry node building |
---|
1243 | * based on geometry type. |
---|
1244 | */ |
---|
1245 | buildGeometry: { |
---|
1246 | // TBD: Anybody care about namespace aliases here (these nodes have |
---|
1247 | // no prefixes)? |
---|
1248 | |
---|
1249 | /** |
---|
1250 | * Method: buildGeometry.point |
---|
1251 | * Given an OpenLayers point geometry, create a KML point. |
---|
1252 | * |
---|
1253 | * Parameters: |
---|
1254 | * geometry - {<OpenLayers.Geometry.Point>} A point geometry. |
---|
1255 | * |
---|
1256 | * Returns: |
---|
1257 | * {DOMElement} A KML point node. |
---|
1258 | */ |
---|
1259 | point: function(geometry) { |
---|
1260 | var kml = this.createElementNS(this.kmlns, "Point"); |
---|
1261 | kml.appendChild(this.buildCoordinatesNode(geometry)); |
---|
1262 | return kml; |
---|
1263 | }, |
---|
1264 | |
---|
1265 | /** |
---|
1266 | * Method: buildGeometry.multipoint |
---|
1267 | * Given an OpenLayers multipoint geometry, create a KML |
---|
1268 | * GeometryCollection. |
---|
1269 | * |
---|
1270 | * Parameters: |
---|
1271 | * geometry - {<OpenLayers.Geometry.Point>} A multipoint geometry. |
---|
1272 | * |
---|
1273 | * Returns: |
---|
1274 | * {DOMElement} A KML GeometryCollection node. |
---|
1275 | */ |
---|
1276 | multipoint: function(geometry) { |
---|
1277 | return this.buildGeometry.collection.apply(this, [geometry]); |
---|
1278 | }, |
---|
1279 | |
---|
1280 | /** |
---|
1281 | * Method: buildGeometry.linestring |
---|
1282 | * Given an OpenLayers linestring geometry, create a KML linestring. |
---|
1283 | * |
---|
1284 | * Parameters: |
---|
1285 | * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry. |
---|
1286 | * |
---|
1287 | * Returns: |
---|
1288 | * {DOMElement} A KML linestring node. |
---|
1289 | */ |
---|
1290 | linestring: function(geometry) { |
---|
1291 | var kml = this.createElementNS(this.kmlns, "LineString"); |
---|
1292 | kml.appendChild(this.buildCoordinatesNode(geometry)); |
---|
1293 | return kml; |
---|
1294 | }, |
---|
1295 | |
---|
1296 | /** |
---|
1297 | * Method: buildGeometry.multilinestring |
---|
1298 | * Given an OpenLayers multilinestring geometry, create a KML |
---|
1299 | * GeometryCollection. |
---|
1300 | * |
---|
1301 | * Parameters: |
---|
1302 | * geometry - {<OpenLayers.Geometry.Point>} A multilinestring geometry. |
---|
1303 | * |
---|
1304 | * Returns: |
---|
1305 | * {DOMElement} A KML GeometryCollection node. |
---|
1306 | */ |
---|
1307 | multilinestring: function(geometry) { |
---|
1308 | return this.buildGeometry.collection.apply(this, [geometry]); |
---|
1309 | }, |
---|
1310 | |
---|
1311 | /** |
---|
1312 | * Method: buildGeometry.linearring |
---|
1313 | * Given an OpenLayers linearring geometry, create a KML linearring. |
---|
1314 | * |
---|
1315 | * Parameters: |
---|
1316 | * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry. |
---|
1317 | * |
---|
1318 | * Returns: |
---|
1319 | * {DOMElement} A KML linearring node. |
---|
1320 | */ |
---|
1321 | linearring: function(geometry) { |
---|
1322 | var kml = this.createElementNS(this.kmlns, "LinearRing"); |
---|
1323 | kml.appendChild(this.buildCoordinatesNode(geometry)); |
---|
1324 | return kml; |
---|
1325 | }, |
---|
1326 | |
---|
1327 | /** |
---|
1328 | * Method: buildGeometry.polygon |
---|
1329 | * Given an OpenLayers polygon geometry, create a KML polygon. |
---|
1330 | * |
---|
1331 | * Parameters: |
---|
1332 | * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry. |
---|
1333 | * |
---|
1334 | * Returns: |
---|
1335 | * {DOMElement} A KML polygon node. |
---|
1336 | */ |
---|
1337 | polygon: function(geometry) { |
---|
1338 | var kml = this.createElementNS(this.kmlns, "Polygon"); |
---|
1339 | var rings = geometry.components; |
---|
1340 | var ringMember, ringGeom, type; |
---|
1341 | for(var i=0, len=rings.length; i<len; ++i) { |
---|
1342 | type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs"; |
---|
1343 | ringMember = this.createElementNS(this.kmlns, type); |
---|
1344 | ringGeom = this.buildGeometry.linearring.apply(this, |
---|
1345 | [rings[i]]); |
---|
1346 | ringMember.appendChild(ringGeom); |
---|
1347 | kml.appendChild(ringMember); |
---|
1348 | } |
---|
1349 | return kml; |
---|
1350 | }, |
---|
1351 | |
---|
1352 | /** |
---|
1353 | * Method: buildGeometry.multipolygon |
---|
1354 | * Given an OpenLayers multipolygon geometry, create a KML |
---|
1355 | * GeometryCollection. |
---|
1356 | * |
---|
1357 | * Parameters: |
---|
1358 | * geometry - {<OpenLayers.Geometry.Point>} A multipolygon geometry. |
---|
1359 | * |
---|
1360 | * Returns: |
---|
1361 | * {DOMElement} A KML GeometryCollection node. |
---|
1362 | */ |
---|
1363 | multipolygon: function(geometry) { |
---|
1364 | return this.buildGeometry.collection.apply(this, [geometry]); |
---|
1365 | }, |
---|
1366 | |
---|
1367 | /** |
---|
1368 | * Method: buildGeometry.collection |
---|
1369 | * Given an OpenLayers geometry collection, create a KML MultiGeometry. |
---|
1370 | * |
---|
1371 | * Parameters: |
---|
1372 | * geometry - {<OpenLayers.Geometry.Collection>} A geometry collection. |
---|
1373 | * |
---|
1374 | * Returns: |
---|
1375 | * {DOMElement} A KML MultiGeometry node. |
---|
1376 | */ |
---|
1377 | collection: function(geometry) { |
---|
1378 | var kml = this.createElementNS(this.kmlns, "MultiGeometry"); |
---|
1379 | var child; |
---|
1380 | for(var i=0, len=geometry.components.length; i<len; ++i) { |
---|
1381 | child = this.buildGeometryNode.apply(this, |
---|
1382 | [geometry.components[i]]); |
---|
1383 | if(child) { |
---|
1384 | kml.appendChild(child); |
---|
1385 | } |
---|
1386 | } |
---|
1387 | return kml; |
---|
1388 | } |
---|
1389 | }, |
---|
1390 | |
---|
1391 | /** |
---|
1392 | * Method: buildCoordinatesNode |
---|
1393 | * Builds and returns the KML coordinates node with the given geometry |
---|
1394 | * <coordinates>...</coordinates> |
---|
1395 | * |
---|
1396 | * Parameters: |
---|
1397 | * geometry - {<OpenLayers.Geometry>} |
---|
1398 | * |
---|
1399 | * Return: |
---|
1400 | * {DOMElement} |
---|
1401 | */ |
---|
1402 | buildCoordinatesNode: function(geometry) { |
---|
1403 | var coordinatesNode = this.createElementNS(this.kmlns, "coordinates"); |
---|
1404 | |
---|
1405 | var path; |
---|
1406 | var points = geometry.components; |
---|
1407 | if(points) { |
---|
1408 | // LineString or LinearRing |
---|
1409 | var point; |
---|
1410 | var numPoints = points.length; |
---|
1411 | var parts = new Array(numPoints); |
---|
1412 | for(var i=0; i<numPoints; ++i) { |
---|
1413 | point = points[i]; |
---|
1414 | parts[i] = point.x + "," + point.y; |
---|
1415 | } |
---|
1416 | path = parts.join(" "); |
---|
1417 | } else { |
---|
1418 | // Point |
---|
1419 | path = geometry.x + "," + geometry.y; |
---|
1420 | } |
---|
1421 | |
---|
1422 | var txtNode = this.createTextNode(path); |
---|
1423 | coordinatesNode.appendChild(txtNode); |
---|
1424 | |
---|
1425 | return coordinatesNode; |
---|
1426 | }, |
---|
1427 | |
---|
1428 | CLASS_NAME: "OpenLayers.Format.KML" |
---|
1429 | }); |
---|