| | 132 | |
|---|
| | 133 | // Set initial recursion level |
|---|
| | 134 | if (!depth) { |
|---|
| | 135 | depth = 0; |
|---|
| | 136 | } |
|---|
| | 137 | |
|---|
| | 138 | // Fetch external links <NetworkLink> and <Link> |
|---|
| | 139 | // Don't do anything if we have reached our maximum depth for recursion |
|---|
| | 140 | if (depth < this.maxDepth) { |
|---|
| | 141 | this.fetchLinks(data, depth); |
|---|
| | 142 | } |
|---|
| | 143 | |
|---|
| | 144 | // Parse internal Style information |
|---|
| | 145 | this.parseStyles(data); |
|---|
| | 146 | |
|---|
| | 147 | // Parse placemarks (features) |
|---|
| | 148 | this.parseFeatures(data); |
|---|
| | 149 | |
|---|
| | 150 | // Reverse drawing order of features (this seems to be standard in KML) |
|---|
| | 151 | // Only do this on highest recursion level |
|---|
| | 152 | if (this.reverseFeatures && depth == 0) { |
|---|
| | 153 | this.features.reverse(); |
|---|
| | 154 | } |
|---|
| | 155 | |
|---|
| | 156 | return this.features; |
|---|
| | 157 | }, |
|---|
| | 158 | |
|---|
| | 159 | /** |
|---|
| | 160 | * Method: fetchLinks |
|---|
| | 161 | * Finds URLs of linked KML documents and fetches them |
|---|
| | 162 | * |
|---|
| | 163 | * Parameters: |
|---|
| | 164 | * data - {DOMElement} data to read/parse. |
|---|
| | 165 | * depth - {Integer} current recursion level |
|---|
| | 166 | * |
|---|
| | 167 | */ |
|---|
| | 168 | fetchLinks: function(data, depth) { |
|---|
| | 169 | |
|---|
| | 170 | var urlNodes = this.getElementsByTagNameNS(data, |
|---|
| | 171 | this.internalns, |
|---|
| | 172 | "NetworkLink"); |
|---|
| | 173 | var numurls = urlNodes.length; |
|---|
| | 174 | var urls = new Array(numurls); |
|---|
| | 175 | for(var i=0; i<numurls; i++) { |
|---|
| | 176 | var hrefNode = this.getElementsByTagNameNS(urlNodes[i], |
|---|
| | 177 | this.internalns, |
|---|
| | 178 | "href")[0]; |
|---|
| | 179 | if(hrefNode) { |
|---|
| | 180 | var href = OpenLayers.Util.getXmlNodeValue(hrefNode); |
|---|
| | 181 | |
|---|
| | 182 | if (this.fetched[href]) { |
|---|
| | 183 | continue; |
|---|
| | 184 | } |
|---|
| | 185 | |
|---|
| | 186 | if (OpenLayers.ProxyHost |
|---|
| | 187 | && OpenLayers.String.startsWith(href, "http")) { |
|---|
| | 188 | href = OpenLayers.ProxyHost + escape(href); |
|---|
| | 189 | } |
|---|
| | 190 | var request = new OpenLayers.Ajax.Request(href, |
|---|
| | 191 | {method: 'get', asynchronous: false }); |
|---|
| | 192 | this.fetched[href] = true; // prevent reloading the same urls |
|---|
| | 193 | if (request && request.transport) { |
|---|
| | 194 | this.read(request.transport.responseText, depth + 1); |
|---|
| | 195 | } |
|---|
| | 196 | } |
|---|
| | 197 | } |
|---|
| | 198 | |
|---|
| | 199 | var linkNodes = this.getElementsByTagNameNS(data, |
|---|
| | 200 | this.internalns, |
|---|
| | 201 | "Link"); |
|---|
| | 202 | var numlinks = linkNodes.length; |
|---|
| | 203 | var links = new Array(numlinks); |
|---|
| | 204 | for(var i=0; i<numlinks; i++) { |
|---|
| | 205 | var hrefNode = this.getElementsByTagNameNS(linkNodes[i], |
|---|
| | 206 | this.internalns, |
|---|
| | 207 | "href")[0]; |
|---|
| | 208 | if(hrefNode) { |
|---|
| | 209 | var href = OpenLayers.Util.getXmlNodeValue(hrefNode); |
|---|
| | 210 | |
|---|
| | 211 | if (this.fetched[href]) { |
|---|
| | 212 | continue; |
|---|
| | 213 | } |
|---|
| | 214 | |
|---|
| | 215 | if (OpenLayers.ProxyHost |
|---|
| | 216 | && OpenLayers.String.startsWith(href, "http")) { |
|---|
| | 217 | href = OpenLayers.ProxyHost + escape(href); |
|---|
| | 218 | } |
|---|
| | 219 | var request = new OpenLayers.Ajax.Request(href, |
|---|
| | 220 | {method: 'get', asynchronous: false }); |
|---|
| | 221 | this.fetched[href] = true; // prevent reloading the same urls |
|---|
| | 222 | if (request && request.transport) { |
|---|
| | 223 | this.read(request.transport.responseText, depth + 1); |
|---|
| | 224 | } |
|---|
| | 225 | } |
|---|
| | 226 | } |
|---|
| | 227 | }, |
|---|
| | 228 | |
|---|
| | 229 | /** |
|---|
| | 230 | * Method: getStyle |
|---|
| | 231 | * Retrieves a style from a style hash using styleUrl as the key |
|---|
| | 232 | * If the styleUrl doesn't exist yet, we try to fetch it |
|---|
| | 233 | * Internet |
|---|
| | 234 | * |
|---|
| | 235 | * Parameters: |
|---|
| | 236 | * styleUrl - {String} URL of style |
|---|
| | 237 | * |
|---|
| | 238 | */ |
|---|
| | 239 | getStyle: function(styleUrl) { |
|---|
| | 240 | |
|---|
| | 241 | // Fetch remote Style URLs (if not fetched before) |
|---|
| | 242 | if (!this.styles[styleUrl] |
|---|
| | 243 | && !OpenLayers.String.startsWith(styleUrl, "#") |
|---|
| | 244 | && !this.fetched[baseUrl] ) { |
|---|
| | 245 | |
|---|
| | 246 | var baseUrl = OpenLayers.Util.removeTail(styleUrl); |
|---|
| | 247 | var href = baseUrl; |
|---|
| | 248 | |
|---|
| | 249 | if (OpenLayers.String.startsWith(styleUrl, "http")) { |
|---|
| | 250 | href = OpenLayers.ProxyHost + escape(baseUrl); |
|---|
| | 251 | } |
|---|
| | 252 | |
|---|
| | 253 | var request = new OpenLayers.Ajax.Request(href, |
|---|
| | 254 | {method: 'get', asynchronous: false }); |
|---|
| | 255 | if (request && request.transport) { |
|---|
| | 256 | this.fetched[baseUrl] = true; // prevent loading more than once |
|---|
| | 257 | var data = request.transport.responseText; |
|---|
| | 258 | if(typeof data == "string") { |
|---|
| | 259 | data = OpenLayers.Format.XML.prototype.read.apply(this, |
|---|
| | 260 | [data]); |
|---|
| | 261 | } |
|---|
| | 262 | if (data) { |
|---|
| | 263 | this.parseStyles(data, baseUrl); |
|---|
| | 264 | } |
|---|
| | 265 | } |
|---|
| | 266 | |
|---|
| | 267 | } |
|---|
| | 268 | |
|---|
| | 269 | // return requested style |
|---|
| | 270 | var style = this.styles[styleUrl]; |
|---|
| | 271 | return style; |
|---|
| | 272 | }, |
|---|
| | 273 | |
|---|
| | 274 | |
|---|
| | 275 | /** |
|---|
| | 276 | * Method: parseStyles |
|---|
| | 277 | * Looks for <Style> nodes in the data and parses them |
|---|
| | 278 | * Also parses <StyleMap> nodes, but only uses the 'normal' key |
|---|
| | 279 | * |
|---|
| | 280 | * Parameters: |
|---|
| | 281 | * data - {DOMElement} data to read/parse. |
|---|
| | 282 | * baseUrl - {String} base URL of style |
|---|
| | 283 | * |
|---|
| | 284 | */ |
|---|
| | 285 | parseStyles: function(data, baseUrl) { |
|---|
| | 286 | // Parse Style nodes first |
|---|
| | 287 | var styleNodes = this.getElementsByTagNameNS(data, |
|---|
| | 288 | this.internalns, |
|---|
| | 289 | "Style"); |
|---|
| | 290 | var numNodes = styleNodes.length; |
|---|
| | 291 | var styles = {}; |
|---|
| | 292 | for(var i=0; i<numNodes; i++) { |
|---|
| | 293 | var style = this.parseStyle(styleNodes[i]); |
|---|
| | 294 | if(style) { |
|---|
| | 295 | styleName = (baseUrl || "") + "#" + style.name; |
|---|
| | 296 | styles[styleName] = style; |
|---|
| | 297 | } |
|---|
| | 298 | } |
|---|
| | 299 | |
|---|
| | 300 | // Parse StyleMaps afterwards |
|---|
| | 301 | // Only the default or "normal" part of the StyleMap is processed now |
|---|
| | 302 | // To do the select or "highlight" bit, we'd need to change lots more |
|---|
| | 303 | var styleMapNodes = this.getElementsByTagNameNS(data, |
|---|
| | 304 | this.internalns, |
|---|
| | 305 | "StyleMap"); |
|---|
| | 306 | |
|---|
| | 307 | var numNodes = styleMapNodes.length; |
|---|
| | 308 | |
|---|
| | 309 | for(var i=0; i<numNodes; i++) { |
|---|
| | 310 | var node = styleMapNodes[i]; |
|---|
| | 311 | var pairs = this.getElementsByTagNameNS(node, this.internalns, |
|---|
| | 312 | "Pair"); |
|---|
| | 313 | |
|---|
| | 314 | var id = node.getAttribute("id"); |
|---|
| | 315 | for (var j=0; j<pairs.length; j++) { |
|---|
| | 316 | var pair = pairs[j]; |
|---|
| | 317 | |
|---|
| | 318 | // Use the shortcut in the SLD format to quickly retrieve the |
|---|
| | 319 | // value of a node. Maybe it's good to have a method in Util |
|---|
| | 320 | // to do this |
|---|
| | 321 | var key = OpenLayers.Format.SLD.prototype.parseProperty(pair, |
|---|
| | 322 | this.internalns, "key"); |
|---|
| | 323 | var styleUrl = OpenLayers.Format.SLD.prototype.parseProperty(pair, |
|---|
| | 324 | this.internalns, "styleUrl"); |
|---|
| | 325 | |
|---|
| | 326 | if (key == "normal") { |
|---|
| | 327 | styles[(baseUrl || "") + "#" + id] = styles[(baseUrl || "") |
|---|
| | 328 | + styleUrl]; |
|---|
| | 329 | } |
|---|
| | 330 | |
|---|
| | 331 | if (key == "highlight") { |
|---|
| | 332 | // TODO: implement the "select" part |
|---|
| | 333 | } |
|---|
| | 334 | |
|---|
| | 335 | } |
|---|
| | 336 | } |
|---|
| | 337 | |
|---|
| | 338 | OpenLayers.Util.extend(this.styles, styles); |
|---|
| | 339 | |
|---|
| | 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 | var type, nodeList, geometry, parser; |
|---|
| | 356 | for(var i=0; i<types.length; ++i) { |
|---|
| | 357 | type = types[i]; |
|---|
| | 358 | //this.internalns = node.namespaceURI ? |
|---|
| | 359 | //node.namespaceURI : this.kmlns; |
|---|
| | 360 | styleTypeNode = this.getElementsByTagNameNS(node, |
|---|
| | 361 | this.internalns, type)[0]; |
|---|
| | 362 | if(!styleTypeNode) { |
|---|
| | 363 | continue; |
|---|
| | 364 | } |
|---|
| | 365 | |
|---|
| | 366 | // only deal with first geometry of this type |
|---|
| | 367 | switch (type.toLowerCase()) { |
|---|
| | 368 | case "linestyle": |
|---|
| | 369 | var colorNode = this.getElementsByTagNameNS(styleTypeNode, |
|---|
| | 370 | this.internalns, |
|---|
| | 371 | "color")[0]; |
|---|
| | 372 | if (colorNode) { |
|---|
| | 373 | var colorNodeValue = OpenLayers.Util.getXmlNodeValue( |
|---|
| | 374 | colorNode); |
|---|
| | 375 | var matches = colorNodeValue.match( |
|---|
| | 376 | this.regExes.kmlColor); |
|---|
| | 377 | |
|---|
| | 378 | // transparency |
|---|
| | 379 | var alpha = matches[1]; |
|---|
| | 380 | style["strokeOpacity"] = parseInt(alpha, 16) / 255; |
|---|
| | 381 | |
|---|
| | 382 | // rgb colors (google uses bgr) |
|---|
| | 383 | var b = matches[2]; |
|---|
| | 384 | var g = matches[3]; |
|---|
| | 385 | var r = matches[4]; |
|---|
| | 386 | style["strokeColor"] = "#" + r + g + b; |
|---|
| | 387 | } |
|---|
| | 388 | |
|---|
| | 389 | var widthNode = this.getElementsByTagNameNS(styleTypeNode, |
|---|
| | 390 | this.internalns, |
|---|
| | 391 | "width")[0]; |
|---|
| | 392 | if (widthNode) { |
|---|
| | 393 | style["strokeWidth"] = OpenLayers.Util.getXmlNodeValue( |
|---|
| | 394 | widthNode); |
|---|
| | 395 | } |
|---|
| | 396 | |
|---|
| | 397 | case "polystyle": |
|---|
| | 398 | var colorNode = this.getElementsByTagNameNS(styleTypeNode, |
|---|
| | 399 | this.internalns, |
|---|
| | 400 | "color")[0]; |
|---|
| | 401 | if (colorNode) { |
|---|
| | 402 | var colorNodeValue = OpenLayers.Util.getXmlNodeValue( |
|---|
| | 403 | colorNode); |
|---|
| | 404 | var matches = colorNodeValue.match( |
|---|
| | 405 | this.regExes.kmlColor); |
|---|
| | 406 | |
|---|
| | 407 | // transparency |
|---|
| | 408 | var alpha = matches[1]; |
|---|
| | 409 | style["fillOpacity"] = parseInt(alpha, 16) / 255; |
|---|
| | 410 | |
|---|
| | 411 | // rgb colors (google uses bgr) |
|---|
| | 412 | var b = matches[2]; |
|---|
| | 413 | var g = matches[3]; |
|---|
| | 414 | var r = matches[4]; |
|---|
| | 415 | style["fillColor"] = "#" + r + g + b; |
|---|
| | 416 | } |
|---|
| | 417 | |
|---|
| | 418 | break; |
|---|
| | 419 | case "iconstyle": |
|---|
| | 420 | var iconNode = this.getElementsByTagNameNS(styleTypeNode, |
|---|
| | 421 | this.internalns, |
|---|
| | 422 | "Icon")[0]; |
|---|
| | 423 | |
|---|
| | 424 | // set default width and height of icon |
|---|
| | 425 | style["graphicWidth"] = 32; |
|---|
| | 426 | style["graphicHeight"] = 32; |
|---|
| | 427 | |
|---|
| | 428 | if (iconNode) { |
|---|
| | 429 | var hrefNode = this.getElementsByTagNameNS(iconNode, |
|---|
| | 430 | this.internalns, |
|---|
| | 431 | "href")[0]; |
|---|
| | 432 | if (hrefNode) { |
|---|
| | 433 | href = OpenLayers.Util.getXmlNodeValue(hrefNode); |
|---|
| | 434 | |
|---|
| | 435 | // support for internal icons |
|---|
| | 436 | // (/root://icons/palette-x.png) |
|---|
| | 437 | // x and y tell the position on the palette: |
|---|
| | 438 | // - in pixels |
|---|
| | 439 | // - starting from the left bottom |
|---|
| | 440 | // We need to translate that to a position in the |
|---|
| | 441 | // list and request the appropriate icon from the |
|---|
| | 442 | // google maps website |
|---|
| | 443 | var matches = href.match(this.regExes.kmlIconPalette); |
|---|
| | 444 | if (matches) { |
|---|
| | 445 | var palette = matches[1]; |
|---|
| | 446 | var file_extension = matches[2]; |
|---|
| | 447 | |
|---|
| | 448 | var xNode = this.getElementsByTagNameNS(iconNode, |
|---|
| | 449 | this.internalns, |
|---|
| | 450 | "x")[0]; |
|---|
| | 451 | var x = OpenLayers.Util.getXmlNodeValue(xNode); |
|---|
| | 452 | |
|---|
| | 453 | var yNode = this.getElementsByTagNameNS(iconNode, |
|---|
| | 454 | this.internalns, |
|---|
| | 455 | "y")[0]; |
|---|
| | 456 | var y = OpenLayers.Util.getXmlNodeValue(yNode); |
|---|
| | 457 | |
|---|
| | 458 | var posX = x ? x/32 : 0; |
|---|
| | 459 | var posY = y ? (7 - y/32) : 7; |
|---|
| | 460 | |
|---|
| | 461 | var pos = posY * 8 + posX; |
|---|
| | 462 | var href = "http://maps.google.com/mapfiles/kml/pal" |
|---|
| | 463 | + palette + "/icon" + pos + file_extension; |
|---|
| | 464 | } |
|---|
| | 465 | |
|---|
| | 466 | |
|---|
| | 467 | var wNode = this.getElementsByTagNameNS(iconNode, |
|---|
| | 468 | this.internalns, |
|---|
| | 469 | "w")[0]; |
|---|
| | 470 | var w = OpenLayers.Util.getXmlNodeValue(wNode); |
|---|
| | 471 | |
|---|
| | 472 | var hNode = this.getElementsByTagNameNS(iconNode, |
|---|
| | 473 | this.internalns, |
|---|
| | 474 | "h")[0]; |
|---|
| | 475 | var h = OpenLayers.Util.getXmlNodeValue(hNode); |
|---|
| | 476 | |
|---|
| | 477 | if (w) { |
|---|
| | 478 | style["graphicWidth"] = parseInt(w); |
|---|
| | 479 | } |
|---|
| | 480 | |
|---|
| | 481 | if (h) { |
|---|
| | 482 | style["graphicHeight"] = parseInt(h); |
|---|
| | 483 | } |
|---|
| | 484 | |
|---|
| | 485 | style["graphicOpacity"] = 1; |
|---|
| | 486 | style["externalGraphic"] = href; |
|---|
| | 487 | } |
|---|
| | 488 | |
|---|
| | 489 | } |
|---|
| | 490 | |
|---|
| | 491 | |
|---|
| | 492 | // hotSpots define the offset for an Icon |
|---|
| | 493 | var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode, |
|---|
| | 494 | this.internalns, |
|---|
| | 495 | "hotSpot")[0]; |
|---|
| | 496 | if (hotSpotNode) { |
|---|
| | 497 | var x = hotSpotNode.getAttribute("x"); |
|---|
| | 498 | var y = hotSpotNode.getAttribute("y"); |
|---|
| | 499 | |
|---|
| | 500 | var xUnits = hotSpotNode.getAttribute("xunits"); |
|---|
| | 501 | if (xUnits == "pixels") { |
|---|
| | 502 | style["graphicXOffset"] = parseInt(x); |
|---|
| | 503 | } |
|---|
| | 504 | else if (xUnits == "insetPixels") { |
|---|
| | 505 | style["graphicXOffset"] = style["graphicWidth"] |
|---|
| | 506 | - parseInt(x); |
|---|
| | 507 | } |
|---|
| | 508 | else if (xUnits == "fraction") { |
|---|
| | 509 | style["graphicXOffset"] = style["graphicWidth"] |
|---|
| | 510 | * parseFloat(x); |
|---|
| | 511 | } |
|---|
| | 512 | |
|---|
| | 513 | var yUnits = hotSpotNode.getAttribute("yunits"); |
|---|
| | 514 | if (yUnits == "pixels") { |
|---|
| | 515 | style["graphicYOffset"] = parseInt(y); |
|---|
| | 516 | } |
|---|
| | 517 | else if (yUnits == "insetPixels") { |
|---|
| | 518 | style["graphicYOffset"] = style["graphicHeight"] |
|---|
| | 519 | - parseInt(y); |
|---|
| | 520 | } |
|---|
| | 521 | else if (yUnits == "fraction") { |
|---|
| | 522 | style["graphicYOffset"] = style["graphicHeight"] |
|---|
| | 523 | * parseFloat(y); |
|---|
| | 524 | } |
|---|
| | 525 | } |
|---|
| | 526 | break; |
|---|
| | 527 | case "balloonstyle": |
|---|
| | 528 | var balloonStyle = OpenLayers.Util.getXmlNodeValue( |
|---|
| | 529 | styleTypeNode); |
|---|
| | 530 | if (balloonStyle) { |
|---|
| | 531 | style["balloonStyle"] = balloonStyle.replace( |
|---|
| | 532 | this.regExes.straightBracket, "${$1}"); |
|---|
| | 533 | } |
|---|
| | 534 | break; |
|---|
| | 535 | default: |
|---|
| | 536 | } |
|---|
| | 537 | } |
|---|
| | 538 | |
|---|
| | 539 | |
|---|
| | 540 | // Some polygons have no line color, so we use that |
|---|
| | 541 | if (!style["strokeColor"]) { |
|---|
| | 542 | style["strokeColor"] = style["fillColor"]; |
|---|
| | 543 | } |
|---|
| | 544 | |
|---|
| | 545 | // Give the style a name |
|---|
| | 546 | style["name"] = node.getAttribute("id"); |
|---|
| | 547 | |
|---|
| | 548 | return style; |
|---|
| | 549 | }, |
|---|
| | 550 | |
|---|
| | 551 | /** |
|---|
| | 552 | * Method: parseFeatures |
|---|
| | 553 | * Loop through all Placemark nodes and parse them. |
|---|
| | 554 | * Will create a list of features |
|---|
| | 555 | * |
|---|
| | 556 | * Parameters: |
|---|
| | 557 | * data - {DOMElement} data to read/parse. |
|---|
| | 558 | * |
|---|
| | 559 | */ |
|---|
| | 560 | parseFeatures: function(data) { |
|---|