|
/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for |
|
* full list of contributors). Published under the Clear BSD license. |
|
* See http://svn.openlayers.org/trunk/openlayers/license.txt for the |
|
* full text of the license. */ |
|
|
|
/** |
|
* @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/Console.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 |
|
*/ |
|
extractAttributes: true, |
|
|
|
/** |
|
* 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 |
|
* Looks for <Style> nodes in the data and parses them |
|
* Also parses <StyleMap> nodes, but only uses the 'normal' key |
|
* |
|
* 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, 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; |