diff --git a/examples/data/kml/lines.kml b/examples/data/kml/lines.kml
new file mode 100644
index 0000000000..adc8db6c55
--- /dev/null
+++ b/examples/data/kml/lines.kml
@@ -0,0 +1,275 @@
+
+
+
+ KML Samples
+ 1
+ Unleash your creativity with the help of these examples!
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Paths
+ 0
+ Examples of paths. Note that the tessellate tag is by default
+ set to 0. If you want to create tessellated lines, they must be authored
+ (or edited) directly in KML.
+
+ Tessellated
+ 0
+ tag has a value of 1, the line will contour to the underlying terrain]]>
+
+ -112.0822680013139
+ 36.09825589333556
+ 0
+ 2889.145007690472
+ 62.04855796276328
+ 103.8120432044965
+
+
+ 1
+ -112.0814237830345,36.10677870477137,0
+ -112.0870267752693,36.0905099328766,0
+
+
+
+ Untessellated
+ 0
+ tag has a value of 0, the line follow a simple straight-line path from point to point]]>
+
+ -112.0822680013139
+ 36.09825589333556
+ 0
+ 2889.145007690472
+ 62.04855796276328
+ 103.8120432044965
+
+
+ 0
+ -112.080622229595,36.10673460007995,0
+ -112.085242575315,36.09049598612422,0
+
+
+
+ Absolute
+ 0
+ Transparent purple line
+
+ -112.2719329043177
+ 36.08890633450894
+ 0
+ 2569.386744398339
+ 44.60763714063257
+ -106.8161545998597
+
+ #transPurpleLineGreenPoly
+
+ 1
+ absolute
+ -112.265654928602,36.09447672602546,2357
+ -112.2660384528238,36.09342608838671,2357
+ -112.2668139013453,36.09251058776881,2357
+ -112.2677826834445,36.09189827357996,2357
+ -112.2688557510952,36.0913137941187,2357
+ -112.2694810717219,36.0903677207521,2357
+ -112.2695268555611,36.08932171487285,2357
+ -112.2690144567276,36.08850916060472,2357
+ -112.2681528815339,36.08753813597956,2357
+ -112.2670588176031,36.08682685262568,2357
+ -112.2657374587321,36.08646312301303,2357
+
+
+
+ Absolute Extruded
+ 0
+ Transparent green wall with yellow outlines
+
+ -112.2643334742529
+ 36.08563154742419
+ 0
+ 4451.842204068102
+ 44.61038665812578
+ -125.7518698668815
+
+ #yellowLineGreenPoly
+
+ 1
+ 1
+ absolute
+ -112.2550785337791,36.07954952145647,2357
+ -112.2549277039738,36.08117083492122,2357
+ -112.2552505069063,36.08260761307279,2357
+ -112.2564540158376,36.08395660588506,2357
+ -112.2580238976449,36.08511401044813,2357
+ -112.2595218489022,36.08584355239394,2357
+ -112.2608216347552,36.08612634548589,2357
+ -112.262073428656,36.08626019085147,2357
+ -112.2633204928495,36.08621519860091,2357
+ -112.2644963846444,36.08627897945274,2357
+ -112.2656969554589,36.08649599090644,2357
+
+
+
+ Relative
+ 0
+ Black line (10 pixels wide), height tracks terrain
+
+ -112.2580438551384
+ 36.1072674824385
+ 0
+ 2927.61105910266
+ 44.61324882043339
+ 4.947421249553717
+
+ #thickBlackLine
+
+ 1
+ relativeToGround
+ -112.2532845153347,36.09886943729116,645
+ -112.2540466121145,36.09919570465255,645
+ -112.254734666947,36.09984998366178,645
+ -112.255493345654,36.10051310621746,645
+ -112.2563157098468,36.10108441943419,645
+ -112.2568033076439,36.10159722088088,645
+ -112.257494011321,36.10204323542867,645
+ -112.2584106072308,36.10229131995655,645
+ -112.2596588987972,36.10240001286358,645
+ -112.2610581199487,36.10213176873407,645
+ -112.2626285262793,36.10157011437219,645
+
+
+
+ Relative Extruded
+ 0
+ Opaque blue walls with red outline, height tracks terrain
+
+ -112.2683594333433
+ 36.09884362144909
+ 0
+ 2184.193522571467
+ 44.60855445139561
+ -72.24271551768405
+
+ #redLineBluePoly
+
+ 1
+ 1
+ relativeToGround
+ -112.2656634181359,36.09445214722695,630
+ -112.2652238941097,36.09520916122063,630
+ -112.2645079986395,36.09580763864907,630
+ -112.2638827428817,36.09628572284063,630
+ -112.2635746835406,36.09679275951239,630
+ -112.2635711822407,36.09740038871899,630
+ -112.2640296531825,36.09804913435539,630
+ -112.264327720538,36.09880337400301,630
+ -112.2642436562271,36.09963644790288,630
+ -112.2639148687042,36.10055381117246,630
+ -112.2626894973474,36.10149062823369,630
+
+
+
+ Blue Icon
+ Just another blue icon.
+ data/kml/styles.kml#blueIcons
+
+ -112.292238941097,36.09520916122063,630
+
+
+
+
+
diff --git a/examples/data/kml/styles.kml b/examples/data/kml/styles.kml
new file mode 100644
index 0000000000..24350ade44
--- /dev/null
+++ b/examples/data/kml/styles.kml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/kml.html b/examples/kml.html
new file mode 100644
index 0000000000..467dbc1485
--- /dev/null
+++ b/examples/kml.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+ KML example
+
+
+
+
+
+
+
+
+
+
+
+
+
KML example
+
Example of using the KML parser.
+
+
KML
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/kml.js b/examples/kml.js
new file mode 100644
index 0000000000..8662b0a508
--- /dev/null
+++ b/examples/kml.js
@@ -0,0 +1,50 @@
+goog.require('ol.Collection');
+goog.require('ol.Map');
+goog.require('ol.RendererHint');
+goog.require('ol.View2D');
+goog.require('ol.layer.TileLayer');
+goog.require('ol.layer.Vector');
+goog.require('ol.parser.KML');
+goog.require('ol.projection');
+goog.require('ol.source.TiledWMS');
+goog.require('ol.source.Vector');
+
+var raster = new ol.layer.TileLayer({
+ source: new ol.source.TiledWMS({
+ url: 'http://vmap0.tiles.osgeo.org/wms/vmap0',
+ crossOrigin: null,
+ params: {
+ 'LAYERS': 'basic',
+ 'VERSION': '1.1.1',
+ 'FORMAT': 'image/jpeg'
+ }
+ })
+});
+
+var epsg4326 = ol.projection.get('EPSG:4326');
+
+var vector = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ projection: epsg4326
+ })
+});
+
+var map = new ol.Map({
+ layers: new ol.Collection([raster, vector]),
+ renderer: ol.RendererHint.CANVAS,
+ target: 'map',
+ view: new ol.View2D({
+ projection: epsg4326,
+ center: [-112.169, 36.099],
+ zoom: 11
+ })
+});
+
+var kml = new ol.parser.KML({
+ maxDepth: 1, dimension: 2, extractStyles: true, extractAttributes: true});
+
+$.ajax({
+ url: 'data/kml/lines.kml'
+}).done(function(data) {
+ vector.parseFeatures(data, kml, epsg4326);
+});
diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js
index d777199f5e..7fbc99feb2 100644
--- a/src/ol/layer/vectorlayer.js
+++ b/src/ol/layer/vectorlayer.js
@@ -326,37 +326,48 @@ ol.layer.Vector.prototype.parseFeatures = function(data, parser, projection) {
var callback = function(feature, type) {
return lookup[type];
};
+
+ var addFeatures = function(features) {
+ var sourceProjection = this.getSource().getProjection();
+ var transform = ol.projection.getTransform(sourceProjection, projection);
+
+ transform(
+ this.pointVertices_.coordinates,
+ this.pointVertices_.coordinates,
+ this.pointVertices_.getDimension());
+
+ transform(
+ this.lineVertices_.coordinates,
+ this.lineVertices_.coordinates,
+ this.lineVertices_.getDimension());
+
+ transform(
+ this.polygonVertices_.coordinates,
+ this.polygonVertices_.coordinates,
+ this.polygonVertices_.getDimension());
+
+ this.addFeatures(features);
+ };
+
if (goog.isString(data)) {
goog.asserts.assert(goog.isFunction(parser.readFeaturesFromString),
'Expected a parser with readFeaturesFromString method.');
features = parser.readFeaturesFromString(data, {callback: callback});
+ addFeatures.call(this, features);
} else if (goog.isObject(data)) {
- goog.asserts.assert(goog.isFunction(parser.readFeaturesFromObject),
- 'Expected a parser with a readFeaturesFromObject method.');
- features = parser.readFeaturesFromObject(data, {callback: callback});
+ if (goog.isFunction(parser.readFeaturesFromObjectAsync)) {
+ parser.readFeaturesFromObjectAsync(data, goog.bind(addFeatures, this),
+ {callback: callback});
+ } else {
+ goog.asserts.assert(goog.isFunction(parser.readFeaturesFromObject),
+ 'Expected a parser with a readFeaturesFromObject method.');
+ features = parser.readFeaturesFromObject(data, {callback: callback});
+ addFeatures.call(this, features);
+ }
} else {
// TODO: parse more data types
throw new Error('Data type not supported: ' + data);
}
- var sourceProjection = this.getSource().getProjection();
- var transform = ol.projection.getTransform(sourceProjection, projection);
-
- transform(
- this.pointVertices_.coordinates,
- this.pointVertices_.coordinates,
- this.pointVertices_.getDimension());
-
- transform(
- this.lineVertices_.coordinates,
- this.lineVertices_.coordinates,
- this.lineVertices_.getDimension());
-
- transform(
- this.polygonVertices_.coordinates,
- this.polygonVertices_.coordinates,
- this.polygonVertices_.getDimension());
-
- this.addFeatures(features);
};
diff --git a/src/ol/parser/featureparser.js b/src/ol/parser/featureparser.js
index 686fcb2443..94b828f2a7 100644
--- a/src/ol/parser/featureparser.js
+++ b/src/ol/parser/featureparser.js
@@ -1,3 +1,4 @@
+goog.provide('ol.parser.AsyncObjectFeatureParser');
goog.provide('ol.parser.DomFeatureParser');
goog.provide('ol.parser.ObjectFeatureParser');
goog.provide('ol.parser.ReadFeaturesOptions');
@@ -54,6 +55,23 @@ ol.parser.StringFeatureParser.prototype.readFeaturesFromString =
goog.abstractMethod;
+
+/**
+ * @interface
+ */
+ol.parser.AsyncObjectFeatureParser = function() {};
+
+
+/**
+ * @param {Object} obj Object representing features.
+ * @param {function(Array.)} callback Callback which is called
+ * after parsing.
+ * @param {ol.parser.ReadFeaturesOptions=} opt_options Feature reading options.
+ */
+ol.parser.AsyncObjectFeatureParser.prototype.readFeaturesFromObjectAsync =
+ goog.abstractMethod;
+
+
/**
* @typedef {function(ol.Feature, ol.geom.GeometryType):ol.geom.SharedVertices}
*/
diff --git a/src/ol/parser/kml.exports b/src/ol/parser/kml.exports
new file mode 100644
index 0000000000..d389b1ad42
--- /dev/null
+++ b/src/ol/parser/kml.exports
@@ -0,0 +1,3 @@
+@exportSymbol ol.parser.KML
+@exportProperty ol.parser.KML.prototype.read
+@exportProperty ol.parser.KML.prototype.write
diff --git a/src/ol/parser/kml.js b/src/ol/parser/kml.js
new file mode 100644
index 0000000000..703b51c0fd
--- /dev/null
+++ b/src/ol/parser/kml.js
@@ -0,0 +1,1042 @@
+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.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.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');
+
+
+/**
+ * @typedef {{extractAttributes: (boolean|undefined),
+ * extractStyles: (boolean|undefined),
+ * dimension: (number|undefined),
+ * maxDepth: (number|undefined)}}
+ */
+ol.parser.KMLOptions;
+
+
+
+/**
+ * @constructor
+ * @implements {ol.parser.DomFeatureParser}
+ * @implements {ol.parser.StringFeatureParser}
+ * @implements {ol.parser.AsyncObjectFeatureParser}
+ * @param {ol.parser.KMLOptions=} opt_options Optional configuration object.
+ * @extends {ol.parser.XML}
+ */
+ol.parser.KML = function(opt_options) {
+ if (goog.isDef(opt_options)) {
+ goog.object.extend(this, opt_options);
+ }
+ if (!goog.isDef(this.extractAttributes)) {
+ this.extractAttributes = true;
+ }
+ if (!goog.isDef(this.extractStyles)) {
+ this.extractStyles = false;
+ }
+ // TODO re-evaluate once shared structures support 3D
+ if (!goog.isDef(this.dimension)) {
+ this.dimension = 3;
+ }
+ if (!goog.isDef(this.maxDepth)) {
+ this.maxDepth = 0;
+ }
+ 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;
+ 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);
+ 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 (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;
+ });
+ // homogeneous collection
+ if (goog.object.getCount(buckets) === 1) {
+ var type = goog.object.getAnyKey(buckets);
+ switch (type) {
+ case ol.geom.GeometryType.POINT:
+ container.geometry = {
+ type: ol.geom.GeometryType.MULTIPOINT,
+ parts: parts
+ };
+ break;
+ case ol.geom.GeometryType.LINESTRING:
+ container.geometry = {
+ type: ol.geom.GeometryType.MULTILINESTRING,
+ parts: parts
+ };
+ break;
+ case ol.geom.GeometryType.POLYGON:
+ container.geometry = {
+ type: ol.geom.GeometryType.MULTIPOLYGON,
+ parts: parts
+ };
+ break;
+ default:
+ break;
+ }
+ } else {
+ container.geometry = {
+ type: ol.geom.GeometryType.GEOMETRYCOLLECTION,
+ parts: parts
+ };
+ }
+ },
+ '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, len = coords.length; i < len; 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.opacity = 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 symbolizer = {};
+ this.readChildNodes(node, symbolizer);
+ if (symbolizer.color) {
+ symbolizer.fillColor = symbolizer.color.color;
+ }
+ if (symbolizer.fill === '0' || symbolizer.fill === 'false') {
+ // TODO we need a better way in the symbolizer to disable fill
+ // now we are using opacity for this, but it's a workaround
+ // see also: https://github.com/openlayers/ol3/issues/475
+ symbolizer.opacity = 0;
+ } else {
+ symbolizer.opacity = symbolizer.color.opacity;
+ }
+ if (symbolizer.width) {
+ symbolizer.strokeWidth = parseFloat(symbolizer.width);
+ }
+ // outline disabled
+ if (symbolizer.outline === '0' || symbolizer.outline === 'false') {
+ symbolizer.strokeWidth = 0;
+ }
+ delete symbolizer.outline;
+ delete symbolizer.width;
+ delete symbolizer.color;
+ delete symbolizer.fill;
+ 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');
+ node.setAttribute('xmlns', this.defaultNamespaceURI);
+ 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) && typeof options[key] === 'string') {
+ 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) {
+ 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();
+ if (literal.opacity !== 0) {
+ this.writeNode('fill', '1', null, node);
+ } else {
+ this.writeNode('fill', '0', null, node);
+ }
+ this.writeNode('color', {
+ color: literal.fillColor.substring(1),
+ opacity: literal.opacity
+ }, null, node);
+ this.writeNode('width', literal.strokeWidth, null, node);
+ return node;
+ },
+ 'fill': function(fill) {
+ var node = this.createElementNS('fill');
+ node.appendChild(this.createTextNode(fill));
+ 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.opacity
+ }, 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');
+ 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) {
+ 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(Array.)} 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);
+};
+
+
+/**
+ * Parse a KML document provided as a string.
+ * @param {string} str KML document.
+ * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options.
+ * @return {Array.} Array of features.
+ */
+ol.parser.KML.prototype.readFeaturesFromString =
+ function(str, opt_options) {
+ this.readFeaturesOptions_ = opt_options;
+ return this.read(str).features;
+};
+
+
+/**
+ * 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 {Array.} Array of features.
+ */
+ol.parser.KML.prototype.readFeaturesFromNode =
+ function(node, opt_options) {
+ this.readFeaturesOptions_ = opt_options;
+ return this.read(node).features;
+};
+
+
+/**
+ * @param {Object} obj Object representing features.
+ * @param {ol.parser.ReadFeaturesOptions=} opt_options Feature reading options.
+ * @return {Array.} Array of features.
+ */
+ol.parser.KML.prototype.readFeaturesFromObject =
+ function(obj, opt_options) {
+ this.readFeaturesOptions_ = opt_options;
+ return this.read(obj).features;
+};
+
+
+/**
+ * @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.dispose();
+ if (data && 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=} opt_callback Optional callback to call when reading
+ * is done.
+ * @return {Object} An object representing the document.
+ */
+ol.parser.KML.prototype.read = function(data, opt_callback) {
+ if (typeof data == 'string') {
+ data = goog.dom.xml.loadXml(data);
+ }
+ if (data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var obj = {};
+ 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.features);
+ }, function() {
+ throw new Error('KML: parsing of NetworkLinks failed');
+ }, this);
+ });
+ } else {
+ return obj;
+ }
+ return null;
+};
+
+
+/**
+ * @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 = ol.geom.MultiPoint.fromParts(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 = ol.geom.MultiPolygon.fromParts(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);
+ return goog.dom.xml.serialize(root);
+};
diff --git a/src/ol/parser/xml.js b/src/ol/parser/xml.js
index 20c14d21af..01ed4bd6e1 100644
--- a/src/ol/parser/xml.js
+++ b/src/ol/parser/xml.js
@@ -9,6 +9,9 @@ goog.require('ol.parser.Parser');
* @extends {ol.parser.Parser}
*/
ol.parser.XML = function() {
+ if (goog.global.ActiveXObject) {
+ this.xmldom = new ActiveXObject('Microsoft.XMLDOM');
+ }
this.regExes = {
trimSpace: (/^\s*|\s*$/g),
removeSpace: (/\s*/g),
@@ -149,3 +152,97 @@ ol.parser.XML.prototype.getAttributeNS = function(node, uri, name) {
}
return attributeValue;
};
+
+
+/**
+ * Create a new element with namespace. This node can be appended to
+ * another node with the standard node.appendChild method. For
+ * cross-browser support, this method must be used instead of
+ * document.createElementNS.
+ *
+ * @param {string} name The qualified name of the element (prefix:localname).
+ * @param {string=} opt_uri Namespace URI for the element.
+ * @return {Element} A DOM element with namespace.
+ */
+ol.parser.XML.prototype.createElementNS = function(name, opt_uri) {
+ var uri = opt_uri ? opt_uri : this.defaultNamespaceURI;
+ var element;
+ if (this.xmldom) {
+ element = this.xmldom.createNode(1, name, uri);
+ } else {
+ element = document.createElementNS(uri, name);
+ }
+ return element;
+};
+
+
+/**
+ * Shorthand for applying one of the named writers and appending the
+ * results to a node.
+ *
+ * @param {string} name The name of a node to generate. Only use a local name.
+ * @param {Object} obj Structure containing data for the writer.
+ * @param {string=} opt_uri The name space uri to which the node belongs.
+ * @param {Element=} opt_parent Result will be appended to this node. If no
+ * parent is supplied, the node will not be appended to anything.
+ * @return {?Element} The child node.
+ */
+ol.parser.XML.prototype.writeNode = function(name, obj, opt_uri, opt_parent) {
+ var child = null;
+ if (goog.isDef(this.writers)) {
+ var uri = opt_uri ? opt_uri : this.defaultNamespaceURI;
+ child = this.writers[uri][name].apply(this, [obj]);
+ if (opt_parent && child) {
+ opt_parent.appendChild(child);
+ }
+ }
+ return child;
+};
+
+
+/**
+ * Create a text node. This node can be appended to another node with
+ * the standard node.appendChild method. For cross-browser support,
+ * this method must be used instead of document.createTextNode.
+ *
+ * @param {string} text The text of the node.
+ * @return {Element} A DOM text node.
+ */
+ol.parser.XML.prototype.createTextNode = function(text) {
+ var node;
+ if (this.xmldom) {
+ node = this.xmldom.createTextNode(text);
+ } else {
+ node = document.createTextNode(text);
+ }
+ return node;
+};
+
+
+/**
+ * Adds a new attribute or changes the value of an attribute with the given
+ * namespace and name.
+ *
+ * @param {Element} node Element node on which to set the attribute.
+ * @param {string} uri Namespace URI for the attribute.
+ * @param {string} name Qualified name (prefix:localname) for the attribute.
+ * @param {string} value Attribute value.
+ */
+ol.parser.XML.prototype.setAttributeNS = function(node, uri, name, value) {
+ if (node.setAttributeNS) {
+ node.setAttributeNS(uri, name, value);
+ } else {
+ if (this.xmldom) {
+ if (uri) {
+ var attribute = node.ownerDocument.createNode(
+ 2, name, uri);
+ attribute.nodeValue = value;
+ node.setAttributeNode(attribute);
+ } else {
+ node.setAttribute(name, value);
+ }
+ } else {
+ throw new Error('setAttributeNS not implemented');
+ }
+ }
+};
diff --git a/src/ol/style/icon.js b/src/ol/style/icon.js
index dce309eda4..b5fc255d20 100644
--- a/src/ol/style/icon.js
+++ b/src/ol/style/icon.js
@@ -119,7 +119,7 @@ ol.style.Icon = function(options) {
* @return {ol.style.IconLiteral} Literal shape symbolizer.
*/
ol.style.Icon.prototype.createLiteral = function(feature) {
- var attrs = feature.getAttributes();
+ var attrs = feature && feature.getAttributes();
var url = /** @type {string} */ (this.url_.evaluate(feature, attrs));
goog.asserts.assert(goog.isString(url) && url != '#', 'url must be a string');
diff --git a/test/spec/ol/parser/kml.test.js b/test/spec/ol/parser/kml.test.js
new file mode 100644
index 0000000000..0345927206
--- /dev/null
+++ b/test/spec/ol/parser/kml.test.js
@@ -0,0 +1,289 @@
+goog.provide('ol.test.parser.kml');
+
+describe('ol.parser.kml', function() {
+
+ var parser = new ol.parser.KML();
+
+ describe('Test KML parser', function() {
+ it('Polygon read correctly', function() {
+ var url = 'spec/ol/parser/kml/polygon.kml';
+ afterLoadXml(url, function(xml) {
+ var obj = parser.read(xml);
+ var output = parser.write(obj);
+ var expected = '' +
+ 'Polygon.kml0' +
+ 'hollow box' +
+ '-122.366278,37.818844,30 ' +
+ '-122.365248,37.819267,30 ' +
+ '-122.36564,37.819861,30 ' +
+ '-122.366669,37.819429,30 ' +
+ '-122.366278,37.818844,30' +
+ '' +
+ '-122.366212,37.818977,30 ' +
+ '-122.365424,37.819294,30 ' +
+ '-122.365704,37.819731,30 ' +
+ '-122.366488,37.819402,30 ' +
+ '-122.366212,37.818977,30' +
+ '';
+ expect(output).to.eql(expected);
+ expect(obj.features.length).to.eql(1);
+ var geom = obj.features[0].getGeometry();
+ expect(geom instanceof ol.geom.Polygon).to.be.ok();
+ expect(geom.dimension).to.eql(3);
+ });
+ });
+ it('Linestring read correctly', function() {
+ var url = 'spec/ol/parser/kml/linestring.kml';
+ afterLoadXml(url, function(xml) {
+ var obj = parser.read(xml);
+ var output = parser.write(obj);
+ var expected = '' +
+ 'LineString.kml1' +
+ 'unextruded-122.364383,' +
+ '37.824664,0 -122.364152,37.824322,0' +
+ 'extruded' +
+ '-122.364167,37.824787,50 -122.363917,37.824423,50' +
+ '';
+ expect(output).to.eql(expected);
+ expect(obj.features.length).to.eql(2);
+ var geom = obj.features[0].getGeometry();
+ expect(geom instanceof ol.geom.LineString).to.be.ok();
+ expect(geom.dimension).to.eql(3);
+ geom = obj.features[1].getGeometry();
+ expect(geom instanceof ol.geom.LineString).to.be.ok();
+ });
+ });
+ it('Point read correctly', function() {
+ var url = 'spec/ol/parser/kml/point.kml';
+ afterLoadXml(url, function(xml) {
+ var obj = parser.read(xml);
+ var output = parser.write(obj);
+ var expected = '' +
+ 'Simple placemark' +
+ 'Attached to the ground. Intelligently places itself \n' +
+ ' at the height of the underlying terrain.' +
+ '-122.0822035425683,37.42228990140251,0' +
+ '';
+ expect(output).to.eql(expected);
+ expect(obj.features.length).to.eql(1);
+ var geom = obj.features[0].getGeometry();
+ expect(geom instanceof ol.geom.Point).to.be.ok();
+ expect(geom.dimension).to.eql(3);
+ });
+ });
+ it('NetworkLink read correctly', function(done) {
+ var url = 'spec/ol/parser/kml/networklink.kml';
+ afterLoadXml(url, function(xml) {
+ var p = new ol.parser.KML({maxDepth: 1});
+ // we need to supply a callback to get visited NetworkLinks
+ var obj = p.read(xml, function(features) {
+ expect(features.length).to.eql(3);
+ done();
+ });
+ });
+ });
+ it('NetworkLink read correctly [recursively]', function(done) {
+ var url = 'spec/ol/parser/kml/networklink_depth.kml';
+ afterLoadXml(url, function(xml) {
+ var p = new ol.parser.KML({maxDepth: 2});
+ // we need to supply a callback to get visited NetworkLinks
+ var obj = p.read(xml, function(features) {
+ expect(features.length).to.eql(2);
+ done();
+ });
+ });
+ });
+ it('NetworkLink maxDepth', function(done) {
+ var url = 'spec/ol/parser/kml/networklink_depth.kml';
+ afterLoadXml(url, function(xml) {
+ var p = new ol.parser.KML({maxDepth: 1});
+ // we need to supply a callback to get visited NetworkLinks
+ var obj = p.read(xml, function(features) {
+ // since maxDepth is 1, we will not get to the second feature
+ expect(features.length).to.eql(1);
+ done();
+ });
+ });
+ });
+ it('Extended data read correctly', function() {
+ var url = 'spec/ol/parser/kml/extended_data.kml';
+ afterLoadXml(url, function(xml) {
+ var obj = parser.read(xml);
+ expect(obj.features[0].get('name')).to.eql('Extended data placemark');
+ var description = 'Attached to the ground. Intelligently places ' +
+ 'itself \n at the height of the underlying terrain.';
+ expect(obj.features[0].get('description')).to.eql(description);
+ expect(obj.features[0].get('foo')).to.eql('bar');
+ });
+ });
+ it('Extended data read correctly [2]', function() {
+ var url = 'spec/ol/parser/kml/extended_data2.kml';
+ afterLoadXml(url, function(xml) {
+ var obj = parser.read(xml);
+ var feature = obj.features[0];
+ expect(feature.get('TrailHeadName')).to.eql('Pi in the sky');
+ expect(feature.get('TrailLength')).to.eql('3.14159');
+ expect(feature.get('ElevationGain')).to.eql('10');
+ });
+ });
+ it('Multi geometry read correctly', function() {
+ var url = 'spec/ol/parser/kml/multigeometry.kml';
+ afterLoadXml(url, function(xml) {
+ var obj = parser.read(xml);
+ var geom = obj.features[0].getGeometry();
+ var expected = '' +
+ 'Polygon.kml0' +
+ 'SF Marina Harbor Master' +
+ '-122.4425587930444,37.80666418607323,0 ' +
+ '-122.4428379594768,37.80663578323093,0' +
+ '-122.4425509770566,' +
+ '37.80662588061205,0 -122.4428340530617,37.8065999493009,0' +
+ '' +
+ '';
+ var output = parser.write(obj);
+ expect(output).to.eql(expected);
+ expect(geom instanceof ol.geom.MultiLineString).to.be.ok();
+ });
+ });
+ it('Discrete multi geometry read correctly', function() {
+ var url = 'spec/ol/parser/kml/multigeometry_discrete.kml';
+ afterLoadXml(url, function(xml) {
+ var obj = parser.read(xml);
+ var geom = obj.features[0].getGeometry();
+ expect(geom instanceof ol.geom.GeometryCollection).to.be.ok();
+ expect(geom.components.length).to.eql(2);
+ expect(geom.components[0] instanceof ol.geom.LineString).to.be.ok();
+ expect(geom.components[1] instanceof ol.geom.Point).to.be.ok();
+ });
+ });
+ it('Test extract tracks', function() {
+ var url = 'spec/ol/parser/kml/macnoise.kml';
+ afterLoadXml(url, function(xml) {
+ var p = new ol.parser.KML({extractStyles: true,
+ trackAttributes: ['speed', 'num']});
+ var obj = p.read(xml);
+ expect(obj.features.length).to.eql(170);
+ var attr = obj.features[4].getAttributes();
+ // standard track point attributes
+ expect(attr['when'] instanceof Date).to.be.ok();
+ expect(attr['when'].getTime()).to.eql(1272736815000);
+ expect(attr['altitude']).to.eql(1006);
+ expect(attr['heading']).to.eql(230);
+ expect(attr['tilt']).to.eql(0);
+ expect(attr['roll']).to.eql(0);
+ expect(attr['name']).to.eql('B752');
+ expect(attr['adflag']).to.eql('A');
+ expect(attr['flightid']).to.eql('DAL2973');
+ expect(attr['speed']).to.eql('166');
+ expect(attr['num']).to.eql('50');
+ var geom = obj.features[4].getGeometry();
+ expect(geom.get(0)).to.eql(-93.0753620391713);
+ expect(geom.get(1)).to.eql(44.9879724110872);
+ expect(geom.get(2)).to.eql(1006);
+ });
+ });
+ it('Test CDATA attributes', function() {
+ var cdata = '' +
+ ' ' +
+ '#rel1.0' +
+ ' 17.266666, 48.283333' +
+ '';
+ var obj = parser.read(cdata);
+ expect(obj.features[0].get('description')).to.eql('Full of text.');
+ expect(obj.features[0].get('name')).to.eql('Pezinok');
+ });
+ it('Test line style', function() {
+ var test_style = ' ' +
+ ' ' +
+ ' -112,36 -113,37 ' +
+ '';
+ var p = new ol.parser.KML({extractStyles: true});
+ var obj = p.read(test_style);
+ var output = p.write(obj);
+ var expected = '' +
+ '' +
+ '-112,36 -113,37' +
+ '';
+ expect(output).to.eql(expected);
+ var symbolizer = obj.features[0].getSymbolizerLiterals()[0];
+ expect(symbolizer instanceof ol.style.LineLiteral).to.be.ok();
+ expect(symbolizer.strokeColor).to.eql('#ff0000');
+ expect(symbolizer.opacity).to.eql(0.5294117647058824);
+ expect(symbolizer.strokeWidth).to.eql(10);
+ });
+ it('Test style fill', function() {
+ var test_style_fill = ' ' +
+ ' ' +
+ '' +
+ '5.001370157823406,49.26855713824488 8.214706453896161,' +
+ '49.630662409673505 8.397385910100951,48.45172350357396 ' +
+ '5.001370157823406,49.26855713824488' +
+ ' ' +
+ '' +
+ '5.001370157823406,49.26855713824488 8.214706453896161,' +
+ '49.630662409673505 8.397385910100951,48.45172350357396 ' +
+ '5.001370157823406,49.26855713824488' +
+ '';
+ var p = new ol.parser.KML({extractStyles: true});
+ var obj = p.read(test_style_fill);
+ var output = p.write(obj);
+ var expected = '' +
+ '' +
+ '5.001370157823406,' +
+ '49.26855713824488 8.214706453896161,49.630662409673505 ' +
+ '8.397385910100951,48.45172350357396 5.001370157823406,' +
+ '49.26855713824488' +
+ '' +
+ '' +
+ '5.001370157823406,49.26855713824488 8.214706453896161,' +
+ '49.630662409673505 8.397385910100951,48.45172350357396 ' +
+ '5.001370157823406,49.26855713824488' +
+ '';
+ expect(output).to.eql(expected);
+ var symbolizer1 = obj.features[0].getSymbolizerLiterals()[0];
+ var symbolizer2 = obj.features[1].getSymbolizerLiterals()[0];
+ expect(symbolizer1.fillColor).to.eql('#ff0000');
+ expect(symbolizer2.opacity).to.eql(0);
+ });
+ it('Test iconStyle', function() {
+ var url = 'spec/ol/parser/kml/iconstyle.kml';
+ afterLoadXml(url, function(xml) {
+ var p = new ol.parser.KML({extractStyles: true});
+ var obj = p.read(xml);
+ var output = p.write(obj);
+ var expected = '' +
+ '' +
+ 'Pin on a mountaintop' +
+ '#pushpin170.1435558771009,' +
+ '-43.60505741890396,0' +
+ '';
+ expect(output).to.eql(expected);
+ var symbolizer = obj.features[0].getSymbolizerLiterals()[0];
+ var url = 'http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png';
+ expect(symbolizer.url).to.eql(url);
+ expect(symbolizer.width).to.eql(32);
+ expect(symbolizer.height).to.eql(32);
+ });
+ });
+ });
+});
+
+goog.require('goog.net.XhrIo');
+
+goog.require('ol.Feature');
+goog.require('ol.geom.GeometryCollection');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.MultiLineString');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.parser.KML');
+goog.require('ol.style.LineLiteral');
diff --git a/test/spec/ol/parser/kml/depth.kml b/test/spec/ol/parser/kml/depth.kml
new file mode 100644
index 0000000000..f1b30ad95c
--- /dev/null
+++ b/test/spec/ol/parser/kml/depth.kml
@@ -0,0 +1,6 @@
+
+
+
+ spec/ol/parser/kml/polygon.kml
+
+
diff --git a/test/spec/ol/parser/kml/extended_data.kml b/test/spec/ol/parser/kml/extended_data.kml
new file mode 100644
index 0000000000..6f23151b7f
--- /dev/null
+++ b/test/spec/ol/parser/kml/extended_data.kml
@@ -0,0 +1,16 @@
+
+
+
+ Extended data placemark
+ Attached to the ground. Intelligently places itself
+ at the height of the underlying terrain.
+
+
+ bar
+
+
+
+ -122.0822035425683,37.42228990140251,0
+
+
+
diff --git a/test/spec/ol/parser/kml/extended_data2.kml b/test/spec/ol/parser/kml/extended_data2.kml
new file mode 100644
index 0000000000..a5e1d4d49f
--- /dev/null
+++ b/test/spec/ol/parser/kml/extended_data2.kml
@@ -0,0 +1,30 @@
+
+
+
+ Easy trail
+
+
+ Pi in the sky
+ 3.14159
+ 10
+
+
+
+ -122.000,37.002
+
+
+
+ Difficult trail
+
+
+ Mount Everest
+ 347.45
+ 10000
+
+
+
+ -122.000,37.002
+
+
+
+
diff --git a/test/spec/ol/parser/kml/iconstyle.kml b/test/spec/ol/parser/kml/iconstyle.kml
new file mode 100644
index 0000000000..493ac02094
--- /dev/null
+++ b/test/spec/ol/parser/kml/iconstyle.kml
@@ -0,0 +1,23 @@
+
+
+
+
+ Pin on a mountaintop
+ #pushpin
+
+ 170.1435558771009,-43.60505741890396,0
+
+
+
+
diff --git a/test/spec/ol/parser/kml/linestring.kml b/test/spec/ol/parser/kml/linestring.kml
new file mode 100644
index 0000000000..01a941a07b
--- /dev/null
+++ b/test/spec/ol/parser/kml/linestring.kml
@@ -0,0 +1,36 @@
+
+
+
+ LineString.kml
+ 1
+
+ -122.36415
+ 37.824553
+ 0
+ 150
+ 50
+ 0
+
+
+ unextruded
+
+ 1
+ 1
+
+ -122.364383,37.824664,0 -122.364152,37.824322,0
+
+
+
+
+ extruded
+
+ 1
+ 1
+ relativeToGround
+
+ -122.364167,37.824787,50 -122.363917,37.824423,50
+
+
+
+
+
diff --git a/test/spec/ol/parser/kml/macnoise.kml b/test/spec/ol/parser/kml/macnoise.kml
new file mode 100644
index 0000000000..f544b48fde
--- /dev/null
+++ b/test/spec/ol/parser/kml/macnoise.kml
@@ -0,0 +1,1038 @@
+
+
+
+
+ 2010-05-01T13:00:00-05:00
+
+ -93.2207
+ 44.882
+ 50000
+ 0
+ 0
+
+
+
+
+
+
+Flight Tracks
+
+ Arrivals
+
+ B752
+ A
+ DAL2973
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:00-05
+ 2010-05-01T13:00:01-05
+ 2010-05-01T13:00:06-05
+ 2010-05-01T13:00:10-05
+ 2010-05-01T13:00:15-05
+ 2010-05-01T13:00:20-05
+ 2010-05-01T13:00:24-05
+ 2010-05-01T13:00:29-05
+ 2010-05-01T13:00:33-05
+ 2010-05-01T13:00:38-05
+ 2010-05-01T13:00:43-05
+ 2010-05-01T13:00:47-05
+ 2010-05-01T13:00:52-05
+ 2010-05-01T13:00:57-05
+ 2010-05-01T13:01:00-05
+ -93.0658625188843 44.9949645987875 1036
+ -93.0664690096445 44.9945424635331 1036
+ -93.0694347065378 44.9923936108644 1036
+ -93.0722946883822 44.9901649091109 1006
+ -93.0753620391713 44.9879724110872 1006
+ -93.078638650624 44.985904678007 975
+ -93.0817463907976 44.9836868456013 975
+ -93.0847749343212 44.9813998515538 945
+ -93.0879207383429 44.9791066547511 914
+ -93.091282218058 44.976822731273 914
+ -93.0945882606646 44.9745372955479 884
+ -93.0979053364864 44.9722421846492 884
+ -93.1012678619471 44.9698451058525 853
+ -93.1044570741037 44.967424293466 853
+ -93.1068079756418 44.9657037851018 853
+ 230 0 0
+ 220 0 0
+ 220 0 0
+ 230 0 0
+ 230 0 0
+ 230 0 0
+ 230 0 0
+ 220 0 0
+ 220 0 0
+ 230 0 0
+ 230 0 0
+ 220 0 0
+ 220 0 0
+ 220 0 0
+ 220 0 0
+ 162
+ 160
+ 159
+ 165
+ 166
+ 174
+ 170
+ 172
+ 180
+ 176
+ 177
+ 177
+ 180
+ 184
+ 177
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+ 130
+ 140
+ 150
+
+
+ E170
+ A
+ TCF7521
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:00-05
+ 2010-05-01T13:00:04-05
+ 2010-05-01T13:00:09-05
+ 2010-05-01T13:00:13-05
+ 2010-05-01T13:00:18-05
+ 2010-05-01T13:00:23-05
+ 2010-05-01T13:00:27-05
+ 2010-05-01T13:00:32-05
+ 2010-05-01T13:00:37-05
+ 2010-05-01T13:00:41-05
+ 2010-05-01T13:00:46-05
+ 2010-05-01T13:00:51-05
+ 2010-05-01T13:00:55-05
+ 2010-05-01T13:01:00-05
+ -93.3806146339391 44.8823651507134 2743
+ -93.3773041814209 44.887531728655 2743
+ -93.3742856469083 44.8942041806778 2743
+ -93.3722375106026 44.9009231720158 2743
+ -93.3711934089417 44.9077495987718 2712
+ -93.3707288919852 44.9145219645156 2712
+ -93.3703882714439 44.921240089024 2682
+ -93.3700882719793 44.9278850664392 2682
+ -93.369810041597 44.934389356737 2651
+ -93.3696836566166 44.9408553642446 2651
+ -93.3695425129226 44.9473561165969 2621
+ -93.3693185423471 44.9537360442564 2621
+ -93.3693194298816 44.9599975904123 2590
+ -93.3694031671108 44.9661411653607 2590
+ 20 0 0
+ 20 0 0
+ 20 0 0
+ 10 0 0
+ 10 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 360 0 0
+ 376
+ 367
+ 361
+ 371
+ 367
+ 363
+ 359
+ 356
+ 352
+ 347
+ 343
+ 347
+ 334
+ 337
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+ 130
+ 140
+
+
+ BE33
+ A
+ N38175
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:00-05
+ 2010-05-01T13:00:02-05
+ 2010-05-01T13:00:07-05
+ 2010-05-01T13:00:12-05
+ 2010-05-01T13:00:16-05
+ 2010-05-01T13:00:21-05
+ 2010-05-01T13:00:25-05
+ 2010-05-01T13:00:30-05
+ 2010-05-01T13:00:35-05
+ 2010-05-01T13:00:39-05
+ 2010-05-01T13:00:44-05
+ 2010-05-01T13:00:49-05
+ 2010-05-01T13:00:53-05
+ 2010-05-01T13:00:58-05
+ 2010-05-01T13:01:00-05
+ -93.0144637208028 44.6541474764804 1006
+ -93.0162681345228 44.6547274296664 1006
+ -93.0196734868835 44.6559915702004 975
+ -93.0231899415297 44.657188463998 945
+ -93.0267619421777 44.6582849847887 945
+ -93.0302021384369 44.6594728216183 914
+ -93.0338776768471 44.6606515995762 914
+ -93.0375866343814 44.6618806707998 884
+ -93.0411146687035 44.6632657982455 884
+ -93.0447829038862 44.6646495821585 884
+ -93.0486933143218 44.6659856209571 914
+ -93.0525604964428 44.6672664774449 884
+ -93.0559892061682 44.6686325276705 884
+ -93.0595122787868 44.6700360197293 884
+ -93.0610274392619 44.6706087373734 884
+ 300 0 0
+ 300 0 0
+ 300 0 0
+ 300 0 0
+ 290 0 0
+ 290 0 0
+ 300 0 0
+ 300 0 0
+ 300 0 0
+ 300 0 0
+ 300 0 0
+ 300 0 0
+ 300 0 0
+ 300 0 0
+ 300 0 0
+ 150
+ 156
+ 152
+ 156
+ 151
+ 152
+ 160
+ 157
+ 159
+ 158
+ 158
+ 160
+ 155
+ 155
+ 156
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+ 130
+ 140
+ 150
+
+
+ A319
+ A
+ DAL1588
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:00-05
+ 2010-05-01T13:00:04-05
+ 2010-05-01T13:00:08-05
+ 2010-05-01T13:00:13-05
+ 2010-05-01T13:00:18-05
+ 2010-05-01T13:00:22-05
+ 2010-05-01T13:00:27-05
+ 2010-05-01T13:00:31-05
+ 2010-05-01T13:00:36-05
+ 2010-05-01T13:00:41-05
+ 2010-05-01T13:00:45-05
+ 2010-05-01T13:00:50-05
+ 2010-05-01T13:00:55-05
+ 2010-05-01T13:00:59-05
+ 2010-05-01T13:01:00-05
+ -93.6927825194056 44.7952011849485 3011
+ -93.6850156681578 44.7968042586582 2987
+ -93.6752785488692 44.7990458605003 2956
+ -93.6657083011645 44.8014897663497 2926
+ -93.6560029615388 44.803768841381 2865
+ -93.6462045264035 44.8058749817725 2834
+ -93.6365671200126 44.8080848199989 2804
+ -93.6269933807039 44.8102767000109 2773
+ -93.6175405757462 44.8123960709083 2743
+ -93.6082528975965 44.8146455509748 2743
+ -93.599077315807 44.816765612372 2743
+ -93.5899428762254 44.8186933623744 2743
+ -93.5809104439923 44.8205403457841 2743
+ -93.5720785209701 44.8224608846058 2743
+ -93.5703603013364 44.8228739543212 2743
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 70 0 0
+ 390
+ 383
+ 397
+ 390
+ 405
+ 388
+ 386
+ 397
+ 377
+ 373
+ 367
+ 362
+ 365
+ 350
+ 354
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+ 130
+ 140
+ 150
+
+
+ E145
+ A
+ CHQ1453
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:00-05
+ 2010-05-01T13:00:01-05
+ 2010-05-01T13:00:06-05
+ 2010-05-01T13:00:11-05
+ 2010-05-01T13:00:15-05
+ 2010-05-01T13:00:20-05
+ 2010-05-01T13:00:24-05
+ 2010-05-01T13:00:29-05
+ 2010-05-01T13:00:34-05
+ 2010-05-01T13:00:38-05
+ 2010-05-01T13:00:43-05
+ 2010-05-01T13:00:48-05
+ 2010-05-01T13:00:52-05
+ 2010-05-01T13:00:57-05
+ 2010-05-01T13:01:00-05
+ -92.5727580977974 45.0236058844647 2530
+ -92.5742776202954 45.0237913896498 2530
+ -92.5803397933112 45.0241784662561 2499
+ -92.5865075192046 45.0247891381303 2469
+ -92.5926877928765 45.0257073410966 2469
+ -92.5986546763805 45.0261844476041 2438
+ -92.6046737535477 45.0267206733977 2438
+ -92.6106885874739 45.0275061986719 2438
+ -92.616359210337 45.027935793162 2438
+ -92.6220735719954 45.028379077688 2438
+ -92.6280403097635 45.0290552550566 2438
+ -92.6341725652711 45.029824064212 2438
+ -92.640279209769 45.0304963952702 2438
+ -92.6463747377703 45.0311129317319 2438
+ -92.650043383232 45.0314890298388 2438
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 235
+ 246
+ 239
+ 244
+ 234
+ 232
+ 238
+ 227
+ 228
+ 229
+ 229
+ 232
+ 228
+ 232
+ 236
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+ 130
+ 140
+ 150
+
+
+ E170
+ A
+ CPZ5695
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:11-05
+ 2010-05-01T13:00:15-05
+ 2010-05-01T13:00:20-05
+ 2010-05-01T13:00:25-05
+ 2010-05-01T13:00:29-05
+ 2010-05-01T13:00:34-05
+ 2010-05-01T13:00:38-05
+ 2010-05-01T13:00:43-05
+ 2010-05-01T13:00:48-05
+ 2010-05-01T13:00:52-05
+ 2010-05-01T13:00:57-05
+ 2010-05-01T13:01:00-05
+ -92.3689380245182 45.0389467469425 2804
+ -92.3759530819834 45.0380951007958 2773
+ -92.3831159633175 45.0369957486846 2712
+ -92.3901362714549 45.0355238496347 2651
+ -92.3970814910858 45.0339385808083 2621
+ -92.4043121546626 45.032585906621 2560
+ -92.4118367565321 45.0319048652958 2499
+ -92.419078934653 45.030875157485 2469
+ -92.4262095560369 45.0291153314744 2438
+ -92.4335237384463 45.0273941113051 2438
+ -92.4408178608932 45.0260076351757 2438
+ -92.4451575746228 45.0254275529773 2438
+ 260 0 0
+ 260 0 0
+ 260 0 0
+ 250 0 0
+ 260 0 0
+ 260 0 0
+ 260 0 0
+ 260 0 0
+ 250 0 0
+ 250 0 0
+ 250 0 0
+ 260 0 0
+ 277
+ 288
+ 283
+ 291
+ 283
+ 284
+ 298
+ 288
+ 288
+ 278
+ 283
+ 288
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+
+
+ DC95
+ A
+ DAL2858
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:00-05
+ 2010-05-01T13:00:03-05
+ 2010-05-01T13:00:07-05
+ 2010-05-01T13:00:12-05
+ 2010-05-01T13:00:17-05
+ 2010-05-01T13:00:21-05
+ 2010-05-01T13:00:26-05
+ 2010-05-01T13:00:30-05
+ 2010-05-01T13:00:35-05
+ 2010-05-01T13:00:40-05
+ 2010-05-01T13:00:44-05
+ 2010-05-01T13:00:49-05
+ 2010-05-01T13:00:54-05
+ 2010-05-01T13:00:58-05
+ 2010-05-01T13:01:00-05
+ -93.1962465696187 44.4584257162471 3078
+ -93.1954858158128 44.462643897726 3078
+ -93.1945524569257 44.4696206853623 3048
+ -93.1935347734104 44.4765680167011 3048
+ -93.1921548885013 44.4834366892852 3048
+ -93.1912787899895 44.4902740201102 3048
+ -93.190869393024 44.496999598511 3048
+ -93.190355669541 44.503701889363 3048
+ -93.1899042890233 44.510392533924 3048
+ -93.1894352972433 44.5171043633827 3048
+ -93.1887272976791 44.523838031578 3017
+ -93.1882343860587 44.5305421014878 2987
+ -93.1878483537445 44.5373007218153 2987
+ -93.187206305476 44.5440099500882 2956
+ -93.1870547021374 44.5466877366242 2956
+ 10 0 0
+ 10 0 0
+ 10 0 0
+ 10 0 0
+ 10 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 0 0 0
+ 378
+ 370
+ 381
+ 373
+ 384
+ 367
+ 365
+ 377
+ 362
+ 362
+ 362
+ 362
+ 368
+ 355
+ 362
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+ 130
+ 140
+ 150
+
+
+ B737
+ A
+ SWA1488
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:00-05
+ 2010-05-01T13:00:01-05
+ 2010-05-01T13:00:06-05
+ 2010-05-01T13:00:11-05
+ 2010-05-01T13:00:15-05
+ 2010-05-01T13:00:20-05
+ 2010-05-01T13:00:24-05
+ 2010-05-01T13:00:29-05
+ 2010-05-01T13:00:34-05
+ 2010-05-01T13:00:38-05
+ 2010-05-01T13:00:43-05
+ 2010-05-01T13:00:48-05
+ 2010-05-01T13:00:52-05
+ 2010-05-01T13:00:57-05
+ 2010-05-01T13:01:00-05
+ -92.7436038977339 45.0176449723009 2438
+ -92.745419752639 45.0178405701636 2438
+ -92.7525586927583 45.0181852080204 2438
+ -92.7599978682742 45.0189437491361 2438
+ -92.7673964649616 45.0200176804669 2438
+ -92.7743047878147 45.0206512321095 2438
+ -92.7812211106102 45.0212438545962 2438
+ -92.7880905786106 45.0219352711124 2438
+ -92.7948110303679 45.0225135550872 2438
+ -92.8016256231407 45.0231539091809 2377
+ -92.808436321378 45.0237782407713 2316
+ -92.8153060032773 45.0245123996427 2255
+ -92.8220950756464 45.0250388052127 2194
+ -92.8289929014999 45.0256725515916 2164
+ -92.8342709686589 45.0263726025032 2118.25
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280 0 0
+ 280
+ 293
+ 284
+ 288
+ 274
+ 272
+ 279
+ 263
+ 263
+ 262
+ 262
+ 275
+ 270
+ 277
+ 287
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+ 130
+ 140
+ 150
+
+
+ A318
+ A
+ FFT106
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:00-05
+ 2010-05-01T13:00:05-05
+ 2010-05-01T13:00:09-05
+ 2010-05-01T13:00:14-05
+ 2010-05-01T13:00:19-05
+ 2010-05-01T13:00:23-05
+ 2010-05-01T13:00:28-05
+ 2010-05-01T13:00:33-05
+ 2010-05-01T13:00:37-05
+ 2010-05-01T13:00:42-05
+ 2010-05-01T13:00:47-05
+ 2010-05-01T13:00:51-05
+ 2010-05-01T13:00:56-05
+ 2010-05-01T13:01:00-05
+ -93.2974568508014 45.0687622602847 1432
+ -93.2934457905393 45.0660257042941 1371
+ -93.2902010482642 45.0627382200457 1341
+ -93.2880735868205 45.0592062737728 1280
+ -93.2866251180089 45.0556538417996 1280
+ -93.2855706436895 45.0521555770546 1249
+ -93.2848929213344 45.0486326683558 1249
+ -93.284149302237 45.0450445279501 1219
+ -93.2832681542582 45.0414770478452 1219
+ -93.2822163760078 45.0378266141909 1219
+ -93.2810695206555 45.0339762188888 1249
+ -93.2800852709943 45.0300242656845 1249
+ -93.2789451826991 45.026165428423 1249
+ -93.2776553627852 45.0222881273358 1219
+ 140 0 0
+ 150 0 0
+ 150 0 0
+ 160 0 0
+ 170 0 0
+ 170 0 0
+ 170 0 0
+ 170 0 0
+ 170 0 0
+ 170 0 0
+ 170 0 0
+ 170 0 0
+ 170 0 0
+ 170 0 0
+ 212
+ 205
+ 208
+ 203
+ 201
+ 196
+ 196
+ 197
+ 202
+ 205
+ 216
+ 215
+ 222
+ 231
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+ 130
+ 140
+
+
+
+ A
+
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:00-05
+ 2010-05-01T13:00:05-05
+ 2010-05-01T13:00:10-05
+ 2010-05-01T13:00:14-05
+ 2010-05-01T13:00:24-05
+ 2010-05-01T13:00:33-05
+ 2010-05-01T13:00:37-05
+ 2010-05-01T13:00:42-05
+ 2010-05-01T13:00:47-05
+ 2010-05-01T13:00:51-05
+ 2010-05-01T13:00:56-05
+ 2010-05-01T13:01:00-05
+ -93.5287325331323 45.3502794027397 731
+ -93.5305174337715 45.3463816209029 731
+ -93.532323089283 45.3433065196778 731
+ -93.5344374505075 45.3397938806867 731
+ -93.5365879669744 45.3355152994798 731
+ -93.538455345577 45.3317693717468 731
+ -93.5402440337749 45.3288175816964 731
+ -93.5420054353005 45.3261482119682 701
+ -93.5437972875724 45.3236486426325 701
+ -93.5449025453586 45.3213557809437 670
+ -93.5460939368394 45.3190373998605 670
+ -93.5479457332637 45.3165177805485 670
+ 200 0 0
+ 200 0 0
+ 200 0 0
+ 200 0 0
+ 200 0 0
+ 200 0 0
+ 200 0 0
+ 200 0 0
+ 200 0 0
+ 200 0 0
+ 200 0 0
+ 200 0 0
+ 202
+ 180
+ 166
+ 171
+ 162
+ 157
+ 143
+ 145
+ 156
+ 147
+ 147
+ 150
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+
+
+ CRJ2
+ A
+ SKW4805
+ #arrival
+
+
+ CRJ2
+ A
+ FLG4092
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:00-05
+ 2010-05-01T13:00:01-05
+ 2010-05-01T13:00:06-05
+ 2010-05-01T13:00:10-05
+ 2010-05-01T13:00:15-05
+ 2010-05-01T13:00:20-05
+ 2010-05-01T13:00:24-05
+ 2010-05-01T13:00:29-05
+ 2010-05-01T13:00:34-05
+ 2010-05-01T13:00:38-05
+ 2010-05-01T13:00:44-05
+ 2010-05-01T13:00:49-05
+ 2010-05-01T13:00:54-05
+ -93.1836067392297 44.9110362339843 432.2
+ -93.1841170614853 44.910663862492 426
+ -93.1867007876887 44.908842129317 426
+ -93.1893728799637 44.9069842219291 396
+ -93.1919479660705 44.9051548529609 365
+ -93.1944798212107 44.9032897679148 365
+ -93.197164452306 44.9014210542153 335
+ -93.1996234874761 44.8995719817206 335
+ -93.2021701211426 44.8975674983317 304
+ -93.2050345971567 44.8955942303701 304
+ -93.2075455037487 44.8938556558558 304
+ -93.2100820128846 44.8918590963212 304
+ -93.2127524858241 44.89000250047 256
+ 220 0 0
+ 230 0 0
+ 230 0 0
+ 230 0 0
+ 230 0 0
+ 220 0 0
+ 220 0 0
+ 220 0 0
+ 220 0 0
+ 220 0 0
+ 220 0 0
+ 220 0 0
+ 220 0 0
+ 141
+ 138
+ 136
+ 141
+ 141
+ 142
+ 143
+ 139
+ 140
+ 134
+ 136
+ 136
+ 123
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+ 130
+
+
+ E170
+ A
+ CPZ5667
+ #arrival
+
+ absolute
+ 1
+ 2010-05-01T13:00:00-05
+ 2010-05-01T13:00:01-05
+ 2010-05-01T13:00:06-05
+ 2010-05-01T13:00:10-05
+ 2010-05-01T13:00:15-05
+ 2010-05-01T13:00:20-05
+ 2010-05-01T13:00:24-05
+ 2010-05-01T13:00:29-05
+ 2010-05-01T13:00:34-05
+ 2010-05-01T13:00:38-05
+ 2010-05-01T13:00:43-05
+ 2010-05-01T13:00:47-05
+ 2010-05-01T13:00:52-05
+ 2010-05-01T13:00:57-05
+ 2010-05-01T13:01:00-05
+ -92.9496238812799 45.0117549407746 1438.2
+ -92.9507065768732 45.0116702587604 1432
+ -92.9563739191926 45.0116271226204 1432
+ -92.9620225732021 45.0115639668496 1432
+ -92.9673675587699 45.0113432900049 1402
+ -92.9725115032188 45.0111442254373 1402
+ -92.9778810091229 45.0112050922639 1371
+ -92.9832227114571 45.0112143826731 1371
+ -92.9884546803523 45.0110418166788 1341
+ -92.9938268606229 45.0109652220709 1341
+ -92.9991151069756 45.010802144845 1310
+ -93.0041467584036 45.0105516668541 1310
+ -93.0090742909164 45.0105233046799 1280
+ -93.0139435770527 45.0106265340001 1280
+ -93.0174882575928 45.0106328449121 1256.75
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 270 0 0
+ 214
+ 207
+ 202
+ 208
+ 207
+ 205
+ 203
+ 202
+ 209
+ 199
+ 196
+ 200
+ 193
+ 194
+ 185
+ 10
+ 20
+ 30
+ 40
+ 50
+ 60
+ 70
+ 80
+ 90
+ 100
+ 110
+ 120
+ 130
+ 140
+ 150
+
+
+
+ Departures
+
+
+ Overflights
+
+
+
diff --git a/test/spec/ol/parser/kml/multigeometry.kml b/test/spec/ol/parser/kml/multigeometry.kml
new file mode 100644
index 0000000000..d24379886f
--- /dev/null
+++ b/test/spec/ol/parser/kml/multigeometry.kml
@@ -0,0 +1,27 @@
+
+
+
+ Polygon.kml
+ 0
+
+ SF Marina Harbor Master
+ 0
+
+
+
+
+ -122.4425587930444,37.80666418607323,0
+ -122.4428379594768,37.80663578323093,0
+
+
+
+
+
+ -122.4425509770566,37.80662588061205,0
+ -122.4428340530617,37.8065999493009,0
+
+
+
+
+
+
diff --git a/test/spec/ol/parser/kml/multigeometry_discrete.kml b/test/spec/ol/parser/kml/multigeometry_discrete.kml
new file mode 100644
index 0000000000..aae283b13c
--- /dev/null
+++ b/test/spec/ol/parser/kml/multigeometry_discrete.kml
@@ -0,0 +1,25 @@
+
+
+
+ Polygon.kml
+ 0
+
+ SF Marina Harbor Master
+ 0
+
+
+
+
+ -122.4425587930444,37.80666418607323,0
+ -122.4428379594768,37.80663578323093,0
+
+
+
+
+ -122.4428340530617,37.8065999493009,0
+
+
+
+
+
+
diff --git a/test/spec/ol/parser/kml/networklink.kml b/test/spec/ol/parser/kml/networklink.kml
new file mode 100644
index 0000000000..54ffa50a12
--- /dev/null
+++ b/test/spec/ol/parser/kml/networklink.kml
@@ -0,0 +1,15 @@
+
+
+
+
+ Simple placemark
+ Attached to the ground. Intelligently places itself
+ at the height of the underlying terrain.
+
+ -122.0822035425683,37.42228990140251,0
+
+
+ spec/ol/parser/kml/polygon.kml
+ spec/ol/parser/kml/point.kml
+
+
diff --git a/test/spec/ol/parser/kml/networklink_depth.kml b/test/spec/ol/parser/kml/networklink_depth.kml
new file mode 100644
index 0000000000..2bf62c81fd
--- /dev/null
+++ b/test/spec/ol/parser/kml/networklink_depth.kml
@@ -0,0 +1,14 @@
+
+
+
+
+ Simple placemark
+ Attached to the ground. Intelligently places itself
+ at the height of the underlying terrain.
+
+ -122.0822035425683,37.42228990140251,0
+
+
+ spec/ol/parser/kml/depth.kml
+
+
diff --git a/test/spec/ol/parser/kml/point.kml b/test/spec/ol/parser/kml/point.kml
new file mode 100644
index 0000000000..f95894ff73
--- /dev/null
+++ b/test/spec/ol/parser/kml/point.kml
@@ -0,0 +1,11 @@
+
+
+
+ Simple placemark
+ Attached to the ground. Intelligently places itself
+ at the height of the underlying terrain.
+
+ -122.0822035425683,37.42228990140251,0
+
+
+
diff --git a/test/spec/ol/parser/kml/polygon.kml b/test/spec/ol/parser/kml/polygon.kml
new file mode 100644
index 0000000000..68c44eb176
--- /dev/null
+++ b/test/spec/ol/parser/kml/polygon.kml
@@ -0,0 +1,36 @@
+
+
+
+ Polygon.kml
+ 0
+
+ hollow box
+
+ 1
+ relativeToGround
+
+
+
+ -122.366278,37.818844,30
+ -122.365248,37.819267,30
+ -122.365640,37.819861,30
+ -122.366669,37.819429,30
+ -122.366278,37.818844,30
+
+
+
+
+
+
+ -122.366212,37.818977,30
+ -122.365424,37.819294,30
+ -122.365704,37.819731,30
+ -122.366488,37.819402,30
+ -122.366212,37.818977,30
+
+
+
+
+
+
+