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