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