From d42a2628144d8e09911fc9ce69c40b27e4a460bf Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 26 Feb 2014 16:08:01 +0100 Subject: [PATCH 1/4] Add out-of-order shared Style and StyleMap test --- test/spec/ol/format/kmlformat.test.js | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/spec/ol/format/kmlformat.test.js b/test/spec/ol/format/kmlformat.test.js index 141f0c0f19..25986592bb 100644 --- a/test/spec/ol/format/kmlformat.test.js +++ b/test/spec/ol/format/kmlformat.test.js @@ -984,6 +984,41 @@ describe('ol.format.KML', function() { expect(s).to.be(ol.format.KML.DEFAULT_STYLE_); }); + it('can use Styles in StyleMaps before they are defined', function() { + var text = + '' + + ' ' + + ' ' + + ' ' + + ' normal' + + ' #foo' + + ' ' + + ' ' + + ' ' + + ' ' + + ' #fooMap' + + ' ' + + ' ' + + ''; + var fs = format.readFeatures(text); + expect(fs).to.have.length(1); + var f = fs[0]; + expect(f).to.be.an(ol.Feature); + var styleFunction = f.getStyleFunction(); + expect(styleFunction).not.to.be(undefined); + var styleArray = styleFunction.call(f, 0); + expect(styleArray).to.be.an(Array); + expect(styleArray).to.have.length(1); + var s = styleArray[0]; + expect(s).to.be.an(ol.style.Style); + expect(s.getFill()).not.to.be(null); + expect(s.getFill().getColor()).to.eql([120, 86, 52, 18 / 255]); + }); + }); describe('shared styles', function() { From 42c832154bceffff4d011ce9bf2a355cc3c45abf Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 26 Feb 2014 16:14:11 +0100 Subject: [PATCH 2/4] Refactor KML style handling to handle shared Styles and StyleMaps --- src/ol/format/kmlformat.js | 226 +++++++++++++++---------------------- 1 file changed, 88 insertions(+), 138 deletions(-) diff --git a/src/ol/format/kmlformat.js b/src/ol/format/kmlformat.js index 8681ba651e..d0738906ce 100644 --- a/src/ol/format/kmlformat.js +++ b/src/ol/format/kmlformat.js @@ -1,6 +1,3 @@ -// FIXME add Styles with ids to sharedStyles_ -// FIXME refactor StyleMap handling -// FIXME handle highlighted keys in StyleMaps - use styleFunctions // FIXME http://earth.google.com/kml/1.0 namespace? // FIXME why does node.getAttribute return an unknown type? // FIXME text @@ -37,12 +34,6 @@ goog.require('ol.style.Style'); goog.require('ol.xml'); -/** - * @define {boolean} Respect visibility. - */ -ol.KML_RESPECT_VISIBILITY = false; - - /** * @typedef {{x: number, xunits: (ol.style.IconAnchorUnits|undefined), * y: number, yunits: (ol.style.IconAnchorUnits|undefined)}} @@ -73,32 +64,28 @@ ol.format.KML = function(opt_options) { var defaultStyle = goog.isDef(options.defaultStyle) ? options.defaultStyle : ol.format.KML.DEFAULT_STYLE_ARRAY_; - /** - * @private - * @type {ol.feature.FeatureStyleFunction} - */ - this.defaultFeatureStyleFunction_ = - /** - * @param {number} resolution Resolution. - * @this {ol.Feature} - * @return {Array.} Style. - */ - function(resolution) { - if (ol.KML_RESPECT_VISIBILITY) { - var visibility = this.get('visibility'); - if (goog.isDef(visibility) && !visibility) { - return null; - } - } - return defaultStyle; - }; - - /** @type {Object.>} */ + /** @type {Object.|string)>} */ var sharedStyles = {}; + var findStyle = + /** + * @param {Array.|string|undefined} styleValue Style + * value. + * @return {Array.} Style. + */ + function(styleValue) { + if (goog.isArray(styleValue)) { + return styleValue; + } else if (goog.isString(styleValue)) { + return findStyle(sharedStyles[styleValue]); + } else { + return defaultStyle; + } + }; + /** * @private - * @type {Object.>} + * @type {Object.|string)>} */ this.sharedStyles_ = sharedStyles; @@ -106,27 +93,23 @@ ol.format.KML = function(opt_options) { * @private * @type {ol.feature.FeatureStyleFunction} */ - this.sharedStyleFeatureStyleFunction_ = + this.featureStyleFunction_ = /** * @param {number} resolution Resolution. * @return {Array.} Style. * @this {ol.Feature} */ function(resolution) { - if (ol.KML_RESPECT_VISIBILITY) { - var visibility = this.get('visibility'); - if (goog.isDef(visibility) && !visibility) { - return null; - } - } - var styleUrl = /** @type {string|undefined} */ (this.get('styleUrl')); - goog.asserts.assert(goog.isDef(styleUrl)); - var style = sharedStyles[styleUrl]; + var style = /** @type {Array.|undefined} */ + (this.get('Style')); if (goog.isDef(style)) { return style; - } else { - return null; } + var styleUrl = /** @type {string|undefined} */ (this.get('styleUrl')); + if (goog.isDef(styleUrl)) { + return findStyle(styleUrl); + } + return defaultStyle; }; }; @@ -287,32 +270,6 @@ ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = { }; -/** - * @param {ol.style.Style} style Style. - * @private - * @return {ol.feature.FeatureStyleFunction} Feature style function. - */ -ol.format.KML.makeFeatureStyleFunction_ = function(style) { - // FIXME handle styleMap? - var styleArray = [style]; - return ( - /** - * @param {number} resolution Resolution. - * @return {Array.} Style. - * @this {ol.Feature} - */ - function(resolution) { - if (ol.KML_RESPECT_VISIBILITY) { - var visibility = this.get('visibility'); - if (goog.isDef(visibility) && !visibility) { - return null; - } - } - return styleArray; - }); -}; - - /** * @param {Node} node Node. * @private @@ -428,6 +385,19 @@ ol.format.KML.readScale_ = function(node) { }; +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {Array.|string|undefined} StyleMap. + */ +ol.format.KML.readStyleMapValue_ = function(node, objectStack) { + return ol.xml.pushParseAndPop( + /** @type {Array.|string|undefined} */ (undefined), + ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack); +}; + + /** * @param {Node} node Node. * @param {Array.<*>} objectStack Object stack. @@ -868,7 +838,7 @@ ol.format.KML.readPolygon_ = function(node, objectStack) { * @param {Node} node Node. * @param {Array.<*>} objectStack Object stack. * @private - * @return {ol.style.Style} Style. + * @return {Array.} Style. */ ol.format.KML.readStyle_ = function(node, objectStack) { goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); @@ -894,14 +864,13 @@ ol.format.KML.readStyle_ = function(node, objectStack) { if (goog.isDef(outline) && !outline) { strokeStyle = null; } - var style = new ol.style.Style({ + return [new ol.style.Style({ fill: fillStyle, image: imageStyle, stroke: strokeStyle, text: null, // FIXME zIndex: undefined // FIXME - }); - return style; + })]; }; @@ -955,18 +924,40 @@ ol.format.KML.PairDataParser_ = function(node, objectStack) { var key = /** @type {string|undefined} */ (goog.object.get(pairObject, 'key')); if (goog.isDef(key) && key == 'normal') { - var featureObject = /** @type {Object} */ - (objectStack[objectStack.length - 1]); - var Style = /** @type {ol.style.Style} */ - (goog.object.get(pairObject, 'Style', null)); - if (!goog.isNull(Style)) { - goog.object.set(featureObject, 'Style', Style); - } var styleUrl = /** @type {string|undefined} */ (goog.object.get(pairObject, 'styleUrl')); if (goog.isDef(styleUrl)) { - goog.object.set(featureObject, 'styleUrl', styleUrl); + objectStack[objectStack.length - 1] = styleUrl; } + var Style = /** @type {ol.style.Style} */ + (goog.object.get(pairObject, 'Style')); + if (goog.isDef(Style)) { + objectStack[objectStack.length - 1] = Style; + } + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.PlacemarkStyleMapParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); + goog.asserts.assert(node.localName == 'StyleMap'); + var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack); + if (!goog.isDef(styleMapValue)) { + return; + } + var placemarkObject = objectStack[objectStack.length - 1]; + goog.asserts.assert(goog.isObject(placemarkObject)); + if (goog.isArray(styleMapValue)) { + goog.object.set(placemarkObject, 'Style', styleMapValue); + } else if (goog.isString(styleMapValue)) { + goog.object.set(placemarkObject, 'styleUrl', styleMapValue); + } else { + goog.asserts.fail(); } }; @@ -1001,18 +992,6 @@ ol.format.KML.SimpleDataParser_ = function(node, objectStack) { }; -/** - * @param {Node} node Node. - * @param {Array.<*>} objectStack Object stack. - * @private - */ -ol.format.KML.StyleMapParser_ = function(node, objectStack) { - goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); - goog.asserts.assert(node.localName == 'StyleMap'); - ol.xml.parse(ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack); -}; - - /** * @param {Node} node Node. * @param {Array.<*>} objectStack Object stack. @@ -1281,7 +1260,7 @@ ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeParsersNS( 'Polygon': ol.xml.makeObjectPropertySetter( ol.format.KML.readPolygon_, 'geometry'), 'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_), - 'StyleMap': ol.format.KML.StyleMapParser_, + 'StyleMap': ol.format.KML.PlacemarkStyleMapParser_, 'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), 'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), 'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), @@ -1397,26 +1376,13 @@ ol.format.KML.prototype.readPlacemark_ = function(node, objectStack) { if (!goog.isDef(object)) { return undefined; } - var style = /** @type {ol.style.Style} */ - (goog.object.get(object, 'Style', null)); - goog.object.remove(object, 'Style'); var feature = new ol.Feature(); var id = node.getAttribute('id'); if (!goog.isNull(id)) { feature.setId(id); } feature.setValues(object); - var styleUrl = /** @type {string|undefined} */ - (goog.object.get(object, 'styleUrl')); - var featureStyleFunction; - if (goog.isDef(styleUrl)) { - featureStyleFunction = this.sharedStyleFeatureStyleFunction_; - } else if (goog.isNull(style)) { - featureStyleFunction = this.defaultFeatureStyleFunction_; - } else { - featureStyleFunction = ol.format.KML.makeFeatureStyleFunction_(style); - } - feature.setStyle(featureStyleFunction); + feature.setStyle(this.featureStyleFunction_); return feature; }; @@ -1439,7 +1405,7 @@ ol.format.KML.prototype.readSharedStyle_ = function(node, objectStack) { } else { styleUri = '#' + id; } - this.sharedStyles_[styleUri] = [style]; + this.sharedStyles_[styleUri] = style; } } }; @@ -1454,36 +1420,20 @@ ol.format.KML.prototype.readSharedStyleMap_ = function(node, objectStack) { goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); goog.asserts.assert(node.localName == 'StyleMap'); var id = node.getAttribute('id'); - if (!goog.isNull(id)) { - var styleObject = ol.xml.pushParseAndPop(/** @type {Object} */ ({}), - ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack); - if (!goog.isDef(styleObject)) { - return; - } - var style = /** @type {ol.style.Style} */ - (goog.object.get(styleObject, 'style', null)); - var styleUri; - if (goog.isDefAndNotNull(node.baseURI)) { - styleUri = goog.Uri.resolve(node.baseURI, '#' + id).toString(); - } else { - styleUri = '#' + id; - } - if (!goog.isNull(style)) { - this.sharedStyles_[styleUri] = [style]; - } - var styleUrl = /** @type {string|undefined} */ - (goog.object.get(styleObject, 'styleUrl')); - if (goog.isDef(styleUrl)) { - var styleUrlUri; - if (goog.isDefAndNotNull(node.baseURI)) { - styleUrlUri = goog.Uri.resolve(node.baseURI, styleUrl).toString(); - } else { - styleUrlUri = '#' + goog.string.trim(styleUrl).replace(/^#/, ''); - } - goog.asserts.assert(styleUrlUri in this.sharedStyles_); - this.sharedStyles_[styleUri] = this.sharedStyles_[styleUrlUri]; - } + if (goog.isNull(id)) { + return; } + var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack); + if (!goog.isDef(styleMapValue)) { + return; + } + var styleUri; + if (goog.isDefAndNotNull(node.baseURI)) { + styleUri = goog.Uri.resolve(node.baseURI, '#' + id).toString(); + } else { + styleUri = '#' + id; + } + this.sharedStyles_[styleUri] = styleMapValue; }; From b3c527166d6b37aabcaed7d9f4ac2bb5d820a738 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 26 Feb 2014 16:30:51 +0100 Subject: [PATCH 3/4] Be more tolerant of out-of-spec KML files --- src/ol/format/kmlformat.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ol/format/kmlformat.js b/src/ol/format/kmlformat.js index d0738906ce..e0e3929c43 100644 --- a/src/ol/format/kmlformat.js +++ b/src/ol/format/kmlformat.js @@ -77,6 +77,12 @@ ol.format.KML = function(opt_options) { if (goog.isArray(styleValue)) { return styleValue; } else if (goog.isString(styleValue)) { + // KML files in the wild occasionally forget the leading `#` on styleUrls + // defined in the same document. Add a leading `#` if it enables to find + // a style. + if (!(styleValue in sharedStyles) && ('#' + styleValue in sharedStyles)) { + styleValue = '#' + styleValue; + } return findStyle(sharedStyles[styleValue]); } else { return defaultStyle; From 7bbef0f0eab9f320bde5011481f2891afc8cd2a5 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 26 Feb 2014 16:44:10 +0100 Subject: [PATCH 4/4] Read Icon hrefs as URIs --- src/ol/format/kmlformat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/format/kmlformat.js b/src/ol/format/kmlformat.js index e0e3929c43..a370342317 100644 --- a/src/ol/format/kmlformat.js +++ b/src/ol/format/kmlformat.js @@ -1156,7 +1156,7 @@ ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeParsersNS( */ ol.format.KML.ICON_PARSERS_ = ol.xml.makeParsersNS( ol.format.KML.NAMESPACE_URIS_, { - 'href': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString) + 'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_) });