| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517 |
- /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
- * full list of contributors). Published under the 2-clause BSD license.
- * See license.txt in the OpenLayers distribution or repository for the
- * full text of the license. */
- /**
- * @requires OpenLayers/BaseTypes/Date.js
- * @requires OpenLayers/Format/XML.js
- * @requires OpenLayers/Feature/Vector.js
- * @requires OpenLayers/Geometry/Point.js
- * @requires OpenLayers/Geometry/LineString.js
- * @requires OpenLayers/Geometry/Polygon.js
- * @requires OpenLayers/Geometry/Collection.js
- * @requires OpenLayers/Request/XMLHttpRequest.js
- * @requires OpenLayers/Projection.js
- */
- /**
- * Class: OpenLayers.Format.KML
- * Read/Write KML. Create a new instance with the <OpenLayers.Format.KML>
- * constructor.
- *
- * Inherits from:
- * - <OpenLayers.Format.XML>
- */
- OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, {
-
- /**
- * Property: namespaces
- * {Object} Mapping of namespace aliases to namespace URIs.
- */
- namespaces: {
- kml: "http://www.opengis.net/kml/2.2",
- gx: "http://www.google.com/kml/ext/2.2"
- },
- /**
- * APIProperty: kmlns
- * {String} KML Namespace to use. Defaults to 2.0 namespace.
- */
- kmlns: "http://earth.google.com/kml/2.0",
-
- /**
- * APIProperty: placemarksDesc
- * {String} Name of the placemarks. Default is "No description available".
- */
- placemarksDesc: "No description available",
-
- /**
- * APIProperty: foldersName
- * {String} Name of the folders. Default is "OpenLayers export".
- * If set to null, no name element will be created.
- */
- foldersName: "OpenLayers export",
-
- /**
- * APIProperty: foldersDesc
- * {String} Description of the folders. Default is "Exported on [date]."
- * If set to null, no description element will be created.
- */
- foldersDesc: "Exported on " + new Date(),
-
- /**
- * APIProperty: extractAttributes
- * {Boolean} Extract attributes from KML. Default is true.
- * Extracting styleUrls requires this to be set to true
- * Note that currently only Data and SimpleData
- * elements are handled.
- */
- extractAttributes: true,
-
- /**
- * APIProperty: kvpAttributes
- * {Boolean} Only used if extractAttributes is true.
- * If set to true, attributes will be simple
- * key-value pairs, compatible with other formats,
- * Any displayName elements will be ignored.
- * If set to false, attributes will be objects,
- * retaining any displayName elements, but not
- * compatible with other formats. Any CDATA in
- * displayName will be read in as a string value.
- * Default is false.
- */
- kvpAttributes: false,
-
- /**
- * Property: extractStyles
- * {Boolean} Extract styles from KML. Default is false.
- * Extracting styleUrls also requires extractAttributes to be
- * set to true
- */
- extractStyles: false,
-
- /**
- * APIProperty: extractTracks
- * {Boolean} Extract gx:Track elements from Placemark elements. Default
- * is false. If true, features will be generated for all points in
- * all gx:Track elements. Features will have a when (Date) attribute
- * based on when elements in the track. If tracks include angle
- * elements, features will have heading, tilt, and roll attributes.
- * If track point coordinates have three values, features will have
- * an altitude attribute with the third coordinate value.
- */
- extractTracks: false,
-
- /**
- * APIProperty: trackAttributes
- * {Array} If <extractTracks> is true, points within gx:Track elements will
- * be parsed as features with when, heading, tilt, and roll attributes.
- * Any additional attribute names can be provided in <trackAttributes>.
- */
- trackAttributes: null,
-
- /**
- * Property: internalns
- * {String} KML Namespace to use -- defaults to the namespace of the
- * Placemark node being parsed, but falls back to kmlns.
- */
- internalns: null,
- /**
- * Property: features
- * {Array} Array of features
- *
- */
- features: null,
- /**
- * Property: styles
- * {Object} Storage of style objects
- *
- */
- styles: null,
-
- /**
- * Property: styleBaseUrl
- * {String}
- */
- styleBaseUrl: "",
- /**
- * Property: fetched
- * {Object} Storage of KML URLs that have been fetched before
- * in order to prevent reloading them.
- */
- fetched: null,
- /**
- * APIProperty: maxDepth
- * {Integer} Maximum depth for recursive loading external KML URLs
- * Defaults to 0: do no external fetching
- */
- maxDepth: 0,
- /**
- * Constructor: OpenLayers.Format.KML
- * Create a new parser for KML.
- *
- * Parameters:
- * options - {Object} An optional object whose properties will be set on
- * this instance.
- */
- initialize: function(options) {
- // compile regular expressions once instead of every time they are used
- this.regExes = {
- trimSpace: (/^\s*|\s*$/g),
- removeSpace: (/\s*/g),
- splitSpace: (/\s+/),
- trimComma: (/\s*,\s*/g),
- kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
- kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
- straightBracket: (/\$\[(.*?)\]/g)
- };
- // KML coordinates are always in longlat WGS84
- this.externalProjection = new OpenLayers.Projection("EPSG:4326");
- OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
- },
- /**
- * APIMethod: read
- * Read data from a string, and return a list of features.
- *
- * Parameters:
- * data - {String} or {DOMElement} data to read/parse.
- *
- * Returns:
- * {Array(<OpenLayers.Feature.Vector>)} List of features.
- */
- read: function(data) {
- this.features = [];
- this.styles = {};
- this.fetched = {};
- // Set default options
- var options = {
- depth: 0,
- styleBaseUrl: this.styleBaseUrl
- };
- return this.parseData(data, options);
- },
- /**
- * Method: parseData
- * Read data from a string, and return a list of features.
- *
- * Parameters:
- * data - {String} or {DOMElement} data to read/parse.
- * options - {Object} Hash of options
- *
- * Returns:
- * {Array(<OpenLayers.Feature.Vector>)} List of features.
- */
- parseData: function(data, options) {
- if(typeof data == "string") {
- data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
- }
- // Loop throught the following node types in this order and
- // process the nodes found
- var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"];
- for(var i=0, len=types.length; i<len; ++i) {
- var type = types[i];
- var nodes = this.getElementsByTagNameNS(data, "*", type);
- // skip to next type if no nodes are found
- if(nodes.length == 0) {
- continue;
- }
- switch (type.toLowerCase()) {
- // Fetch external links
- case "link":
- case "networklink":
- this.parseLinks(nodes, options);
- break;
- // parse style information
- case "style":
- if (this.extractStyles) {
- this.parseStyles(nodes, options);
- }
- break;
- case "stylemap":
- if (this.extractStyles) {
- this.parseStyleMaps(nodes, options);
- }
- break;
- // parse features
- case "placemark":
- this.parseFeatures(nodes, options);
- break;
- }
- }
-
- return this.features;
- },
- /**
- * Method: parseLinks
- * Finds URLs of linked KML documents and fetches them
- *
- * Parameters:
- * nodes - {Array} of {DOMElement} data to read/parse.
- * options - {Object} Hash of options
- *
- */
- parseLinks: function(nodes, options) {
-
- // Fetch external links <NetworkLink> and <Link>
- // Don't do anything if we have reached our maximum depth for recursion
- if (options.depth >= this.maxDepth) {
- return false;
- }
- // increase depth
- var newOptions = OpenLayers.Util.extend({}, options);
- newOptions.depth++;
- for(var i=0, len=nodes.length; i<len; i++) {
- var href = this.parseProperty(nodes[i], "*", "href");
- if(href && !this.fetched[href]) {
- this.fetched[href] = true; // prevent reloading the same urls
- var data = this.fetchLink(href);
- if (data) {
- this.parseData(data, newOptions);
- }
- }
- }
- },
- /**
- * Method: fetchLink
- * Fetches a URL and returns the result
- *
- * Parameters:
- * href - {String} url to be fetched
- *
- */
- fetchLink: function(href) {
- var request = OpenLayers.Request.GET({url: href, async: false});
- if (request) {
- return request.responseText;
- }
- },
- /**
- * Method: parseStyles
- * Parses <Style> nodes
- *
- * Parameters:
- * nodes - {Array} of {DOMElement} data to read/parse.
- * options - {Object} Hash of options
- *
- */
- parseStyles: function(nodes, options) {
- for(var i=0, len=nodes.length; i<len; i++) {
- var style = this.parseStyle(nodes[i]);
- if(style) {
- var styleName = (options.styleBaseUrl || "") + "#" + style.id;
-
- this.styles[styleName] = style;
- }
- }
- },
- /**
- * Method: parseKmlColor
- * Parses a kml color (in 'aabbggrr' format) and returns the corresponding
- * color and opacity or null if the color is invalid.
- *
- * Parameters:
- * kmlColor - {String} a kml formated color
- *
- * Returns:
- * {Object}
- */
- parseKmlColor: function(kmlColor) {
- var color = null;
- if (kmlColor) {
- var matches = kmlColor.match(this.regExes.kmlColor);
- if (matches) {
- color = {
- color: '#' + matches[4] + matches[3] + matches[2],
- opacity: parseInt(matches[1], 16) / 255
- };
- }
- }
- return color;
- },
- /**
- * Method: parseStyle
- * Parses the children of a <Style> node and builds the style hash
- * accordingly
- *
- * Parameters:
- * node - {DOMElement} <Style> node
- *
- */
- parseStyle: function(node) {
- var style = {};
-
- var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle",
- "LabelStyle"];
- var type, styleTypeNode, nodeList, geometry, parser;
- for(var i=0, len=types.length; i<len; ++i) {
- type = types[i];
- styleTypeNode = this.getElementsByTagNameNS(node, "*", type)[0];
- if(!styleTypeNode) {
- continue;
- }
- // only deal with first geometry of this type
- switch (type.toLowerCase()) {
- case "linestyle":
- var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
- var color = this.parseKmlColor(kmlColor);
- if (color) {
- style["strokeColor"] = color.color;
- style["strokeOpacity"] = color.opacity;
- }
-
- var width = this.parseProperty(styleTypeNode, "*", "width");
- if (width) {
- style["strokeWidth"] = width;
- }
- break;
- case "polystyle":
- var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
- var color = this.parseKmlColor(kmlColor);
- if (color) {
- style["fillOpacity"] = color.opacity;
- style["fillColor"] = color.color;
- }
- // Check if fill is disabled
- var fill = this.parseProperty(styleTypeNode, "*", "fill");
- if (fill == "0") {
- style["fillColor"] = "none";
- }
- // Check if outline is disabled
- var outline = this.parseProperty(styleTypeNode, "*", "outline");
- if (outline == "0") {
- style["strokeWidth"] = "0";
- }
-
- break;
- case "iconstyle":
- // set scale
- var scale = parseFloat(this.parseProperty(styleTypeNode,
- "*", "scale") || 1);
-
- // set default width and height of icon
- var width = 32 * scale;
- var height = 32 * scale;
- var iconNode = this.getElementsByTagNameNS(styleTypeNode,
- "*",
- "Icon")[0];
- if (iconNode) {
- var href = this.parseProperty(iconNode, "*", "href");
- if (href) {
- var w = this.parseProperty(iconNode, "*", "w");
- var h = this.parseProperty(iconNode, "*", "h");
- // Settings for Google specific icons that are 64x64
- // We set the width and height to 64 and halve the
- // scale to prevent icons from being too big
- var google = "http://maps.google.com/mapfiles/kml";
- if (OpenLayers.String.startsWith(
- href, google) && !w && !h) {
- w = 64;
- h = 64;
- scale = scale / 2;
- }
-
- // if only dimension is defined, make sure the
- // other one has the same value
- w = w || h;
- h = h || w;
- if (w) {
- width = parseInt(w) * scale;
- }
- if (h) {
- height = parseInt(h) * scale;
- }
- // support for internal icons
- // (/root://icons/palette-x.png)
- // x and y tell the position on the palette:
- // - in pixels
- // - starting from the left bottom
- // We translate that to a position in the list
- // and request the appropriate icon from the
- // google maps website
- var matches = href.match(this.regExes.kmlIconPalette);
- if (matches) {
- var palette = matches[1];
- var file_extension = matches[2];
- var x = this.parseProperty(iconNode, "*", "x");
- var y = this.parseProperty(iconNode, "*", "y");
- var posX = x ? x/32 : 0;
- var posY = y ? (7 - y/32) : 7;
- var pos = posY * 8 + posX;
- href = "http://maps.google.com/mapfiles/kml/pal"
- + palette + "/icon" + pos + file_extension;
- }
- style["graphicOpacity"] = 1; // fully opaque
- style["externalGraphic"] = href;
- }
- }
- // hotSpots define the offset for an Icon
- var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode,
- "*",
- "hotSpot")[0];
- if (hotSpotNode) {
- var x = parseFloat(hotSpotNode.getAttribute("x"));
- var y = parseFloat(hotSpotNode.getAttribute("y"));
- var xUnits = hotSpotNode.getAttribute("xunits");
- if (xUnits == "pixels") {
- style["graphicXOffset"] = -x * scale;
- }
- else if (xUnits == "insetPixels") {
- style["graphicXOffset"] = -width + (x * scale);
- }
- else if (xUnits == "fraction") {
- style["graphicXOffset"] = -width * x;
- }
- var yUnits = hotSpotNode.getAttribute("yunits");
- if (yUnits == "pixels") {
- style["graphicYOffset"] = -height + (y * scale) + 1;
- }
- else if (yUnits == "insetPixels") {
- style["graphicYOffset"] = -(y * scale) + 1;
- }
- else if (yUnits == "fraction") {
- style["graphicYOffset"] = -height * (1 - y) + 1;
- }
- }
- style["graphicWidth"] = width;
- style["graphicHeight"] = height;
- break;
- case "balloonstyle":
- var balloonStyle = OpenLayers.Util.getXmlNodeValue(
- styleTypeNode);
- if (balloonStyle) {
- style["balloonStyle"] = balloonStyle.replace(
- this.regExes.straightBracket, "${$1}");
- }
- break;
- case "labelstyle":
- var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
- var color = this.parseKmlColor(kmlColor);
- if (color) {
- style["fontColor"] = color.color;
- style["fontOpacity"] = color.opacity;
- }
- break;
- default:
- }
- }
- // Some polygons have no line color, so we use the fillColor for that
- if (!style["strokeColor"] && style["fillColor"]) {
- style["strokeColor"] = style["fillColor"];
- }
- var id = node.getAttribute("id");
- if (id && style) {
- style.id = id;
- }
- return style;
- },
- /**
- * Method: parseStyleMaps
- * Parses <StyleMap> nodes, but only uses the 'normal' key
- *
- * Parameters:
- * nodes - {Array} of {DOMElement} data to read/parse.
- * options - {Object} Hash of options
- *
- */
- parseStyleMaps: function(nodes, options) {
- // Only the default or "normal" part of the StyleMap is processed now
- // To do the select or "highlight" bit, we'd need to change lots more
- for(var i=0, len=nodes.length; i<len; i++) {
- var node = nodes[i];
- var pairs = this.getElementsByTagNameNS(node, "*",
- "Pair");
- var id = node.getAttribute("id");
- for (var j=0, jlen=pairs.length; j<jlen; j++) {
- var pair = pairs[j];
- // Use the shortcut in the SLD format to quickly retrieve the
- // value of a node. Maybe it's good to have a method in
- // Format.XML to do this
- var key = this.parseProperty(pair, "*", "key");
- var styleUrl = this.parseProperty(pair, "*", "styleUrl");
- if (styleUrl && key == "normal") {
- this.styles[(options.styleBaseUrl || "") + "#" + id] =
- this.styles[(options.styleBaseUrl || "") + styleUrl];
- }
- // TODO: implement the "select" part
- //if (styleUrl && key == "highlight") {
- //}
- }
- }
- },
- /**
- * Method: parseFeatures
- * Loop through all Placemark nodes and parse them.
- * Will create a list of features
- *
- * Parameters:
- * nodes - {Array} of {DOMElement} data to read/parse.
- * options - {Object} Hash of options
- *
- */
- parseFeatures: function(nodes, options) {
- var features = [];
- for(var i=0, len=nodes.length; i<len; i++) {
- var featureNode = nodes[i];
- var feature = this.parseFeature.apply(this,[featureNode]) ;
- if(feature) {
- // Create reference to styleUrl
- if (this.extractStyles && feature.attributes &&
- feature.attributes.styleUrl) {
- feature.style = this.getStyle(feature.attributes.styleUrl, options);
- }
- if (this.extractStyles) {
- // Make sure that <Style> nodes within a placemark are
- // processed as well
- var inlineStyleNode = this.getElementsByTagNameNS(featureNode,
- "*",
- "Style")[0];
- if (inlineStyleNode) {
- var inlineStyle= this.parseStyle(inlineStyleNode);
- if (inlineStyle) {
- feature.style = OpenLayers.Util.extend(
- feature.style, inlineStyle
- );
- }
- }
- }
- // check if gx:Track elements should be parsed
- if (this.extractTracks) {
- var tracks = this.getElementsByTagNameNS(
- featureNode, this.namespaces.gx, "Track"
- );
- if (tracks && tracks.length > 0) {
- var track = tracks[0];
- var container = {
- features: [],
- feature: feature
- };
- this.readNode(track, container);
- if (container.features.length > 0) {
- features.push.apply(features, container.features);
- }
- }
- } else {
- // add feature to list of features
- features.push(feature);
- }
- } else {
- throw "Bad Placemark: " + i;
- }
- }
- // add new features to existing feature list
- this.features = this.features.concat(features);
- },
-
- /**
- * Property: readers
- * Contains public functions, grouped by namespace prefix, that will
- * be applied when a namespaced node is found matching the function
- * name. The function will be applied in the scope of this parser
- * with two arguments: the node being read and a context object passed
- * from the parent.
- */
- readers: {
- "kml": {
- "when": function(node, container) {
- container.whens.push(OpenLayers.Date.parse(
- this.getChildValue(node)
- ));
- },
- "_trackPointAttribute": function(node, container) {
- var name = node.nodeName.split(":").pop();
- container.attributes[name].push(this.getChildValue(node));
- }
- },
- "gx": {
- "Track": function(node, container) {
- var obj = {
- whens: [],
- points: [],
- angles: []
- };
- if (this.trackAttributes) {
- var name;
- obj.attributes = {};
- for (var i=0, ii=this.trackAttributes.length; i<ii; ++i) {
- name = this.trackAttributes[i];
- obj.attributes[name] = [];
- if (!(name in this.readers.kml)) {
- this.readers.kml[name] = this.readers.kml._trackPointAttribute;
- }
- }
- }
- this.readChildNodes(node, obj);
- if (obj.whens.length !== obj.points.length) {
- throw new Error("gx:Track with unequal number of when (" +
- obj.whens.length + ") and gx:coord (" +
- obj.points.length + ") elements.");
- }
- var hasAngles = obj.angles.length > 0;
- if (hasAngles && obj.whens.length !== obj.angles.length) {
- throw new Error("gx:Track with unequal number of when (" +
- obj.whens.length + ") and gx:angles (" +
- obj.angles.length + ") elements.");
- }
- var feature, point, angles;
- for (var i=0, ii=obj.whens.length; i<ii; ++i) {
- feature = container.feature.clone();
- feature.fid = container.feature.fid || container.feature.id;
- point = obj.points[i];
- feature.geometry = point;
- if ("z" in point) {
- feature.attributes.altitude = point.z;
- }
- if (this.internalProjection && this.externalProjection) {
- feature.geometry.transform(
- this.externalProjection, this.internalProjection
- );
- }
- if (this.trackAttributes) {
- for (var j=0, jj=this.trackAttributes.length; j<jj; ++j) {
- var name = this.trackAttributes[j];
- feature.attributes[name] = obj.attributes[name][i];
- }
- }
- feature.attributes.when = obj.whens[i];
- feature.attributes.trackId = container.feature.id;
- if (hasAngles) {
- angles = obj.angles[i];
- feature.attributes.heading = parseFloat(angles[0]);
- feature.attributes.tilt = parseFloat(angles[1]);
- feature.attributes.roll = parseFloat(angles[2]);
- }
- container.features.push(feature);
- }
- },
- "coord": function(node, container) {
- var str = this.getChildValue(node);
- var coords = str.replace(this.regExes.trimSpace, "").split(/\s+/);
- var point = new OpenLayers.Geometry.Point(coords[0], coords[1]);
- if (coords.length > 2) {
- point.z = parseFloat(coords[2]);
- }
- container.points.push(point);
- },
- "angles": function(node, container) {
- var str = this.getChildValue(node);
- var parts = str.replace(this.regExes.trimSpace, "").split(/\s+/);
- container.angles.push(parts);
- }
- }
- },
-
- /**
- * Method: parseFeature
- * This function is the core of the KML parsing code in OpenLayers.
- * It creates the geometries that are then attached to the returned
- * feature, and calls parseAttributes() to get attribute data out.
- *
- * Parameters:
- * node - {DOMElement}
- *
- * Returns:
- * {<OpenLayers.Feature.Vector>} A vector feature.
- */
- parseFeature: function(node) {
- // only accept one geometry per feature - look for highest "order"
- var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
- var type, nodeList, geometry, parser;
- for(var i=0, len=order.length; i<len; ++i) {
- type = order[i];
- this.internalns = node.namespaceURI ?
- node.namespaceURI : this.kmlns;
- nodeList = this.getElementsByTagNameNS(node,
- this.internalns, type);
- if(nodeList.length > 0) {
- // only deal with first geometry of this type
- var parser = this.parseGeometry[type.toLowerCase()];
- if(parser) {
- geometry = parser.apply(this, [nodeList[0]]);
- if (this.internalProjection && this.externalProjection) {
- geometry.transform(this.externalProjection,
- this.internalProjection);
- }
- } else {
- throw new TypeError("Unsupported geometry type: " + type);
- }
- // stop looking for different geometry types
- break;
- }
- }
- // construct feature (optionally with attributes)
- var attributes;
- if(this.extractAttributes) {
- attributes = this.parseAttributes(node);
- }
- var feature = new OpenLayers.Feature.Vector(geometry, attributes);
- var fid = node.getAttribute("id") || node.getAttribute("name");
- if(fid != null) {
- feature.fid = fid;
- }
- return feature;
- },
-
- /**
- * Method: getStyle
- * Retrieves a style from a style hash using styleUrl as the key
- * If the styleUrl doesn't exist yet, we try to fetch it
- * Internet
- *
- * Parameters:
- * styleUrl - {String} URL of style
- * options - {Object} Hash of options
- *
- * Returns:
- * {Object} - (reference to) Style hash
- */
- getStyle: function(styleUrl, options) {
- var styleBaseUrl = OpenLayers.Util.removeTail(styleUrl);
- var newOptions = OpenLayers.Util.extend({}, options);
- newOptions.depth++;
- newOptions.styleBaseUrl = styleBaseUrl;
- // Fetch remote Style URLs (if not fetched before)
- if (!this.styles[styleUrl]
- && !OpenLayers.String.startsWith(styleUrl, "#")
- && newOptions.depth <= this.maxDepth
- && !this.fetched[styleBaseUrl] ) {
- var data = this.fetchLink(styleBaseUrl);
- if (data) {
- this.parseData(data, newOptions);
- }
- }
- // return requested style
- var style = OpenLayers.Util.extend({}, this.styles[styleUrl]);
- return style;
- },
-
- /**
- * Property: parseGeometry
- * Properties of this object are the functions that parse geometries based
- * on their type.
- */
- parseGeometry: {
-
- /**
- * Method: parseGeometry.point
- * Given a KML node representing a point geometry, create an OpenLayers
- * point geometry.
- *
- * Parameters:
- * node - {DOMElement} A KML Point node.
- *
- * Returns:
- * {<OpenLayers.Geometry.Point>} A point geometry.
- */
- point: function(node) {
- var nodeList = this.getElementsByTagNameNS(node, this.internalns,
- "coordinates");
- var coords = [];
- if(nodeList.length > 0) {
- var coordString = nodeList[0].firstChild.nodeValue;
- coordString = coordString.replace(this.regExes.removeSpace, "");
- coords = coordString.split(",");
- }
- var point = null;
- if(coords.length > 1) {
- // preserve third dimension
- if(coords.length == 2) {
- coords[2] = null;
- }
- point = new OpenLayers.Geometry.Point(coords[0], coords[1],
- coords[2]);
- } else {
- throw "Bad coordinate string: " + coordString;
- }
- return point;
- },
-
- /**
- * Method: parseGeometry.linestring
- * Given a KML node representing a linestring geometry, create an
- * OpenLayers linestring geometry.
- *
- * Parameters:
- * node - {DOMElement} A KML LineString node.
- *
- * Returns:
- * {<OpenLayers.Geometry.LineString>} A linestring geometry.
- */
- linestring: function(node, ring) {
- var nodeList = this.getElementsByTagNameNS(node, this.internalns,
- "coordinates");
- var line = null;
- if(nodeList.length > 0) {
- var coordString = this.getChildValue(nodeList[0]);
- coordString = coordString.replace(this.regExes.trimSpace,
- "");
- coordString = coordString.replace(this.regExes.trimComma,
- ",");
- var pointList = coordString.split(this.regExes.splitSpace);
- var numPoints = pointList.length;
- var points = new Array(numPoints);
- var coords, numCoords;
- for(var i=0; i<numPoints; ++i) {
- coords = pointList[i].split(",");
- numCoords = coords.length;
- if(numCoords > 1) {
- if(coords.length == 2) {
- coords[2] = null;
- }
- points[i] = new OpenLayers.Geometry.Point(coords[0],
- coords[1],
- coords[2]);
- } else {
- throw "Bad LineString point coordinates: " +
- pointList[i];
- }
- }
- if(numPoints) {
- if(ring) {
- line = new OpenLayers.Geometry.LinearRing(points);
- } else {
- line = new OpenLayers.Geometry.LineString(points);
- }
- } else {
- throw "Bad LineString coordinates: " + coordString;
- }
- }
- return line;
- },
-
- /**
- * Method: parseGeometry.polygon
- * Given a KML node representing a polygon geometry, create an
- * OpenLayers polygon geometry.
- *
- * Parameters:
- * node - {DOMElement} A KML Polygon node.
- *
- * Returns:
- * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
- */
- polygon: function(node) {
- var nodeList = this.getElementsByTagNameNS(node, this.internalns,
- "LinearRing");
- var numRings = nodeList.length;
- var components = new Array(numRings);
- if(numRings > 0) {
- // this assumes exterior ring first, inner rings after
- var ring;
- for(var i=0, len=nodeList.length; i<len; ++i) {
- ring = this.parseGeometry.linestring.apply(this,
- [nodeList[i], true]);
- if(ring) {
- components[i] = ring;
- } else {
- throw "Bad LinearRing geometry: " + i;
- }
- }
- }
- return new OpenLayers.Geometry.Polygon(components);
- },
-
- /**
- * Method: parseGeometry.multigeometry
- * Given a KML node representing a multigeometry, create an
- * OpenLayers geometry collection.
- *
- * Parameters:
- * node - {DOMElement} A KML MultiGeometry node.
- *
- * Returns:
- * {<OpenLayers.Geometry.Collection>} A geometry collection.
- */
- multigeometry: function(node) {
- var child, parser;
- var parts = [];
- var children = node.childNodes;
- for(var i=0, len=children.length; i<len; ++i ) {
- child = children[i];
- if(child.nodeType == 1) {
- var type = (child.prefix) ?
- child.nodeName.split(":")[1] :
- child.nodeName;
- var parser = this.parseGeometry[type.toLowerCase()];
- if(parser) {
- parts.push(parser.apply(this, [child]));
- }
- }
- }
- return new OpenLayers.Geometry.Collection(parts);
- }
-
- },
- /**
- * Method: parseAttributes
- *
- * Parameters:
- * node - {DOMElement}
- *
- * Returns:
- * {Object} An attributes object.
- */
- parseAttributes: function(node) {
- var attributes = {};
-
- // Extended Data is parsed first.
- var edNodes = node.getElementsByTagName("ExtendedData");
- if (edNodes.length) {
- attributes = this.parseExtendedData(edNodes[0]);
- }
-
- // assume attribute nodes are type 1 children with a type 3 or 4 child
- var child, grandchildren, grandchild;
- var children = node.childNodes;
- for(var i=0, len=children.length; i<len; ++i) {
- child = children[i];
- if(child.nodeType == 1) {
- grandchildren = child.childNodes;
- if(grandchildren.length >= 1 && grandchildren.length <= 3) {
- var grandchild;
- switch (grandchildren.length) {
- case 1:
- grandchild = grandchildren[0];
- break;
- case 2:
- var c1 = grandchildren[0];
- var c2 = grandchildren[1];
- grandchild = (c1.nodeType == 3 || c1.nodeType == 4) ?
- c1 : c2;
- break;
- case 3:
- default:
- grandchild = grandchildren[1];
- break;
- }
- if(grandchild.nodeType == 3 || grandchild.nodeType == 4) {
- var name = (child.prefix) ?
- child.nodeName.split(":")[1] :
- child.nodeName;
- var value = OpenLayers.Util.getXmlNodeValue(grandchild);
- if (value) {
- value = value.replace(this.regExes.trimSpace, "");
- attributes[name] = value;
- }
- }
- }
- }
- }
- return attributes;
- },
- /**
- * Method: parseExtendedData
- * Parse ExtendedData from KML. Limited support for schemas/datatypes.
- * See http://code.google.com/apis/kml/documentation/kmlreference.html#extendeddata
- * for more information on extendeddata.
- */
- parseExtendedData: function(node) {
- var attributes = {};
- var i, len, data, key;
- var dataNodes = node.getElementsByTagName("Data");
- for (i = 0, len = dataNodes.length; i < len; i++) {
- data = dataNodes[i];
- key = data.getAttribute("name");
- var ed = {};
- var valueNode = data.getElementsByTagName("value");
- if (valueNode.length) {
- ed['value'] = this.getChildValue(valueNode[0]);
- }
- if (this.kvpAttributes) {
- attributes[key] = ed['value'];
- } else {
- var nameNode = data.getElementsByTagName("displayName");
- if (nameNode.length) {
- ed['displayName'] = this.getChildValue(nameNode[0]);
- }
- attributes[key] = ed;
- }
- }
- var simpleDataNodes = node.getElementsByTagName("SimpleData");
- for (i = 0, len = simpleDataNodes.length; i < len; i++) {
- var ed = {};
- data = simpleDataNodes[i];
- key = data.getAttribute("name");
- ed['value'] = this.getChildValue(data);
- if (this.kvpAttributes) {
- attributes[key] = ed['value'];
- } else {
- ed['displayName'] = key;
- attributes[key] = ed;
- }
- }
-
- return attributes;
- },
-
- /**
- * Method: parseProperty
- * Convenience method to find a node and return its value
- *
- * Parameters:
- * xmlNode - {<DOMElement>}
- * namespace - {String} namespace of the node to find
- * tagName - {String} name of the property to parse
- *
- * Returns:
- * {String} The value for the requested property (defaults to null)
- */
- parseProperty: function(xmlNode, namespace, tagName) {
- var value;
- var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName);
- try {
- value = OpenLayers.Util.getXmlNodeValue(nodeList[0]);
- } catch(e) {
- value = null;
- }
-
- return value;
- },
- /**
- * APIMethod: write
- * Accept Feature Collection, and return a string.
- *
- * Parameters:
- * features - {Array(<OpenLayers.Feature.Vector>)} An array of features.
- *
- * Returns:
- * {String} A KML string.
- */
- write: function(features) {
- if(!(OpenLayers.Util.isArray(features))) {
- features = [features];
- }
- var kml = this.createElementNS(this.kmlns, "kml");
- var folder = this.createFolderXML();
- for(var i=0, len=features.length; i<len; ++i) {
- folder.appendChild(this.createPlacemarkXML(features[i]));
- }
- kml.appendChild(folder);
- return OpenLayers.Format.XML.prototype.write.apply(this, [kml]);
- },
- /**
- * Method: createFolderXML
- * Creates and returns a KML folder node
- *
- * Returns:
- * {DOMElement}
- */
- createFolderXML: function() {
- // Folder
- var folder = this.createElementNS(this.kmlns, "Folder");
- // Folder name
- if (this.foldersName) {
- var folderName = this.createElementNS(this.kmlns, "name");
- var folderNameText = this.createTextNode(this.foldersName);
- folderName.appendChild(folderNameText);
- folder.appendChild(folderName);
- }
- // Folder description
- if (this.foldersDesc) {
- var folderDesc = this.createElementNS(this.kmlns, "description");
- var folderDescText = this.createTextNode(this.foldersDesc);
- folderDesc.appendChild(folderDescText);
- folder.appendChild(folderDesc);
- }
- return folder;
- },
- /**
- * Method: createPlacemarkXML
- * Creates and returns a KML placemark node representing the given feature.
- *
- * Parameters:
- * feature - {<OpenLayers.Feature.Vector>}
- *
- * Returns:
- * {DOMElement}
- */
- createPlacemarkXML: function(feature) {
- // Placemark name
- var placemarkName = this.createElementNS(this.kmlns, "name");
- var label = (feature.style && feature.style.label) ? feature.style.label : feature.id;
- var name = feature.attributes.name || label;
- placemarkName.appendChild(this.createTextNode(name));
- // Placemark description
- var placemarkDesc = this.createElementNS(this.kmlns, "description");
- var desc = feature.attributes.description || this.placemarksDesc;
- placemarkDesc.appendChild(this.createTextNode(desc));
-
- // Placemark
- var placemarkNode = this.createElementNS(this.kmlns, "Placemark");
- if(feature.fid != null) {
- placemarkNode.setAttribute("id", feature.fid);
- }
- placemarkNode.appendChild(placemarkName);
- placemarkNode.appendChild(placemarkDesc);
- // Geometry node (Point, LineString, etc. nodes)
- var geometryNode = this.buildGeometryNode(feature.geometry);
- placemarkNode.appendChild(geometryNode);
-
- // output attributes as extendedData
- if (feature.attributes) {
- var edNode = this.buildExtendedData(feature.attributes);
- if (edNode) {
- placemarkNode.appendChild(edNode);
- }
- }
-
- return placemarkNode;
- },
- /**
- * Method: buildGeometryNode
- * Builds and returns a KML geometry node with the given geometry.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry>}
- *
- * Returns:
- * {DOMElement}
- */
- buildGeometryNode: function(geometry) {
- var className = geometry.CLASS_NAME;
- var type = className.substring(className.lastIndexOf(".") + 1);
- var builder = this.buildGeometry[type.toLowerCase()];
- var node = null;
- if(builder) {
- node = builder.apply(this, [geometry]);
- }
- return node;
- },
- /**
- * Property: buildGeometry
- * Object containing methods to do the actual geometry node building
- * based on geometry type.
- */
- buildGeometry: {
- // TBD: Anybody care about namespace aliases here (these nodes have
- // no prefixes)?
- /**
- * Method: buildGeometry.point
- * Given an OpenLayers point geometry, create a KML point.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
- *
- * Returns:
- * {DOMElement} A KML point node.
- */
- point: function(geometry) {
- var kml = this.createElementNS(this.kmlns, "Point");
- kml.appendChild(this.buildCoordinatesNode(geometry));
- return kml;
- },
-
- /**
- * Method: buildGeometry.multipoint
- * Given an OpenLayers multipoint geometry, create a KML
- * GeometryCollection.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry.Point>} A multipoint geometry.
- *
- * Returns:
- * {DOMElement} A KML GeometryCollection node.
- */
- multipoint: function(geometry) {
- return this.buildGeometry.collection.apply(this, [geometry]);
- },
- /**
- * Method: buildGeometry.linestring
- * Given an OpenLayers linestring geometry, create a KML linestring.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
- *
- * Returns:
- * {DOMElement} A KML linestring node.
- */
- linestring: function(geometry) {
- var kml = this.createElementNS(this.kmlns, "LineString");
- kml.appendChild(this.buildCoordinatesNode(geometry));
- return kml;
- },
-
- /**
- * Method: buildGeometry.multilinestring
- * Given an OpenLayers multilinestring geometry, create a KML
- * GeometryCollection.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry.Point>} A multilinestring geometry.
- *
- * Returns:
- * {DOMElement} A KML GeometryCollection node.
- */
- multilinestring: function(geometry) {
- return this.buildGeometry.collection.apply(this, [geometry]);
- },
- /**
- * Method: buildGeometry.linearring
- * Given an OpenLayers linearring geometry, create a KML linearring.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
- *
- * Returns:
- * {DOMElement} A KML linearring node.
- */
- linearring: function(geometry) {
- var kml = this.createElementNS(this.kmlns, "LinearRing");
- kml.appendChild(this.buildCoordinatesNode(geometry));
- return kml;
- },
-
- /**
- * Method: buildGeometry.polygon
- * Given an OpenLayers polygon geometry, create a KML polygon.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
- *
- * Returns:
- * {DOMElement} A KML polygon node.
- */
- polygon: function(geometry) {
- var kml = this.createElementNS(this.kmlns, "Polygon");
- var rings = geometry.components;
- var ringMember, ringGeom, type;
- for(var i=0, len=rings.length; i<len; ++i) {
- type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
- ringMember = this.createElementNS(this.kmlns, type);
- ringGeom = this.buildGeometry.linearring.apply(this,
- [rings[i]]);
- ringMember.appendChild(ringGeom);
- kml.appendChild(ringMember);
- }
- return kml;
- },
-
- /**
- * Method: buildGeometry.multipolygon
- * Given an OpenLayers multipolygon geometry, create a KML
- * GeometryCollection.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry.Point>} A multipolygon geometry.
- *
- * Returns:
- * {DOMElement} A KML GeometryCollection node.
- */
- multipolygon: function(geometry) {
- return this.buildGeometry.collection.apply(this, [geometry]);
- },
- /**
- * Method: buildGeometry.collection
- * Given an OpenLayers geometry collection, create a KML MultiGeometry.
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry.Collection>} A geometry collection.
- *
- * Returns:
- * {DOMElement} A KML MultiGeometry node.
- */
- collection: function(geometry) {
- var kml = this.createElementNS(this.kmlns, "MultiGeometry");
- var child;
- for(var i=0, len=geometry.components.length; i<len; ++i) {
- child = this.buildGeometryNode.apply(this,
- [geometry.components[i]]);
- if(child) {
- kml.appendChild(child);
- }
- }
- return kml;
- }
- },
- /**
- * Method: buildCoordinatesNode
- * Builds and returns the KML coordinates node with the given geometry
- * <coordinates>...</coordinates>
- *
- * Parameters:
- * geometry - {<OpenLayers.Geometry>}
- *
- * Returns:
- * {DOMElement}
- */
- buildCoordinatesNode: function(geometry) {
- var coordinatesNode = this.createElementNS(this.kmlns, "coordinates");
-
- var path;
- var points = geometry.components;
- if(points) {
- // LineString or LinearRing
- var point;
- var numPoints = points.length;
- var parts = new Array(numPoints);
- for(var i=0; i<numPoints; ++i) {
- point = points[i];
- parts[i] = this.buildCoordinates(point);
- }
- path = parts.join(" ");
- } else {
- // Point
- path = this.buildCoordinates(geometry);
- }
-
- var txtNode = this.createTextNode(path);
- coordinatesNode.appendChild(txtNode);
-
- return coordinatesNode;
- },
-
- /**
- * Method: buildCoordinates
- *
- * Parameters:
- * point - {<OpenLayers.Geometry.Point>}
- *
- * Returns
- * {String} a coordinate pair
- */
- buildCoordinates: function(point) {
- if (this.internalProjection && this.externalProjection) {
- point = point.clone();
- point.transform(this.internalProjection,
- this.externalProjection);
- }
- return point.x + "," + point.y;
- },
- /**
- * Method: buildExtendedData
- *
- * Parameters:
- * attributes - {Object}
- *
- * Returns
- * {DOMElement} A KML ExtendedData node or {null} if no attributes.
- */
- buildExtendedData: function(attributes) {
- var extendedData = this.createElementNS(this.kmlns, "ExtendedData");
- for (var attributeName in attributes) {
- // empty, name, description, styleUrl attributes ignored
- if (attributes[attributeName] && attributeName != "name" && attributeName != "description" && attributeName != "styleUrl") {
- var data = this.createElementNS(this.kmlns, "Data");
- data.setAttribute("name", attributeName);
- var value = this.createElementNS(this.kmlns, "value");
- if (typeof attributes[attributeName] == "object") {
- // cater for object attributes with 'value' properties
- // other object properties will output an empty node
- if (attributes[attributeName].value) {
- value.appendChild(this.createTextNode(attributes[attributeName].value));
- }
- if (attributes[attributeName].displayName) {
- var displayName = this.createElementNS(this.kmlns, "displayName");
- // displayName always written as CDATA
- displayName.appendChild(this.getXMLDoc().createCDATASection(attributes[attributeName].displayName));
- data.appendChild(displayName);
- }
- } else {
- value.appendChild(this.createTextNode(attributes[attributeName]));
- }
- data.appendChild(value);
- extendedData.appendChild(data);
- }
- }
- if (this.isSimpleContent(extendedData)) {
- return null;
- } else {
- return extendedData;
- }
- },
-
- CLASS_NAME: "OpenLayers.Format.KML"
- });
|