KML.js 54 KB

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