diff --git a/src/ol/format/kml.js b/src/ol/format/kml.js index 0f38f6ecf2..98194dd9e0 100644 --- a/src/ol/format/kml.js +++ b/src/ol/format/kml.js @@ -543,9 +543,7 @@ ol.format.KML.readStyleMapValue_ = function(node, objectStack) { return ol.xml.pushParseAndPop(undefined, ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack); }; - - -/** + /** * @param {Node} node Node. * @param {Array.<*>} objectStack Object stack. * @private @@ -1166,14 +1164,13 @@ ol.format.KML.DataParser_ = function(node, objectStack) { 'node.nodeType should be ELEMENT'); ol.DEBUG && console.assert(node.localName == 'Data', 'localName should be Data'); var name = node.getAttribute('name'); + ol.xml.parseNode(ol.format.KML.DATA_PARSERS_, node, objectStack); + var featureObject = + /** @type {Object} */ (objectStack[objectStack.length - 1]); if (name !== null) { - var data = ol.xml.pushParseAndPop( - undefined, ol.format.KML.DATA_PARSERS_, node, objectStack); - if (data) { - var featureObject = - /** @type {Object} */ (objectStack[objectStack.length - 1]); - featureObject[name] = data; - } + featureObject[name] = featureObject.value; + } else if (featureObject.displayName !== null) { + featureObject[featureObject.displayName] = featureObject.value; } }; @@ -1191,6 +1188,18 @@ ol.format.KML.ExtendedDataParser_ = function(node, objectStack) { ol.xml.parseNode(ol.format.KML.EXTENDED_DATA_PARSERS_, node, objectStack); }; +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.RegionParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Region', + 'localName should be Region'); + ol.xml.parseNode(ol.format.KML.REGION_PARSERS_, node, objectStack); +}; /** * @param {Node} node Node. @@ -1282,6 +1291,56 @@ ol.format.KML.SimpleDataParser_ = function(node, objectStack) { }; +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LatLonAltBoxParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'LatLonAltBox', + 'localName should be LatLonAltBox'); + var object = ol.xml.pushParseAndPop({}, ol.format.KML.LAT_LON_ALT_BOX_PARSERS_, node, objectStack); + if (!object) { + return; + } + var regionObject = /** @type {Object} */ (objectStack[objectStack.length - 1]); + var extent = [ + parseFloat(object['west']), + parseFloat(object['south']), + parseFloat(object['east']), + parseFloat(object['north']) + ]; + regionObject['extent'] = extent; + regionObject['altitudeMode'] = object['altitudeMode']; + regionObject['minAltitude'] = parseFloat(object['minAltitude']); + regionObject['maxAltitude'] = parseFloat(object['maxAltitude']); +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.LodParser_ = function(node, objectStack) { + ol.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE, + 'node.nodeType should be ELEMENT'); + ol.DEBUG && console.assert(node.localName == 'Lod', + 'localName should be Lod'); + var object = ol.xml.pushParseAndPop({}, ol.format.KML.LOD_PARSERS_, node, objectStack); + if (!object) { + return; + } + var lodObject = /** @type {Object} */ (objectStack[objectStack.length - 1]); + lodObject['minLodPixels'] = parseFloat(object['minLodPixels']); + lodObject['maxLodPixels'] = parseFloat(object['maxLodPixels']); + lodObject['minFadeExtent'] = parseFloat(object['minFadeExtent']); + lodObject['maxFadeExtent'] = parseFloat(object['maxFadeExtent']); +}; + + /** * @param {Node} node Node. * @param {Array.<*>} objectStack Object stack. @@ -1370,7 +1429,8 @@ ol.format.KML.whenParser_ = function(node, objectStack) { */ ol.format.KML.DATA_PARSERS_ = ol.xml.makeStructureNS( ol.format.KML.NAMESPACE_URIS_, { - 'value': ol.xml.makeReplacer(ol.format.XSD.readString) + 'displayName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'value': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) }); @@ -1386,6 +1446,49 @@ ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeStructureNS( }); +/** + * @const + * @type {Object.>} + * @private + */ +ol.format.KML.REGION_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'LatLonAltBox': ol.format.KML.LatLonAltBoxParser_, + 'Lod': ol.format.KML.LodParser_ + }); + + +/** + * @const + * @type {Object.>} + * @private + */ +ol.format.KML.LAT_LON_ALT_BOX_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'altitudeMode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'minAltitude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'maxAltitude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'north': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'south': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'east': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'west': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + }); + + +/** + * @const + * @type {Object.>} + * @private + */ +ol.format.KML.LOD_PARSERS_ = ol.xml.makeStructureNS( + ol.format.KML.NAMESPACE_URIS_, { + 'minLodPixels': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'maxLodPixels': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'minFadeExtent': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), + 'maxFadeExtent': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal) + }); + + /** * @const * @type {Object.>} @@ -1546,6 +1649,7 @@ ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS( ol.format.KML.NETWORK_LINK_PARSERS_ = ol.xml.makeStructureNS( ol.format.KML.NAMESPACE_URIS_, { 'ExtendedData': ol.format.KML.ExtendedDataParser_, + 'Region': ol.format.KML.RegionParser_, 'Link': ol.format.KML.LinkParser_, 'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), 'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), @@ -1599,6 +1703,7 @@ ol.format.KML.PAIR_PARSERS_ = ol.xml.makeStructureNS( ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeStructureNS( ol.format.KML.NAMESPACE_URIS_, { 'ExtendedData': ol.format.KML.ExtendedDataParser_, + 'Region': ol.format.KML.RegionParser_, 'MultiGeometry': ol.xml.makeObjectPropertySetter( ol.format.KML.readMultiGeometry_, 'geometry'), 'LineString': ol.xml.makeObjectPropertySetter( @@ -2044,6 +2149,72 @@ ol.format.KML.prototype.readNetworkLinksFromNode = function(node) { }; +/** + * Read the regions of the KML. + * + * @param {Document|Node|string} source Source. + * @return {Array.} Regions. + * @api + */ +ol.format.KML.prototype.readRegion = function(source) { + var regions = []; + if (ol.xml.isDocument(source)) { + ol.array.extend(regions, this.readRegionFromDocument( + /** @type {Document} */ (source))); + } else if (ol.xml.isNode(source)) { + ol.array.extend(regions, this.readRegionFromNode( + /** @type {Node} */ (source))); + } else if (typeof source === 'string') { + var doc = ol.xml.parse(source); + ol.array.extend(regions, this.readRegionFromDocument(doc)); + } + return regions; +}; + + +/** + * @param {Document} doc Document. + * @return {Array.} Region. + */ +ol.format.KML.prototype.readRegionFromDocument = function(doc) { + var n, regions = []; + for (n = doc.firstChild; n; n = n.nextSibling) { + if (n.nodeType == Node.ELEMENT_NODE) { + ol.array.extend(regions, this.readRegionFromNode(n)); + } + } + return regions; +}; + + +/** + * @param {Node} node Node. + * @return {Array.} Region. + * @api + */ +ol.format.KML.prototype.readRegionFromNode = function(node) { + var n, regions = []; + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + n.localName == 'Region') { + var obj = ol.xml.pushParseAndPop({}, ol.format.KML.REGION_PARSERS_, + n, []); + regions.push(obj); + } + } + for (n = node.firstElementChild; n; n = n.nextElementSibling) { + var localName = n.localName; + if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) && + (localName == 'Document' || + localName == 'Folder' || + localName == 'kml')) { + ol.array.extend(regions, this.readRegionFromNode(n)); + } + } + return regions; +}; + + /** * Read the projection from a KML source. * diff --git a/test/spec/ol/format/kml.test.js b/test/spec/ol/format/kml.test.js index 36f7a2aa2e..ce144eadd1 100644 --- a/test/spec/ol/format/kml.test.js +++ b/test/spec/ol/format/kml.test.js @@ -817,6 +817,84 @@ describe('ol.format.KML', function() { expect(node).to.xmleql(ol.xml.parse(text)); }); + it('can read MultiPolygon geometries', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ' 0' + + ' absolute' + + ' ' + + ' ' + + ' 0,0,0 0,1,0 1,1,0 1,0,0' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 3,0,0 3,1,0 4,1,0 4,0,0' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + var fs = format.readFeatures(text); + expect(fs).to.have.length(1); + var f = fs[0]; + expect(f).to.be.an(ol.Feature); + var g = f.getGeometry(); + expect(g).to.be.an(ol.geom.MultiPolygon); + expect(g.getCoordinates()).to.eql( + [[[[0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0]]], + [[[3, 0, 0], [3, 1, 0], [4, 1, 0], [4, 0, 0]]]]); + expect(g.get('extrude')).to.be.an('array'); + expect(g.get('extrude')).to.have.length(2); + expect(g.get('extrude')[0]).to.be(false); + expect(g.get('extrude')[1]).to.be(undefined); + expect(g.get('altitudeMode')).to.be.an('array'); + expect(g.get('altitudeMode')).to.have.length(2); + expect(g.get('altitudeMode')[0]).to.be('absolute'); + expect(g.get('altitudeMode')[1]).to.be(undefined); + }); + + it('can write MultiPolygon geometries', function() { + var layout = 'XYZ'; + var multiPolygon = new ol.geom.MultiPolygon( + [[[[0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0]]], + [[[3, 0, 0], [3, 1, 0], [4, 1, 0], [4, 0, 0]]]], layout); + var features = [new ol.Feature(multiPolygon)]; + var node = format.writeFeaturesNode(features); + var text = + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 0,0,0 0,1,0 1,1,0 1,0,0' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 3,0,0 3,1,0 4,1,0 4,0,0' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + expect(node).to.xmleql(ol.xml.parse(text)); + }); + it('can read MultiPoint geometries', function() { var text = '' + @@ -1342,6 +1420,71 @@ describe('ol.format.KML', function() { }); + describe('region', function() { + + it('can read Region', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 43.651015' + + ' 43.540908' + + ' 1.514582' + + ' 1.384133' + + ' 133.57' + + ' 146.16' + + ' relativeToGround' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + var fs = format.readFeatures(text); + expect(fs).to.have.length(1); + var f = fs[0]; + expect(f).to.be.an(ol.Feature); + var extent = f.get('extent'); + expect(extent).to.be.an(Array); + expect(extent).to.have.length(4); + expect(extent[0]).to.be(1.384133); + expect(extent[1]).to.be(43.540908); + expect(extent[2]).to.be(1.514582); + expect(extent[3]).to.be(43.651015); + expect(f.get('altitudeMode')).to.be('relativeToGround'); + expect(f.get('minAltitude')).to.be(133.57); + expect(f.get('maxAltitude')).to.be(146.16); + }); + + it('can read Lod', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 128' + + ' 2048' + + ' 0.2' + + ' 10.5' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + var fs = format.readFeatures(text); + expect(fs).to.have.length(1); + var f = fs[0]; + expect(f).to.be.an(ol.Feature); + expect(f.get('minLodPixels')).to.be(128); + expect(f.get('maxLodPixels')).to.be(2048); + expect(f.get('minFadeExtent')).to.be(0.2); + expect(f.get('maxFadeExtent')).to.be(10.5); + }); + + }); + describe('extended data', function() { it('can read ExtendedData', function() { @@ -1362,6 +1505,25 @@ describe('ol.format.KML', function() { expect(f.get('foo')).to.be('bar'); }); + it('can read ExtendedData with displayName instead of name', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ' foo' + + ' bar' + + ' ' + + ' ' + + ' ' + + ''; + var fs = format.readFeatures(text); + expect(fs).to.have.length(1); + var f = fs[0]; + expect(f).to.be.an(ol.Feature); + expect(f.get('foo')).to.be('bar'); + }); + it('can read SchemaData', function() { var text = '' + @@ -1381,6 +1543,30 @@ describe('ol.format.KML', function() { expect(f.get('capital')).to.be('London'); expect(f.get('population')).to.be('60000000'); }); + + it('can read ExtendedData with displayName when name undefined', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ' capital' + + ' London' + + ' ' + + ' ' + + ' Country' + + ' United-Kingdom' + + ' /Data>' + + ' ' + + ' ' + + ''; + var fs = format.readFeatures(text); + expect(fs).to.have.length(1); + var f = fs[0]; + expect(f).to.be.an(ol.Feature); + expect(f.get('capital')).to.be('London'); + expect(f.get('country')).to.be('United-Kingdom'); + }); }); describe('styles', function() { @@ -2935,7 +3121,64 @@ describe('ol.format.KML', function() { expect(/\/bar\/bar\.kml$/.test(nl[0].href)).to.be.ok(); expect(nl[1].href).to.be('http://foo.com/foo.kml'); }); + }); + describe('#readRegion', function() { + + it('returns an array of regions', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ' 0' + + ' -90' + + ' 0' + + ' -180' + + ' 0' + + ' 4000' + + ' clampToGround' + + ' ' + + ' ' + + ' 0' + + ' -1' + + ' 0' + + ' 0' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' 90' + + ' 0' + + ' 180' + + ' 0' + + ' 0' + + ' 0' + + ' clampToGround' + + ' ' + + ' ' + + ' 0' + + ' -1' + + ' 0' + + ' 0' + + ' ' + + ' ' + + ' ' + + ''; + var nl = format.readRegion(text); + expect(nl).to.have.length(2); + expect(nl[0].extent).to.eql([-180, -90, 0, 0]); + expect(nl[0].minAltitude).to.be(0); + expect(nl[0].maxAltitude).to.be(4000); + expect(nl[0].altitudeMode).to.be('clampToGround'); + expect(nl[0].minLodPixels).to.be(0); + expect(nl[0].maxLodPixels).to.be(-1); + expect(nl[0].minFadeExtent).to.be(0); + expect(nl[0].maxFadeExtent).to.be(0); + expect(nl[1].extent).to.eql([0,0,180,90]); + }); }); }); });