diff --git a/src/ol/format/kml.js b/src/ol/format/kml.js
index 7e14f23dee..39dd52d934 100644
--- a/src/ol/format/kml.js
+++ b/src/ol/format/kml.js
@@ -2278,6 +2278,54 @@ ol.format.KML.writeCoordinatesTextNode_ = function(node, coordinates, objectStac
};
+/**
+ * @param {Node} node Node.
+ * @param {{name: *, value: *}} pair Name value pair.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writeDataNode_ = function(node, pair, objectStack) {
+ node.setAttribute('name', pair.name);
+ var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
+ var value = pair.value;
+
+ if (typeof value == 'object') {
+ if (value !== null && value.displayName) {
+ ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_,
+ ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value.displayName], objectStack, ['displayName']);
+ }
+
+ if (value !== null && value.value) {
+ ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_,
+ ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value.value], objectStack, ['value']);
+ }
+ } else {
+ ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_,
+ ol.xml.OBJECT_PROPERTY_NODE_FACTORY, [value], objectStack, ['value']);
+ }
+};
+
+
+/**
+ * @param {Node} node Node to append a TextNode with the name to.
+ * @param {string} name DisplayName.
+ * @private
+ */
+ol.format.KML.writeDataNodeName_ = function(node, name) {
+ ol.format.XSD.writeCDATASection(node, name);
+};
+
+
+/**
+ * @param {Node} node Node to append a CDATA Section with the value to.
+ * @param {string} value Value.
+ * @private
+ */
+ol.format.KML.writeDataNodeValue_ = function(node, value) {
+ ol.format.XSD.writeStringTextNode(node, value);
+};
+
+
/**
* @param {Node} node Node.
* @param {Array.
} features Features.
@@ -2293,6 +2341,24 @@ ol.format.KML.writeDocument_ = function(node, features, objectStack) {
};
+/**
+ * @param {Node} node Node.
+ * @param {{names: Array, values: (Array<*>)}} namesAndValues Names and values.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.KML.writeExtendedData_ = function(node, namesAndValues, objectStack) {
+ var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
+ var names = namesAndValues.names, values = namesAndValues.values;
+ var length = names.length;
+
+ for (var i = 0; i < length; i++) {
+ ol.xml.pushSerializeAndPop(context, ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_,
+ ol.format.KML.DATA_NODE_FACTORY_, [{name: names[i], value: values[i]}], objectStack);
+ }
+};
+
+
/**
* @param {Node} node Node.
* @param {Object} icon Icon object.
@@ -2490,6 +2556,21 @@ ol.format.KML.writePlacemark_ = function(node, feature, objectStack) {
// serialize properties (properties unknown to KML are not serialized)
var properties = feature.getProperties();
+ // don't export these to ExtendedData
+ var filter = {'address': 1, 'description': 1, 'name': 1, 'open': 1,
+ 'phoneNumber': 1, 'styleUrl': 1, 'visibility': 1};
+ filter[feature.getGeometryName()] = 1;
+ var keys = Object.keys(properties || {}).sort().filter(function(v) {
+ return !filter[v];
+ });
+
+ if (keys.length > 0) {
+ var sequence = ol.xml.makeSequence(properties, keys);
+ var namesAndValues = {names: keys, values: sequence};
+ ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_,
+ ol.format.KML.EXTENDEDDATA_NODE_FACTORY_, [namesAndValues], objectStack);
+ }
+
var styleFunction = feature.getStyleFunction();
if (styleFunction) {
// FIXME the styles returned by the style function are supposed to be
@@ -2678,6 +2759,19 @@ ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS(
});
+/**
+ * @const
+ * @type {Object.>}
+ * @private
+ */
+ol.format.KML.EXTENDEDDATA_NODE_SERIALIZERS_ = ol.xml.makeStructureNS(
+ ol.format.KML.NAMESPACE_URIS_, {
+ 'Data': ol.xml.makeChildAppender(ol.format.KML.writeDataNode_),
+ 'value': ol.xml.makeChildAppender(ol.format.KML.writeDataNodeValue_),
+ 'displayName': ol.xml.makeChildAppender(ol.format.KML.writeDataNodeName_)
+ });
+
+
/**
* @const
* @type {Object.}
@@ -2845,6 +2939,8 @@ ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS(
*/
ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS(
ol.format.KML.NAMESPACE_URIS_, {
+ 'ExtendedData': ol.xml.makeChildAppender(
+ ol.format.KML.writeExtendedData_),
'MultiGeometry': ol.xml.makeChildAppender(
ol.format.KML.writeMultiGeometry_),
'LineString': ol.xml.makeChildAppender(
@@ -3000,6 +3096,26 @@ ol.format.KML.COORDINATES_NODE_FACTORY_ =
ol.xml.makeSimpleNodeFactory('coordinates');
+/**
+ * A factory for creating Data nodes.
+ * @const
+ * @type {function(*, Array.<*>): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.DATA_NODE_FACTORY_ =
+ ol.xml.makeSimpleNodeFactory('Data');
+
+
+/**
+ * A factory for creating ExtendedData nodes.
+ * @const
+ * @type {function(*, Array.<*>): (Node|undefined)}
+ * @private
+ */
+ol.format.KML.EXTENDEDDATA_NODE_FACTORY_ =
+ ol.xml.makeSimpleNodeFactory('ExtendedData');
+
+
/**
* A factory for creating innerBoundaryIs nodes.
* @const
diff --git a/src/ol/format/xsd.js b/src/ol/format/xsd.js
index 32bff8a168..8fc439f1d7 100644
--- a/src/ol/format/xsd.js
+++ b/src/ol/format/xsd.js
@@ -114,6 +114,15 @@ ol.format.XSD.writeBooleanTextNode = function(node, bool) {
};
+/**
+ * @param {Node} node Node to append a CDATA Section with the string to.
+ * @param {string} string String.
+ */
+ol.format.XSD.writeCDATASection = function(node, string) {
+ node.appendChild(ol.xml.DOCUMENT.createCDATASection(string));
+};
+
+
/**
* @param {Node} node Node to append a TextNode with the dateTime to.
* @param {number} dateTime DateTime in seconds.
diff --git a/test/spec/ol/format/kml.test.js b/test/spec/ol/format/kml.test.js
index d899f84a68..035b51222a 100644
--- a/test/spec/ol/format/kml.test.js
+++ b/test/spec/ol/format/kml.test.js
@@ -1488,6 +1488,83 @@ describe('ol.format.KML', function() {
describe('extended data', function() {
+ it('can write ExtendedData with no values', function() {
+ var feature = new ol.Feature();
+ feature.set('foo', null);
+ feature.set('bar', undefined);
+ var features = [feature];
+ var node = format.writeFeaturesNode(features);
+ var text =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ expect(node).to.xmleql(ol.xml.parse(text));
+ });
+
+ it('can write ExtendedData with values', function() {
+ var feature = new ol.Feature();
+ feature.set('foo', 'bar');
+ feature.set('aNumber', 1000);
+ var features = [feature];
+ var node = format.writeFeaturesNode(features);
+ var text =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' 1000' +
+ ' ' +
+ ' ' +
+ ' bar' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ expect(node).to.xmleql(ol.xml.parse(text));
+ });
+
+ it('can write ExtendedData pair with displayName and value', function() {
+ var pair = {
+ value: 'bar',
+ displayName: 'display name'
+ };
+
+ var feature = new ol.Feature();
+ feature.set('foo', pair);
+
+ var features = [feature];
+ var node = format.writeFeaturesNode(features);
+ var text =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' bar' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ expect(node).to.xmleql(ol.xml.parse(text));
+ });
+
it('can read ExtendedData', function() {
var text =
'' +