| 94 | | var featureNodes = this.getElementsByTagNameNS(data, |
|---|
| 95 | | '*', |
|---|
| 96 | | "Placemark"); |
|---|
| 97 | | var numFeatures = featureNodes.length; |
|---|
| 98 | | var features = new Array(numFeatures); |
|---|
| 99 | | for(var i=0; i<numFeatures; i++) { |
|---|
| 100 | | var feature = this.parseFeature(featureNodes[i]); |
|---|
| | 165 | |
|---|
| | 166 | // Loop throught the following node types in this order and |
|---|
| | 167 | // process the nodes found |
|---|
| | 168 | var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"]; |
|---|
| | 169 | for(var i=0; i<types.length; ++i) { |
|---|
| | 170 | var type = types[i]; |
|---|
| | 171 | |
|---|
| | 172 | var nodes = this.getElementsByTagNameNS(data, "*", type); |
|---|
| | 173 | |
|---|
| | 174 | // skip to next type if no nodes are found |
|---|
| | 175 | if(nodes.length == 0) { |
|---|
| | 176 | continue; |
|---|
| | 177 | } |
|---|
| | 178 | |
|---|
| | 179 | switch (type.toLowerCase()) { |
|---|
| | 180 | |
|---|
| | 181 | // Fetch external links |
|---|
| | 182 | case "link": |
|---|
| | 183 | case "networklink": |
|---|
| | 184 | this.parseLinks(nodes, options); |
|---|
| | 185 | break; |
|---|
| | 186 | |
|---|
| | 187 | // parse style information |
|---|
| | 188 | case "style": |
|---|
| | 189 | if (this.extractStyles) { |
|---|
| | 190 | this.parseStyles(nodes, options); |
|---|
| | 191 | } |
|---|
| | 192 | break; |
|---|
| | 193 | case "stylemap": |
|---|
| | 194 | if (this.extractStyles) { |
|---|
| | 195 | this.parseStyleMaps(nodes, options); |
|---|
| | 196 | } |
|---|
| | 197 | break; |
|---|
| | 198 | |
|---|
| | 199 | // parse features |
|---|
| | 200 | case "placemark": |
|---|
| | 201 | this.parseFeatures(nodes, options); |
|---|
| | 202 | break; |
|---|
| | 203 | } |
|---|
| | 204 | } |
|---|
| | 205 | |
|---|
| | 206 | return this.features; |
|---|
| | 207 | }, |
|---|
| | 208 | |
|---|
| | 209 | /** |
|---|
| | 210 | * Method: parseLinks |
|---|
| | 211 | * Finds URLs of linked KML documents and fetches them |
|---|
| | 212 | * |
|---|
| | 213 | * Parameters: |
|---|
| | 214 | * nodes - {Array} of {DOMElement} data to read/parse. |
|---|
| | 215 | * options - {Object} Hash of options |
|---|
| | 216 | * |
|---|
| | 217 | */ |
|---|
| | 218 | parseLinks: function(nodes, options) { |
|---|
| | 219 | |
|---|
| | 220 | // Fetch external links <NetworkLink> and <Link> |
|---|
| | 221 | // Don't do anything if we have reached our maximum depth for recursion |
|---|
| | 222 | if (options.depth >= this.maxDepth) { |
|---|
| | 223 | return false; |
|---|
| | 224 | } |
|---|
| | 225 | |
|---|
| | 226 | // increase depth |
|---|
| | 227 | var newOptions = OpenLayers.Util.extend({}, options); |
|---|
| | 228 | newOptions.depth++; |
|---|
| | 229 | |
|---|
| | 230 | for(var i=0; i < nodes.length; i++) { |
|---|
| | 231 | var href = this.parseProperty(nodes[i], "*", "href"); |
|---|
| | 232 | if(href && !this.fetched[href]) { |
|---|
| | 233 | this.fetched[href] = true; // prevent reloading the same urls |
|---|
| | 234 | var data = this.fetchLink(href); |
|---|
| | 235 | if (data) { |
|---|
| | 236 | this.parseData(data, newOptions); |
|---|
| | 237 | } |
|---|
| | 238 | } |
|---|
| | 239 | } |
|---|
| | 240 | |
|---|
| | 241 | }, |
|---|
| | 242 | |
|---|
| | 243 | /** |
|---|
| | 244 | * Method: fetchLink |
|---|
| | 245 | * Fetches a URL and returns the result |
|---|
| | 246 | * |
|---|
| | 247 | * Parameters: |
|---|
| | 248 | * href - {String} url to be fetched |
|---|
| | 249 | * |
|---|
| | 250 | */ |
|---|
| | 251 | fetchLink: function(href) { |
|---|
| | 252 | |
|---|
| | 253 | if (OpenLayers.ProxyHost |
|---|
| | 254 | && OpenLayers.String.startsWith(href, "http")) { |
|---|
| | 255 | href = OpenLayers.ProxyHost + escape(href); |
|---|
| | 256 | } |
|---|
| | 257 | |
|---|
| | 258 | var request = new OpenLayers.Ajax.Request(href, |
|---|
| | 259 | {method: 'get', asynchronous: false }); |
|---|
| | 260 | |
|---|
| | 261 | if (request && request.transport) { |
|---|
| | 262 | return request.transport.responseText; |
|---|
| | 263 | } |
|---|
| | 264 | |
|---|
| | 265 | }, |
|---|
| | 266 | |
|---|
| | 267 | /** |
|---|
| | 268 | * Method: parseStyles |
|---|
| | 269 | * Looks for <Style> nodes in the data and parses them |
|---|
| | 270 | * Also parses <StyleMap> nodes, but only uses the 'normal' key |
|---|
| | 271 | * |
|---|
| | 272 | * Parameters: |
|---|
| | 273 | * nodes - {Array} of {DOMElement} data to read/parse. |
|---|
| | 274 | * options - {Object} Hash of options |
|---|
| | 275 | * |
|---|
| | 276 | */ |
|---|
| | 277 | parseStyles: function(nodes, options) { |
|---|
| | 278 | for(var i=0; i < nodes.length; i++) { |
|---|
| | 279 | var style = this.parseStyle(nodes[i]); |
|---|
| | 280 | if(style) { |
|---|
| | 281 | styleName = (options.styleBaseUrl || "") + "#" + style.id; |
|---|
| | 282 | |
|---|
| | 283 | this.styles[styleName] = style; |
|---|
| | 284 | } |
|---|
| | 285 | } |
|---|
| | 286 | }, |
|---|
| | 287 | |
|---|
| | 288 | /** |
|---|
| | 289 | * Method: parseStyle |
|---|
| | 290 | * Parses the children of a <Style> node and builds the style hash |
|---|
| | 291 | * accordingly |
|---|
| | 292 | * |
|---|
| | 293 | * Parameters: |
|---|
| | 294 | * node - {DOMElement} <Style> node |
|---|
| | 295 | * |
|---|
| | 296 | */ |
|---|
| | 297 | parseStyle: function(node) { |
|---|
| | 298 | var style = {}; |
|---|
| | 299 | |
|---|
| | 300 | var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle"]; |
|---|
| | 301 | var type, nodeList, geometry, parser; |
|---|
| | 302 | for(var i=0; i<types.length; ++i) { |
|---|
| | 303 | type = types[i]; |
|---|
| | 304 | styleTypeNode = this.getElementsByTagNameNS(node, |
|---|
| | 305 | "*", type)[0]; |
|---|
| | 306 | if(!styleTypeNode) { |
|---|
| | 307 | continue; |
|---|
| | 308 | } |
|---|
| | 309 | |
|---|
| | 310 | // only deal with first geometry of this type |
|---|
| | 311 | switch (type.toLowerCase()) { |
|---|
| | 312 | case "linestyle": |
|---|
| | 313 | var color = this.parseProperty(styleTypeNode, "*", "color"); |
|---|
| | 314 | if (color) { |
|---|
| | 315 | var matches = (color.toString()).match( |
|---|
| | 316 | this.regExes.kmlColor); |
|---|
| | 317 | |
|---|
| | 318 | // transparency |
|---|
| | 319 | var alpha = matches[1]; |
|---|
| | 320 | style["strokeOpacity"] = parseInt(alpha, 16) / 255; |
|---|
| | 321 | |
|---|
| | 322 | // rgb colors (google uses bgr) |
|---|
| | 323 | var b = matches[2]; |
|---|
| | 324 | var g = matches[3]; |
|---|
| | 325 | var r = matches[4]; |
|---|
| | 326 | style["strokeColor"] = "#" + r + g + b; |
|---|
| | 327 | } |
|---|
| | 328 | |
|---|
| | 329 | var width = this.parseProperty(styleTypeNode, "*", "width"); |
|---|
| | 330 | if (width) { |
|---|
| | 331 | style["strokeWidth"] = width; |
|---|
| | 332 | } |
|---|
| | 333 | |
|---|
| | 334 | case "polystyle": |
|---|
| | 335 | var color = this.parseProperty(styleTypeNode, "*", "color"); |
|---|
| | 336 | if (color) { |
|---|
| | 337 | var matches = (color.toString()).match( |
|---|
| | 338 | this.regExes.kmlColor); |
|---|
| | 339 | |
|---|
| | 340 | // transparency |
|---|
| | 341 | var alpha = matches[1]; |
|---|
| | 342 | style["fillOpacity"] = parseInt(alpha, 16) / 255; |
|---|
| | 343 | |
|---|
| | 344 | // rgb colors (google uses bgr) |
|---|
| | 345 | var b = matches[2]; |
|---|
| | 346 | var g = matches[3]; |
|---|
| | 347 | var r = matches[4]; |
|---|
| | 348 | style["fillColor"] = "#" + r + g + b; |
|---|
| | 349 | } |
|---|
| | 350 | |
|---|
| | 351 | break; |
|---|
| | 352 | case "iconstyle": |
|---|
| | 353 | var iconNode = this.getElementsByTagNameNS(styleTypeNode, |
|---|
| | 354 | "*", |
|---|
| | 355 | "Icon")[0]; |
|---|
| | 356 | |
|---|
| | 357 | // set default width and height of icon |
|---|
| | 358 | style["graphicWidth"] = 32; |
|---|
| | 359 | style["graphicHeight"] = 32; |
|---|
| | 360 | |
|---|
| | 361 | if (iconNode) { |
|---|
| | 362 | var href = this.parseProperty(iconNode, "*", "href"); |
|---|
| | 363 | if (href) { |
|---|
| | 364 | |
|---|
| | 365 | // support for internal icons |
|---|
| | 366 | // (/root://icons/palette-x.png) |
|---|
| | 367 | // x and y tell the position on the palette: |
|---|
| | 368 | // - in pixels |
|---|
| | 369 | // - starting from the left bottom |
|---|
| | 370 | // We translate that to a position in the list |
|---|
| | 371 | // and request the appropriate icon from the |
|---|
| | 372 | // google maps website |
|---|
| | 373 | var matches = href.match(this.regExes.kmlIconPalette); |
|---|
| | 374 | if (matches) { |
|---|
| | 375 | var palette = matches[1]; |
|---|
| | 376 | var file_extension = matches[2]; |
|---|
| | 377 | |
|---|
| | 378 | var x = this.parseProperty(iconNode, "*", "x"); |
|---|
| | 379 | var y = this.parseProperty(iconNode, "*", "y"); |
|---|
| | 380 | |
|---|
| | 381 | var posX = x ? x/32 : 0; |
|---|
| | 382 | var posY = y ? (7 - y/32) : 7; |
|---|
| | 383 | |
|---|
| | 384 | var pos = posY * 8 + posX; |
|---|
| | 385 | href = "http://maps.google.com/mapfiles/kml/pal" |
|---|
| | 386 | + palette + "/icon" + pos + file_extension; |
|---|
| | 387 | } |
|---|
| | 388 | |
|---|
| | 389 | |
|---|
| | 390 | var w = this.parseProperty(iconNode, "*", "w"); |
|---|
| | 391 | if (w) { |
|---|
| | 392 | style["graphicWidth"] = parseInt(w); |
|---|
| | 393 | } |
|---|
| | 394 | |
|---|
| | 395 | var h = this.parseProperty(iconNode, "*", "h"); |
|---|
| | 396 | if (h) { |
|---|
| | 397 | style["graphicHeight"] = parseInt(h); |
|---|
| | 398 | } |
|---|
| | 399 | |
|---|
| | 400 | style["graphicOpacity"] = 1; // fully opaque |
|---|
| | 401 | style["externalGraphic"] = href; |
|---|
| | 402 | } |
|---|
| | 403 | |
|---|
| | 404 | } |
|---|
| | 405 | |
|---|
| | 406 | |
|---|
| | 407 | // hotSpots define the offset for an Icon |
|---|
| | 408 | var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode, |
|---|
| | 409 | "*", |
|---|
| | 410 | "hotSpot")[0]; |
|---|
| | 411 | if (hotSpotNode) { |
|---|
| | 412 | var x = hotSpotNode.getAttribute("x"); |
|---|
| | 413 | var y = hotSpotNode.getAttribute("y"); |
|---|
| | 414 | |
|---|
| | 415 | var xUnits = hotSpotNode.getAttribute("xunits"); |
|---|
| | 416 | if (xUnits == "pixels") { |
|---|
| | 417 | style["graphicXOffset"] = parseInt(x); |
|---|
| | 418 | } |
|---|
| | 419 | else if (xUnits == "insetPixels") { |
|---|
| | 420 | style["graphicXOffset"] = style["graphicWidth"] |
|---|
| | 421 | - parseInt(x); |
|---|
| | 422 | } |
|---|
| | 423 | else if (xUnits == "fraction") { |
|---|
| | 424 | style["graphicXOffset"] = style["graphicWidth"] |
|---|
| | 425 | * parseFloat(x); |
|---|
| | 426 | } |
|---|
| | 427 | |
|---|
| | 428 | var yUnits = hotSpotNode.getAttribute("yunits"); |
|---|
| | 429 | if (yUnits == "pixels") { |
|---|
| | 430 | style["graphicYOffset"] = parseInt(y); |
|---|
| | 431 | } |
|---|
| | 432 | else if (yUnits == "insetPixels") { |
|---|
| | 433 | style["graphicYOffset"] = style["graphicHeight"] |
|---|
| | 434 | - parseInt(y); |
|---|
| | 435 | } |
|---|
| | 436 | else if (yUnits == "fraction") { |
|---|
| | 437 | style["graphicYOffset"] = style["graphicHeight"] |
|---|
| | 438 | * parseFloat(y); |
|---|
| | 439 | } |
|---|
| | 440 | } |
|---|
| | 441 | break; |
|---|
| | 442 | case "balloonstyle": |
|---|
| | 443 | var balloonStyle = OpenLayers.Util.getXmlNodeValue( |
|---|
| | 444 | styleTypeNode); |
|---|
| | 445 | if (balloonStyle) { |
|---|
| | 446 | style["balloonStyle"] = balloonStyle.replace( |
|---|
| | 447 | this.regExes.straightBracket, "${$1}"); |
|---|
| | 448 | } |
|---|
| | 449 | break; |
|---|
| | 450 | default: |
|---|
| | 451 | } |
|---|
| | 452 | } |
|---|
| | 453 | |
|---|
| | 454 | // Some polygons have no line color, so we use the fillColor for that |
|---|
| | 455 | if (!style["strokeColor"] && style["fillColor"]) { |
|---|
| | 456 | style["strokeColor"] = style["fillColor"]; |
|---|
| | 457 | } |
|---|
| | 458 | |
|---|
| | 459 | var id = node.getAttribute("id"); |
|---|
| | 460 | if (id && style) { |
|---|
| | 461 | style.id = id; |
|---|
| | 462 | } |
|---|
| | 463 | |
|---|
| | 464 | return style; |
|---|
| | 465 | }, |
|---|
| | 466 | |
|---|
| | 467 | /** |
|---|
| | 468 | * Method: parseStyleMaps |
|---|
| | 469 | * Looks for <Style> nodes in the data and parses them |
|---|
| | 470 | * Also parses <StyleMap> nodes, but only uses the 'normal' key |
|---|
| | 471 | * |
|---|
| | 472 | * Parameters: |
|---|
| | 473 | * nodes - {Array} of {DOMElement} data to read/parse. |
|---|
| | 474 | * options - {Object} Hash of options |
|---|
| | 475 | * |
|---|
| | 476 | */ |
|---|
| | 477 | parseStyleMaps: function(nodes, options) { |
|---|
| | 478 | // Only the default or "normal" part of the StyleMap is processed now |
|---|
| | 479 | // To do the select or "highlight" bit, we'd need to change lots more |
|---|
| | 480 | |
|---|
| | 481 | for(var i=0; i < nodes.length; i++) { |
|---|
| | 482 | var node = nodes[i]; |
|---|
| | 483 | var pairs = this.getElementsByTagNameNS(node, "*", |
|---|
| | 484 | "Pair"); |
|---|
| | 485 | |
|---|
| | 486 | var id = node.getAttribute("id"); |
|---|
| | 487 | for (var j=0; j<pairs.length; j++) { |
|---|
| | 488 | var pair = pairs[j]; |
|---|
| | 489 | // Use the shortcut in the SLD format to quickly retrieve the |
|---|
| | 490 | // value of a node. Maybe it's good to have a method in |
|---|
| | 491 | // Format.XML to do this |
|---|
| | 492 | var key = this.parseProperty(pair, "*", "key"); |
|---|
| | 493 | var styleUrl = this.parseProperty(pair, "*", "styleUrl"); |
|---|
| | 494 | |
|---|
| | 495 | if (styleUrl && key == "normal") { |
|---|
| | 496 | this.styles[(options.styleBaseUrl || "") + "#" + id] = |
|---|
| | 497 | this.styles[(options.styleBaseUrl || "") + styleUrl]; |
|---|
| | 498 | } |
|---|
| | 499 | |
|---|
| | 500 | if (styleUrl && key == "highlight") { |
|---|
| | 501 | // TODO: implement the "select" part |
|---|
| | 502 | } |
|---|
| | 503 | |
|---|
| | 504 | } |
|---|
| | 505 | } |
|---|
| | 506 | |
|---|
| | 507 | }, |
|---|
| | 508 | |
|---|
| | 509 | |
|---|
| | 510 | /** |
|---|
| | 511 | * Method: parseFeatures |
|---|
| | 512 | * Loop through all Placemark nodes and parse them. |
|---|
| | 513 | * Will create a list of features |
|---|
| | 514 | * |
|---|
| | 515 | * Parameters: |
|---|
| | 516 | * nodes - {Array} of {DOMElement} data to read/parse. |
|---|
| | 517 | * options - {Object} Hash of options |
|---|
| | 518 | * |
|---|
| | 519 | */ |
|---|
| | 520 | parseFeatures: function(nodes, options) { |
|---|
| | 521 | var features = new Array(nodes.length); |
|---|
| | 522 | for(var i=0; i < nodes.length; i++) { |
|---|
| | 523 | var featureNode = nodes[i]; |
|---|
| | 524 | var feature = this.parseFeature.apply(this,[featureNode]) ; |
|---|