From ed9861317f22ec5782af6eb89ef58da80de3d714 Mon Sep 17 00:00:00 2001 From: Bart van den Eijnden Date: Tue, 9 Apr 2013 15:34:03 +0200 Subject: [PATCH] Add a parser for reading and writing KML. This adds ol.parser.ogc.KML which can be used to read and write KML documents. NetworkLinks are retrieved asynchronously. Current caveats of the implementation are: * LabelStyle not yet implemented. Missing support in renderers. * When using shared structures the parser needs to be configured with dimension 2. * We need a better way to disable fill, currently we use opacity as a workaround. * We cannot really roundtrip documents, since some of the info is not preserved in the ol structures. But we can write out most of the important info. --- examples/kml.html | 56 + examples/kml.js | 50 + examples/kml/lines.kml | 275 +++++ examples/kml/styles.kml | 21 + src/ol/layer/vectorlayer.js | 55 +- src/ol/parser/featureparser.js | 18 + src/ol/parser/ogc/kml.exports | 3 + src/ol/parser/ogc/kml.js | 1041 +++++++++++++++++ src/ol/parser/xml.js | 97 ++ src/ol/style/icon.js | 2 +- test/spec/ol/parser/ogc/kml.test.js | 289 +++++ test/spec/ol/parser/ogc/xml/kml_depth.kml | 6 + .../ol/parser/ogc/xml/kml_extended_data.kml | 16 + .../ol/parser/ogc/xml/kml_extended_data2.kml | 30 + test/spec/ol/parser/ogc/xml/kml_iconstyle.kml | 23 + .../spec/ol/parser/ogc/xml/kml_linestring.kml | 36 + test/spec/ol/parser/ogc/xml/kml_macnoise.kml | 1038 ++++++++++++++++ .../ol/parser/ogc/xml/kml_multigeometry.kml | 27 + .../ogc/xml/kml_multigeometry_discrete.kml | 25 + .../ol/parser/ogc/xml/kml_networklink.kml | 15 + .../parser/ogc/xml/kml_networklink_depth.kml | 14 + test/spec/ol/parser/ogc/xml/kml_point.kml | 11 + test/spec/ol/parser/ogc/xml/kml_polygon.kml | 36 + 23 files changed, 3161 insertions(+), 23 deletions(-) create mode 100644 examples/kml.html create mode 100644 examples/kml.js create mode 100644 examples/kml/lines.kml create mode 100644 examples/kml/styles.kml create mode 100644 src/ol/parser/ogc/kml.exports create mode 100644 src/ol/parser/ogc/kml.js create mode 100644 test/spec/ol/parser/ogc/kml.test.js create mode 100644 test/spec/ol/parser/ogc/xml/kml_depth.kml create mode 100644 test/spec/ol/parser/ogc/xml/kml_extended_data.kml create mode 100644 test/spec/ol/parser/ogc/xml/kml_extended_data2.kml create mode 100644 test/spec/ol/parser/ogc/xml/kml_iconstyle.kml create mode 100644 test/spec/ol/parser/ogc/xml/kml_linestring.kml create mode 100644 test/spec/ol/parser/ogc/xml/kml_macnoise.kml create mode 100644 test/spec/ol/parser/ogc/xml/kml_multigeometry.kml create mode 100644 test/spec/ol/parser/ogc/xml/kml_multigeometry_discrete.kml create mode 100644 test/spec/ol/parser/ogc/xml/kml_networklink.kml create mode 100644 test/spec/ol/parser/ogc/xml/kml_networklink_depth.kml create mode 100644 test/spec/ol/parser/ogc/xml/kml_point.kml create mode 100644 test/spec/ol/parser/ogc/xml/kml_polygon.kml 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.

+
+

See the kml.js source to see how this is done.

+
+
KML
+
+ +
+ +
+ + + + + + + diff --git a/examples/kml.js b/examples/kml.js new file mode 100644 index 0000000000..7414f080d8 --- /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.ogc.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.ogc.KML({ + maxDepth: 1, dimension: 2, extractStyles: true, extractAttributes: true}); + +$.ajax({ + url: 'kml/lines.kml' +}).done(function(data) { + vector.parseFeatures(data, kml, epsg4326); +}); diff --git a/examples/kml/lines.kml b/examples/kml/lines.kml new file mode 100644 index 0000000000..5999aaa131 --- /dev/null +++ b/examples/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. + kml/styles.kml#blueIcons + + -112.292238941097,36.09520916122063,630 + + + + + diff --git a/examples/kml/styles.kml b/examples/kml/styles.kml new file mode 100644 index 0000000000..24350ade44 --- /dev/null +++ b/examples/kml/styles.kml @@ -0,0 +1,21 @@ + + + + + + + + + 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/ogc/kml.exports b/src/ol/parser/ogc/kml.exports new file mode 100644 index 0000000000..de5b20774b --- /dev/null +++ b/src/ol/parser/ogc/kml.exports @@ -0,0 +1,3 @@ +@exportSymbol ol.parser.ogc.KML +@exportProperty ol.parser.ogc.KML.prototype.read +@exportProperty ol.parser.ogc.KML.prototype.write diff --git a/src/ol/parser/ogc/kml.js b/src/ol/parser/ogc/kml.js new file mode 100644 index 0000000000..588b05b67b --- /dev/null +++ b/src/ol/parser/ogc/kml.js @@ -0,0 +1,1041 @@ +goog.provide('ol.parser.ogc.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.ogc.KMLOptions; + + + +/** + * @constructor + * @implements {ol.parser.DomFeatureParser} + * @implements {ol.parser.StringFeatureParser} + * @implements {ol.parser.AsyncObjectFeatureParser} + * @param {ol.parser.ogc.KMLOptions=} opt_options Optional configuration object. + * @extends {ol.parser.XML} + */ +ol.parser.ogc.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.ogc.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.ogc.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.ogc.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.ogc.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.ogc.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.ogc.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(); + 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.ogc.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.ogc.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.ogc.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.ogc.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..5c40d9d636 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 (window.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/ogc/kml.test.js b/test/spec/ol/parser/ogc/kml.test.js new file mode 100644 index 0000000000..5a88a6fc77 --- /dev/null +++ b/test/spec/ol/parser/ogc/kml.test.js @@ -0,0 +1,289 @@ +goog.provide('ol.test.parser.ogc.kml'); + +describe('ol.parser.kml', function() { + + var parser = new ol.parser.ogc.KML(); + + describe('Test KML parser', function() { + it('Polygon read correctly', function() { + var url = 'spec/ol/parser/ogc/xml/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/ogc/xml/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/ogc/xml/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/ogc/xml/kml_networklink.kml'; + afterLoadXml(url, function(xml) { + var p = new ol.parser.ogc.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/ogc/xml/kml_networklink_depth.kml'; + afterLoadXml(url, function(xml) { + var p = new ol.parser.ogc.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/ogc/xml/kml_networklink_depth.kml'; + afterLoadXml(url, function(xml) { + var p = new ol.parser.ogc.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/ogc/xml/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/ogc/xml/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/ogc/xml/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/ogc/xml/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/ogc/xml/kml_macnoise.kml'; + afterLoadXml(url, function(xml) { + var p = new ol.parser.ogc.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.ogc.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.ogc.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/ogc/xml/kml_iconstyle.kml'; + afterLoadXml(url, function(xml) { + var p = new ol.parser.ogc.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.ogc.KML'); +goog.require('ol.style.LineLiteral'); diff --git a/test/spec/ol/parser/ogc/xml/kml_depth.kml b/test/spec/ol/parser/ogc/xml/kml_depth.kml new file mode 100644 index 0000000000..09c48be4bf --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/kml_depth.kml @@ -0,0 +1,6 @@ + + + + spec/ol/parser/ogc/xml/kml_polygon.kml + + diff --git a/test/spec/ol/parser/ogc/xml/kml_extended_data.kml b/test/spec/ol/parser/ogc/xml/kml_extended_data.kml new file mode 100644 index 0000000000..6f23151b7f --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/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/ogc/xml/kml_extended_data2.kml b/test/spec/ol/parser/ogc/xml/kml_extended_data2.kml new file mode 100644 index 0000000000..a5e1d4d49f --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/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/ogc/xml/kml_iconstyle.kml b/test/spec/ol/parser/ogc/xml/kml_iconstyle.kml new file mode 100644 index 0000000000..493ac02094 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/kml_iconstyle.kml @@ -0,0 +1,23 @@ + + + + + Pin on a mountaintop + #pushpin + + 170.1435558771009,-43.60505741890396,0 + + + + diff --git a/test/spec/ol/parser/ogc/xml/kml_linestring.kml b/test/spec/ol/parser/ogc/xml/kml_linestring.kml new file mode 100644 index 0000000000..01a941a07b --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/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/ogc/xml/kml_macnoise.kml b/test/spec/ol/parser/ogc/xml/kml_macnoise.kml new file mode 100644 index 0000000000..f544b48fde --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/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/ogc/xml/kml_multigeometry.kml b/test/spec/ol/parser/ogc/xml/kml_multigeometry.kml new file mode 100644 index 0000000000..d24379886f --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/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/ogc/xml/kml_multigeometry_discrete.kml b/test/spec/ol/parser/ogc/xml/kml_multigeometry_discrete.kml new file mode 100644 index 0000000000..aae283b13c --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/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/ogc/xml/kml_networklink.kml b/test/spec/ol/parser/ogc/xml/kml_networklink.kml new file mode 100644 index 0000000000..ca99d17ee8 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/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/ogc/xml/kml_polygon.kml + spec/ol/parser/ogc/xml/kml_point.kml + + diff --git a/test/spec/ol/parser/ogc/xml/kml_networklink_depth.kml b/test/spec/ol/parser/ogc/xml/kml_networklink_depth.kml new file mode 100644 index 0000000000..d54d778797 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/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/ogc/xml/kml_depth.kml + + diff --git a/test/spec/ol/parser/ogc/xml/kml_point.kml b/test/spec/ol/parser/ogc/xml/kml_point.kml new file mode 100644 index 0000000000..f95894ff73 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/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/ogc/xml/kml_polygon.kml b/test/spec/ol/parser/ogc/xml/kml_polygon.kml new file mode 100644 index 0000000000..68c44eb176 --- /dev/null +++ b/test/spec/ol/parser/ogc/xml/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 + + + + + + +