goog.provide('ol.parser.KML'); goog.require('goog.array'); goog.require('goog.async.Deferred'); goog.require('goog.async.DeferredList'); goog.require('goog.date'); goog.require('goog.dispose'); goog.require('goog.dom.xml'); goog.require('goog.events'); goog.require('goog.net.EventType'); goog.require('goog.net.XhrIo'); goog.require('goog.object'); goog.require('goog.string'); goog.require('ol.Feature'); goog.require('ol.geom.AbstractCollection'); goog.require('ol.geom.GeometryCollection'); goog.require('ol.geom.GeometryType'); goog.require('ol.geom.LineString'); goog.require('ol.geom.MultiLineString'); goog.require('ol.geom.MultiPoint'); goog.require('ol.geom.MultiPolygon'); goog.require('ol.geom.Point'); goog.require('ol.geom.Polygon'); goog.require('ol.geom.SharedVertices'); goog.require('ol.parser.AsyncObjectFeatureParser'); goog.require('ol.parser.AsyncStringFeatureParser'); goog.require('ol.parser.DomFeatureParser'); goog.require('ol.parser.ReadFeaturesOptions'); goog.require('ol.parser.StringFeatureParser'); goog.require('ol.parser.XML'); goog.require('ol.style.Icon'); goog.require('ol.style.Line'); goog.require('ol.style.LineLiteral'); goog.require('ol.style.Polygon'); goog.require('ol.style.PolygonLiteral'); /** * @constructor * @implements {ol.parser.DomFeatureParser} * @implements {ol.parser.StringFeatureParser} * @implements {ol.parser.AsyncObjectFeatureParser} * @implements {ol.parser.AsyncStringFeatureParser} * @param {ol.parser.KMLOptions=} opt_options Optional configuration object. * @extends {ol.parser.XML} */ ol.parser.KML = function(opt_options) { var options = /** @type {ol.parser.KMLOptions} */ (goog.isDef(opt_options) ? opt_options : {}); this.extractAttributes = goog.isDef(options.extractAttributes) ? options.extractAttributes : true; this.extractStyles = goog.isDef(options.extractStyles) ? options.extractStyles : false; this.schemaLocation = 'http://www.opengis.net/kml/2.2 ' + 'http://schemas.opengis.net/kml/2.2.0/ogckml22.xsd'; // TODO re-evaluate once shared structures support 3D this.dimension = goog.isDef(options.dimension) ? options.dimension : 3; this.maxDepth = goog.isDef(options.maxDepth) ? options.maxDepth : 0; this.trackAttributes = goog.isDef(options.trackAttributes) ? options.trackAttributes : null; this.defaultNamespaceURI = 'http://www.opengis.net/kml/2.2'; this.readers = { 'http://www.opengis.net/kml/2.2': { 'kml': function(node, obj) { if (!goog.isDef(obj.features)) { obj.features = []; } if (!goog.isDef(obj.links)) { obj.links = []; } this.readChildNodes(node, obj); }, 'Document': function(node, obj) { this.readChildNodes(node, obj); }, '*': function(node, obj) { if (this.extractAttributes === true) { var len = node.childNodes.length; if ((len === 1 || len === 2) && (node.firstChild.nodeType === 3 || node.firstChild.nodeType === 4)) { var readers = this.readers[this.defaultNamespaceURI]; readers['_attribute'].apply(this, arguments); } } }, 'NetworkLink': function(node, obj) { var link = {}; this.readChildNodes(node, link); obj.links.push(link); }, 'Link': function(node, obj) { this.readChildNodes(node, obj); }, '_attribute': function(node, obj) { var local = node.localName || node.nodeName.split(':').pop(); var value = this.getChildValue(node); if (obj.properties) { obj.properties[local] = value.replace(this.regExes.trimSpace, ''); } else { obj[local] = value.replace(this.regExes.trimSpace, ''); } }, 'Placemark': function(node, obj) { var container = {properties: {}}; var sharedVertices, callback; var id = node.getAttribute('id'); this.readChildNodes(node, container); if (goog.isDef(container.track)) { var track = container.track, j, jj; delete container.track; for (var i = 0, ii = track.whens.length; i < ii; ++i) { if (this.trackAttributes) { for (j = 0, jj = this.trackAttributes.length; j < jj; ++j) { var name = this.trackAttributes[j]; container.properties[name] = track.attributes[name][i]; } } container.properties['when'] = track.whens[i]; if (goog.isDef(track.angles[i])) { container.properties['heading'] = parseFloat(track.angles[i][0]); container.properties['tilt'] = parseFloat(track.angles[i][1]); container.properties['roll'] = parseFloat(track.angles[i][2]); } if (track.points[i].coordinates.length === 3) { container.properties['altitude'] = track.points[i].coordinates[2]; } var feature = new ol.Feature(container.properties); if (!goog.isNull(id)) { feature.setFeatureId(id); } var geom = track.points[i]; if (geom) { sharedVertices = undefined; if (this.readFeaturesOptions_) { callback = this.readFeaturesOptions_.callback; if (callback) { sharedVertices = callback(feature, geom.type); } } var geometry = this.createGeometry_({geometry: geom}, sharedVertices); if (goog.isDef(geometry)) { feature.setGeometry(geometry); } } obj.features.push(feature); } } else if (goog.isDef(container.geometry)) { var styleUrl = container.properties['styleUrl']; if (goog.isDef(styleUrl)) { if (!goog.string.startsWith(styleUrl, '#')) { obj.links.push({href: styleUrl}); } } feature = new ol.Feature(container.properties); if (!goog.isNull(id)) { feature.setFeatureId(id); } if (container.geometry) { sharedVertices = undefined; if (this.readFeaturesOptions_) { callback = this.readFeaturesOptions_.callback; if (callback) { sharedVertices = callback(feature, container.geometry.type); } } geometry = this.createGeometry_(container, sharedVertices); if (goog.isDef(geometry)) { feature.setGeometry(geometry); } } var symbolizers = undefined; if (goog.isDef(container.styles)) { symbolizers = container.styles[0].symbolizers; } this.applyStyle_(feature, obj['styles'], symbolizers); obj.features.push(feature); } }, 'MultiGeometry': function(node, container) { var parts = []; this.readChildNodes(node, parts); var buckets = goog.array.bucket(parts, function(val) { return val.type; }); var obj = {}; if (goog.object.getCount(buckets) === 1) { // homogeneous collection var type = goog.object.getAnyKey(buckets); switch (type) { case ol.geom.GeometryType.POINT: obj.geometry = { type: ol.geom.GeometryType.MULTIPOINT, parts: parts }; break; case ol.geom.GeometryType.LINESTRING: obj.geometry = { type: ol.geom.GeometryType.MULTILINESTRING, parts: parts }; break; case ol.geom.GeometryType.POLYGON: obj.geometry = { type: ol.geom.GeometryType.MULTIPOLYGON, parts: parts }; break; default: break; } } else { // mixed collection obj.geometry = { type: ol.geom.GeometryType.GEOMETRYCOLLECTION, parts: parts }; } if (goog.isArray(container)) { // MultiGeometry nested inside another container.push(obj.geometry); } else { container.geometry = obj.geometry; } }, 'Point': function(node, container) { var coordinates = []; this.readChildNodes(node, coordinates); var point = { type: ol.geom.GeometryType.POINT, coordinates: coordinates[0][0] }; // in the case of a multi geometry this is parts if (goog.isArray(container)) { container.push(point); } else { container.geometry = point; } }, 'Polygon': function(node, container) { var coordinates = []; this.readChildNodes(node, coordinates); var polygon = { type: ol.geom.GeometryType.POLYGON, coordinates: coordinates }; // in the case of a multi geometry this is parts if (goog.isArray(container)) { container.push(polygon); } else { container.geometry = polygon; } }, 'LineString': function(node, container) { var coordinates = []; this.readChildNodes(node, coordinates); var linestring = { type: ol.geom.GeometryType.LINESTRING, coordinates: coordinates[0] }; // in the case of a multi geometry this is parts if (goog.isArray(container)) { container.push(linestring); } else { container.geometry = linestring; } }, 'outerBoundaryIs': function(node, coordinates) { this.readChildNodes(node, coordinates); }, 'LinearRing': function(node, coordinates) { this.readChildNodes(node, coordinates); }, 'coordinates': function(node, coordinates) { var coordstr = this.getChildValue(node); var reg = this.regExes; var coords = coordstr.replace(reg.trimSpace, '').split(reg.splitSpace); var coordArray = []; for (var i = 0, ii = coords.length; i < ii; i++) { var array = coords[i].replace(reg.removeSpace, '').split(','); var pair = []; var jj = Math.min(array.length, this.dimension); for (var j = 0; j < jj; j++) { pair.push(parseFloat(array[j])); } coordArray.push(pair); } coordinates.push(coordArray); }, 'innerBoundaryIs': function(node, coordinates) { this.readChildNodes(node, coordinates); }, 'Folder': function(node, obj) { this.readChildNodes(node, obj); }, 'ExtendedData': function(node, container) { this.readChildNodes(node, container.properties); }, 'SchemaData': function(node, attributes) { this.readChildNodes(node, attributes); }, 'SimpleData': function(node, attributes) { attributes[node.getAttribute('name')] = this.getChildValue(node); }, 'Data': function(node, attributes) { var data = {}; this.readChildNodes(node, data); attributes[node.getAttribute('name')] = data['value']; }, 'when': function(node, container) { var value = this.getChildValue(node); var split1 = value.split('T'); if (split1.length === 2) { var split2 = split1[1].split('-'); if (split2.length === 2) { value += ':00'; } } container.whens.push(goog.date.fromIsoString(value).date_); }, '_trackPointAttribute': function(node, container) { var name = node.nodeName.split(':').pop(); container.attributes[name].push(this.getChildValue(node)); }, 'Style': function(node, obj) { if (this.extractStyles === true) { if (!obj['styles']) { obj['styles'] = []; } var style = {'symbolizers': [], 'ids': []}; var id = node.getAttribute('id'); if (!goog.isNull(id)) { style['id'] = id; } this.readChildNodes(node, style); obj['styles'].push(style); } }, 'LineStyle': function(node, obj) { var symbolizer = {}; this.readChildNodes(node, symbolizer); if (symbolizer.color) { symbolizer.strokeColor = symbolizer.color.color; symbolizer.strokeOpacity = symbolizer.color.opacity; } if (symbolizer.width) { symbolizer.strokeWidth = parseFloat(symbolizer.width); } delete symbolizer.color; delete symbolizer.width; obj['ids'].push(node.getAttribute('id')); obj['symbolizers'].push(new ol.style.Line(symbolizer)); }, 'PolyStyle': function(node, obj) { var style = {}; // from KML var symbolizer = {}; // for ol.style.Polygon this.readChildNodes(node, style); // check if poly has fill if (!(style.fill === '0' || style.fill === 'false')) { if (style.color) { symbolizer.fillColor = style.color.color; symbolizer.fillOpacity = style.color.opacity; } else { // KML defaults symbolizer.fillColor = '#ffffff'; symbolizer.fillOpacity = 1; } } // check if poly has stroke if (!(style.outline === '0' || style.outline === 'false')) { if (style.color) { symbolizer.strokeColor = style.color.color; symbolizer.strokeOpacity = style.color.opacity; } else { // KML defaults symbolizer.strokeColor = '#ffffff'; symbolizer.strokeOpacity = 1; } } obj['ids'].push(node.getAttribute('id')); obj['symbolizers'].push(new ol.style.Polygon(symbolizer)); }, 'fill': function(node, obj) { obj.fill = this.getChildValue(node); }, 'outline': function(node, obj) { obj.outline = this.getChildValue(node); }, 'scale': function(node, obj) { obj.scale = parseFloat(this.getChildValue(node)); }, 'Icon': function(node, obj) { obj.icon = {}; this.readChildNodes(node, obj.icon); }, 'href': function(node, obj) { obj.href = this.getChildValue(node); }, 'w': function(node, obj) { obj.w = this.getChildValue(node); }, 'h': function(node, obj) { obj.h = this.getChildValue(node); }, 'x': function(node, obj) { obj.x = this.getChildValue(node); }, 'y': function(node, obj) { obj.y = this.getChildValue(node); }, 'hotSpot': function(node, obj) { obj.hotSpot = { x: parseFloat(node.getAttribute('x')), y: parseFloat(node.getAttribute('y')), xunits: node.getAttribute('xunits'), yunits: node.getAttribute('yunits') }; }, 'IconStyle': function(node, obj) { var symbolizer = {}; this.readChildNodes(node, symbolizer); var scale = symbolizer.scale || 1; // set default width and height of icon var width = 32 * scale; var height = 32 * scale; var x, y; delete symbolizer.scale; if (goog.isDef(symbolizer.icon)) { var href = symbolizer.icon.href; if (goog.isDef(href)) { var w = symbolizer.icon.w; var h = symbolizer.icon.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 (goog.string.startsWith(href, google) && !goog.isDef(w) && !goog.isDef(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, 10) * scale; } if (h) { height = parseInt(h, 10) * 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]; x = symbolizer.icon.x; y = symbolizer.icon.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; } symbolizer.opacity = 1; symbolizer.url = href; } } if (goog.isDef(symbolizer.hotSpot)) { x = symbolizer.hotSpot.x; y = symbolizer.hotSpot.y; var xUnits = symbolizer.hotSpot.xunits, yUnits = symbolizer.hotSpot.yunits; if (xUnits === 'pixels') { symbolizer.graphicXOffset = -x * scale; } else if (xUnits === 'insetPixels') { symbolizer.graphicXOffset = -width + (x * scale); } else if (xUnits === 'fraction') { symbolizer.graphicXOffset = -width * x; } if (yUnits == 'pixels') { symbolizer.graphicYOffset = -height + (y * scale) + 1; } else if (yUnits == 'insetPixels') { symbolizer.graphicYOffset = -(y * scale) + 1; } else if (yUnits == 'fraction') { symbolizer.graphicYOffset = -height * (1 - y) + 1; } } symbolizer.width = width; symbolizer.height = height; delete symbolizer.scale; delete symbolizer.icon; delete symbolizer.hotSpot; obj['ids'].push(node.getAttribute('id')); obj['symbolizers'].push(new ol.style.Icon(symbolizer)); }, 'color': function(node, obj) { var kmlColor = this.getChildValue(node); if (kmlColor) { var matches = kmlColor.match(this.regExes.kmlColor); if (matches) { obj.color = { color: '#' + matches[4] + matches[3] + matches[2], opacity: parseInt(matches[1], 16) / 255 }; } } }, 'width': function(node, obj) { obj.width = this.getChildValue(node); } }, 'http://www.google.com/kml/ext/2.2': { 'Track': function(node, container) { container.track = { whens: [], points: [], angles: [] }; if (this.trackAttributes) { var name; container.track.attributes = {}; for (var i = 0, ii = this.trackAttributes.length; i < ii; ++i) { name = this.trackAttributes[i]; container.track.attributes[name] = []; var readers = this.readers[this.defaultNamespaceURI]; if (!(name in readers)) { readers[name] = readers['_trackPointAttribute']; } } } this.readChildNodes(node, container.track); if (container.track.whens.length !== container.track.points.length) { throw new Error('gx:Track with unequal number of when (' + container.track.whens.length + ') and gx:coord (' + container.track.points.length + ') elements.'); } var hasAngles = container.track.angles.length > 0; if (hasAngles && container.track.whens.length !== container.track.angles.length) { throw new Error('gx:Track with unequal number of when (' + container.track.whens.length + ') and gx:angles (' + container.track.angles.length + ') elements.'); } }, 'coord': function(node, container) { var str = this.getChildValue(node); var coords = str.replace(this.regExes.trimSpace, '').split(/\s+/); for (var i = 0, ii = this.dimension; i < ii; ++i) { coords[i] = parseFloat(coords[i]); } var point = { type: ol.geom.GeometryType.POINT, coordinates: coords }; 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); } } }; this.writers = { 'http://www.opengis.net/kml/2.2': { 'kml': function(options) { var node = this.createElementNS('kml'); this.writeNode('Document', options, null, node); return node; }, 'Document': function(options) { var node = this.createElementNS('Document'); for (var key in options) { if (options.hasOwnProperty(key) && goog.isString(options[key])) { var child = this.createElementNS(key); child.appendChild(this.createTextNode(options[key])); node.appendChild(child); } } var i, ii; if (goog.isDef(options.styles)) { for (i = 0, ii = options.styles.length; i < ii; ++i) { this.writeNode('_style', options.styles[i], null, node); } } for (i = 0, ii = options.features.length; i < ii; ++i) { this.writeNode('_feature', options.features[i], null, node); } return node; }, '_style': function(style) { var node = this.createElementNS('Style'); if (goog.isDef(style.id)) { this.setAttributeNS(node, null, 'id', style.id); } for (var i = 0, ii = style.symbolizers.length; i < ii; ++i) { this.writeNode('_symbolizer', { symbolizer: style.symbolizers[i], id: style.ids ? style.ids[i] : undefined }, null, node); } return node; }, '_symbolizer': function(symbolizerObj) { var symbolizer = symbolizerObj.symbolizer; if (symbolizer instanceof ol.style.Icon) { return this.writeNode('IconStyle', symbolizerObj); } else if (symbolizer instanceof ol.style.Line || symbolizer instanceof ol.style.LineLiteral) { return this.writeNode('LineStyle', symbolizerObj); } else if (symbolizer instanceof ol.style.Polygon || symbolizer instanceof ol.style.PolygonLiteral) { return this.writeNode('PolyStyle', symbolizerObj); } }, 'PolyStyle': function(symbolizerObj) { /** * There is not a 1:1 mapping between KML PolyStyle and * ol.style.Polygon. In KML, if a PolyStyle has 1 * then the "current" LineStyle is used to stroke the polygon. */ var node = this.createElementNS('PolyStyle'); if (symbolizerObj.id) { this.setAttributeNS(node, null, 'id', symbolizerObj.id); } var symbolizer = symbolizerObj.symbolizer; var literal = symbolizer instanceof ol.style.PolygonLiteral ? symbolizer : symbolizer.createLiteral(); var color, opacity; if (literal.fillOpacity !== 0) { this.writeNode('fill', '1', null, node); color = literal.fillColor; opacity = literal.fillOpacity; } else { this.writeNode('fill', '0', null, node); } if (literal.strokeOpacity) { this.writeNode('outline', '1', null, node); color = color || literal.strokeColor; opacity = opacity || literal.strokeOpacity; } else { this.writeNode('outline', '0', null, node); } if (color && opacity) { this.writeNode('color', { color: color.substring(1), opacity: opacity }, null, node); } return node; }, 'fill': function(fill) { var node = this.createElementNS('fill'); node.appendChild(this.createTextNode(fill)); return node; }, 'outline': function(outline) { var node = this.createElementNS('outline'); node.appendChild(this.createTextNode(outline)); return node; }, 'LineStyle': function(symbolizerObj) { var node = this.createElementNS('LineStyle'); if (symbolizerObj.id) { this.setAttributeNS(node, null, 'id', symbolizerObj.id); } var symbolizer = symbolizerObj.symbolizer; var literal = symbolizer instanceof ol.style.LineLiteral ? symbolizer : symbolizer.createLiteral(); this.writeNode('color', { color: literal.strokeColor.substring(1), opacity: literal.strokeOpacity }, null, node); this.writeNode('width', literal.strokeWidth, null, node); return node; }, 'color': function(colorObj) { var color = colorObj.color; var text = (colorObj.opacity * 255).toString(16) + color.substring(4, 6) + color.substring(2, 4) + color.substring(0, 2); var node = this.createElementNS('color'); node.appendChild(this.createTextNode(text)); return node; }, 'width': function(width) { var node = this.createElementNS('width'); node.appendChild(this.createTextNode(width)); return node; }, 'IconStyle': function(symbolizerObj) { var node = this.createElementNS('IconStyle'); this.setAttributeNS(node, null, 'id', symbolizerObj.id); this.writeNode('Icon', symbolizerObj.symbolizer.createLiteral().url, null, node); return node; }, 'Icon': function(url) { var node = this.createElementNS('Icon'); this.writeNode('href', url, null, node); return node; }, 'href': function(url) { var node = this.createElementNS('href'); node.appendChild(this.createTextNode(url)); return node; }, '_feature': function(feature) { var node = this.createElementNS('Placemark'); var fid = feature.getFeatureId(); if (goog.isDef(fid)) { node.setAttribute('id', fid); } this.writeNode('name', feature, null, node); this.writeNode('description', feature, null, node); var literals = feature.getSymbolizerLiterals(); if (goog.isDef(feature.get('styleUrl'))) { this.writeNode('styleUrl', feature, null, node); } else if (goog.isDefAndNotNull(literals)) { // inline style this.writeNode('_style', {symbolizers: literals}, null, node); } this.writeNode('_geometry', feature.getGeometry(), null, node); return node; }, 'name': function(feature) { var name = feature.get('name'); if (goog.isDef(name)) { var node = this.createElementNS('name'); node.appendChild(this.createTextNode(name)); return node; } }, 'description': function(feature) { var description = feature.get('description'); if (goog.isDef(description)) { var node = this.createElementNS('description'); node.appendChild(this.createTextNode(description)); return node; } }, 'styleUrl': function(feature) { var styleUrl = feature.get('styleUrl'); var node = this.createElementNS('styleUrl'); node.appendChild(this.createTextNode(styleUrl)); return node; }, '_geometry': function(geometry) { if (geometry instanceof ol.geom.Point) { return this.writeNode('Point', geometry); } else if (geometry instanceof ol.geom.LineString) { return this.writeNode('LineString', geometry); } else if (geometry instanceof ol.geom.Polygon) { return this.writeNode('Polygon', geometry); } else if (geometry instanceof ol.geom.AbstractCollection) { return this.writeNode('MultiGeometry', geometry); } }, 'MultiGeometry': function(geometry) { var node = this.createElementNS('MultiGeometry'); for (var i = 0, ii = geometry.components.length; i < ii; ++i) { this.writeNode('_geometry', geometry.components[i], null, node); } return node; }, 'Point': function(geometry) { var node = this.createElementNS('Point'); var coords = geometry.getCoordinates(); this.writeNode('coordinates', [coords], null, node); return node; }, 'LineString': function(geometry) { var node = this.createElementNS('LineString'); this.writeNode('coordinates', geometry.getCoordinates(), null, node); return node; }, 'Polygon': function(geometry) { /** * KML doesn't specify the winding order of coordinates in linear * rings. So we keep them as they are in the geometries. */ var node = this.createElementNS('Polygon'); var coordinates = geometry.getCoordinates(); this.writeNode('outerBoundaryIs', coordinates[0], null, node); for (var i = 1, ii = coordinates.length; i < ii; ++i) { this.writeNode('innerBoundaryIs', coordinates[i], null, node); } return node; }, 'outerBoundaryIs': function(vertexArray) { var node = this.createElementNS('outerBoundaryIs'); this.writeNode('LinearRing', vertexArray, null, node); return node; }, 'innerBoundaryIs': function(vertexArray) { var node = this.createElementNS('innerBoundaryIs'); this.writeNode('LinearRing', vertexArray, null, node); return node; }, 'LinearRing': function(vertexArray) { var node = this.createElementNS('LinearRing'); this.writeNode('coordinates', vertexArray, null, node); return node; }, 'coordinates': function(vertexArray) { var node = this.createElementNS('coordinates'); var coordstr = ''; for (var i = 0, ii = vertexArray.length; i < ii; ++i) { for (var j = 0, jj = vertexArray[i].length; j < jj; ++j) { coordstr += vertexArray[i][j]; if (j < jj - 1) { coordstr += ','; } } if (i < ii - 1) { coordstr += ' '; } } node.appendChild(this.createTextNode(coordstr)); return node; } } }; goog.base(this); goog.object.extend(this.regExes, { kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/), kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/), straightBracket: (/\$\[(.*?)\]/g) }); }; goog.inherits(ol.parser.KML, ol.parser.XML); /** * @param {Object} obj Object representing features. * @param {function(ol.parser.ReadFeaturesResult)} callback Callback which is * called after parsing. * @param {ol.parser.ReadFeaturesOptions=} opt_options Feature reading options. */ ol.parser.KML.prototype.readFeaturesFromObjectAsync = function(obj, callback, opt_options) { this.readFeaturesOptions_ = opt_options; this.read(obj, callback); }; /** * @param {string} str String data. * @param {function(ol.parser.ReadFeaturesResult)} * callback Callback which is called after parsing. * @param {ol.parser.ReadFeaturesOptions=} opt_options Feature reading options. */ ol.parser.KML.prototype.readFeaturesFromStringAsync = function(str, callback, opt_options) { this.readFeaturesOptions_ = opt_options; this.read(str, callback); }; /** * Parse a KML document provided as a string. * @param {string} str KML document. * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options. * @return {ol.parser.ReadFeaturesResult} Features and metadata. */ ol.parser.KML.prototype.readFeaturesFromString = function(str, opt_options) { this.readFeaturesOptions_ = opt_options; return /** @type {ol.parser.ReadFeaturesResult} */ (this.read(str)); }; /** * Parse a KML document provided as a DOM structure. * @param {Element|Document} node Document or element node. * @param {ol.parser.ReadFeaturesOptions=} opt_options Feature reading options. * @return {ol.parser.ReadFeaturesResult} Features and metadata. */ ol.parser.KML.prototype.readFeaturesFromNode = function(node, opt_options) { this.readFeaturesOptions_ = opt_options; return /** @type {ol.parser.ReadFeaturesResult} */ (this.read(node)); }; /** * @param {Object} obj Object representing features. * @param {ol.parser.ReadFeaturesOptions=} opt_options Feature reading options. * @return {ol.parser.ReadFeaturesResult} Features and metadata. */ ol.parser.KML.prototype.readFeaturesFromObject = function(obj, opt_options) { this.readFeaturesOptions_ = opt_options; return /** @type {ol.parser.ReadFeaturesResult} */ (this.read(obj)); }; /** * @param {Array} deferreds List of deferred instances. * @param {Object} obj The returned object from the parser. * @param {Function} done A callback for when all links have been retrieved. */ ol.parser.KML.prototype.parseLinks = function(deferreds, obj, done) { var unvisited; if (this.depth_ < this.maxDepth) { this.depth_++; for (var i = 0, ii = obj.links.length; i < ii; ++i) { var link = obj.links[i]; if (link.visited !== true) { unvisited = true; var deferred = new goog.async.Deferred(); var xhr = new goog.net.XhrIo(); var me = this; goog.events.listen(xhr, goog.net.EventType.COMPLETE, function(e) { if (e.target.isSuccess()) { var data = e.target.getResponseXml() || e.target.getResponseText(); if (goog.isString(data)) { data = goog.dom.xml.loadXml(data); } goog.dispose(e.target); if (data) { if (data.nodeType == 9) { data = data.documentElement; } me.readNode(data, obj); } me.parseLinks(deferreds, obj, done); this.callback(data); } }, false, deferred); deferreds.push(deferred); xhr.send(link.href); link.visited = true; } } } if (unvisited !== true && this.callbackCalled_ !== true) { done.call(this); } }; /** * @param {string|Document|Element|Object} data Data to read. * @param {function(ol.parser.ReadFeaturesResult)=} opt_callback Optional * callback to call when reading is done. If provided, this method will * return undefined. * @return {ol.parser.ReadFeaturesResult|undefined} An object representing the * document if `opt_callback` was not provided. */ ol.parser.KML.prototype.read = function(data, opt_callback) { if (goog.isString(data)) { data = goog.dom.xml.loadXml(data); } if (data && data.nodeType == 9) { data = data.documentElement; } var obj = /** @type {ol.parser.ReadFeaturesResult} */ ({metadata: {projection: 'EPSG:4326'}}); this.readNode(data, obj); if (goog.isDef(opt_callback)) { var deferreds = []; this.depth_ = 0; this.callbackCalled_ = false; this.parseLinks(deferreds, obj, function() { this.callbackCalled_ = true; goog.async.DeferredList.gatherResults(deferreds).addCallbacks( function(datas) { for (var i = 0, ii = obj.features.length; i < ii; ++i) { var feature = obj.features[i]; this.applyStyle_(feature, obj['styles']); } opt_callback.call(null, obj); }, function() { throw new Error('KML: parsing of NetworkLinks failed'); }, this); }); } else { return obj; } }; /** * @private * @param {ol.Feature} feature The feature to apply the style to. * @param {Array} styles The style list to search in. * @param {Array=} opt_symbolizers Optional symbolizers. */ ol.parser.KML.prototype.applyStyle_ = function(feature, styles, opt_symbolizers) { var symbolizers = opt_symbolizers; var i, ii; if (feature.get('styleUrl') && feature.getSymbolizerLiterals() === null) { var styleUrl = feature.get('styleUrl'); styleUrl = styleUrl.substring(styleUrl.indexOf('#') + 1); // look for the style and set in the feature if (goog.isDef(styles)) { for (i = 0, ii = styles.length; i < ii; ++i) { if (styles[i]['id'] === styleUrl) { symbolizers = styles[i]['symbolizers']; break; } } } } if (goog.isDef(symbolizers)) { var geom = feature.getGeometry(); if (geom && geom instanceof ol.geom.LineString) { for (i = 0, ii = symbolizers.length; i < ii; i++) { if (symbolizers[i] instanceof ol.style.Polygon) { symbolizers.splice(i, 1); } } } feature.setSymbolizers(symbolizers); } }; /** * @private * @param {Object} container Geometry container. * @param {ol.geom.SharedVertices=} opt_vertices Shared vertices. * @return {ol.geom.Geometry} The geometry created. */ ol.parser.KML.prototype.createGeometry_ = function(container, opt_vertices) { var geometry = null, coordinates, i, ii; switch (container.geometry.type) { case ol.geom.GeometryType.POINT: geometry = new ol.geom.Point(container.geometry.coordinates, opt_vertices); break; case ol.geom.GeometryType.LINESTRING: geometry = new ol.geom.LineString(container.geometry.coordinates, opt_vertices); break; case ol.geom.GeometryType.POLYGON: geometry = new ol.geom.Polygon(container.geometry.coordinates, opt_vertices); break; case ol.geom.GeometryType.MULTIPOINT: coordinates = []; for (i = 0, ii = container.geometry.parts.length; i < ii; i++) { coordinates.push(container.geometry.parts[i].coordinates); } geometry = new ol.geom.MultiPoint(coordinates, opt_vertices); break; case ol.geom.GeometryType.MULTILINESTRING: coordinates = []; for (i = 0, ii = container.geometry.parts.length; i < ii; i++) { coordinates.push(container.geometry.parts[i].coordinates); } geometry = new ol.geom.MultiLineString(coordinates, opt_vertices); break; case ol.geom.GeometryType.MULTIPOLYGON: coordinates = []; for (i = 0, ii = container.geometry.parts.length; i < ii; i++) { coordinates.push(container.geometry.parts[i].coordinates); } geometry = new ol.geom.MultiPolygon(coordinates, opt_vertices); break; case ol.geom.GeometryType.GEOMETRYCOLLECTION: var geometries = []; for (i = 0, ii = container.geometry.parts.length; i < ii; i++) { geometries.push(this.createGeometry_({ geometry: container.geometry.parts[i] }, opt_vertices)); } geometry = new ol.geom.GeometryCollection(geometries); break; default: break; } return geometry; }; /** * @param {Object} obj Object structure to write out as XML. * @return {string} An string representing the XML document. */ ol.parser.KML.prototype.write = function(obj) { var root = this.writeNode('kml', obj); this.setAttributeNS( root, 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', this.schemaLocation); return this.serialize(root); };