diff --git a/externs/olx.js b/externs/olx.js index 601ba00637..19f3e2c1c3 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -1299,6 +1299,25 @@ olx.format.GMLOptions.prototype.multiSurface; olx.format.GMLOptions.prototype.schemaLocation; +/** + * @typedef {{readExtensions: (function(ol.Feature, Node)|undefined)}} + * @api + */ +olx.format.GPXOptions; + + +/** + * Callback function to process `extensions` nodes. + * To prevent memory leaks, this callback function must + * not store any references to the node. Note that the `extensions` + * node is not allowed in GPX 1.0. Moreover, only `extensions` + * nodes from `wpt`, `rte` and `trk` can be processed, as those are + * directly mapped to a feature. + * @type {function(ol.Feature, Node)} + */ +olx.format.GPXOptions.prototype.readExtensions; + + /** * @typedef {{featureNS: string, * featureType: string, diff --git a/src/ol/format/gpxformat.js b/src/ol/format/gpxformat.js index 9097be5c5b..1e1b9f5d07 100644 --- a/src/ol/format/gpxformat.js +++ b/src/ol/format/gpxformat.js @@ -21,10 +21,20 @@ goog.require('ol.xml'); * * @constructor * @extends {ol.format.XMLFeature} + * @param {olx.format.GPXOptions=} opt_options Options. * @api */ -ol.format.GPX = function() { +ol.format.GPX = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + goog.base(this); + + /** + * @type {function(ol.Feature, Node)|undefined} + * @private + */ + this.readExtensions_ = options.readExtensions; }; goog.inherits(ol.format.GPX, ol.format.XMLFeature); @@ -88,6 +98,19 @@ ol.format.GPX.parseLink_ = function(node, objectStack) { }; +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.GPX.parseExtensions_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); + goog.asserts.assert(node.localName == 'extensions'); + var values = /** @type {Object} */ (objectStack[objectStack.length - 1]); + goog.object.set(values, 'extensionsNode_', node); +}; + + /** * @param {Node} node Node. * @param {Array.<*>} objectStack Object stack. @@ -275,6 +298,7 @@ ol.format.GPX.RTE_PARSERS_ = ol.xml.makeParsersNS( 'link': ol.format.GPX.parseLink_, 'number': ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), + 'extensions': ol.format.GPX.parseExtensions_, 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), 'rtept': ol.format.GPX.parseRtePt_ }); @@ -307,6 +331,7 @@ ol.format.GPX.TRK_PARSERS_ = ol.xml.makeParsersNS( 'number': ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), 'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString), + 'extensions': ol.format.GPX.parseExtensions_, 'trkseg': ol.format.GPX.parseTrkSeg_ }); @@ -361,10 +386,30 @@ ol.format.GPX.WPT_PARSERS_ = ol.xml.makeParsersNS( 'ageofdgpsdata': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal), 'dgpsid': - ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger) + ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger), + 'extensions': ol.format.GPX.parseExtensions_ }); +/** + * @param {Array.} features + * @private + */ +ol.format.GPX.prototype.handleReadExtensions_ = function(features) { + if (goog.isNull(features)) { + features = []; + } + for (var i = 0, ii = features.length; i < ii; ++i) { + var feature = features[i]; + if (goog.isDef(this.readExtensions_)) { + var extensionsNode = feature.get('extensionsNode_') || null; + this.readExtensions_(feature, extensionsNode); + } + feature.set('extensionsNode_', undefined); + } +}; + + /** * Read the first feature from a GPX source. * @@ -392,6 +437,7 @@ ol.format.GPX.prototype.readFeatureFromNode = function(node) { if (!goog.isDef(feature)) { return null; } + this.handleReadExtensions_([feature]); return feature; }; @@ -420,6 +466,7 @@ ol.format.GPX.prototype.readFeaturesFromNode = function(node) { /** @type {Array.} */ ([]), ol.format.GPX.GPX_PARSERS_, node, []); if (goog.isDef(features)) { + this.handleReadExtensions_(features); return features; } else { return []; diff --git a/test/spec/ol/format/gpxformat.test.js b/test/spec/ol/format/gpxformat.test.js index 7d880ed391..1a0f9c7180 100644 --- a/test/spec/ol/format/gpxformat.test.js +++ b/test/spec/ol/format/gpxformat.test.js @@ -394,6 +394,73 @@ describe('ol.format.GPX', function() { }); + describe('extensions support', function() { + + beforeEach(function() { + format = new ol.format.GPX({ + readExtensions: function(feature, extensionsNode) { + var nodes = extensionsNode.getElementsByTagName('id'); + var id = nodes.item(0).textContent; + feature.setId(id); + } + }); + }); + + it('can process extensions from wpt', function() { + var text = + '' + + ' ' + + ' ' + + ' feature-id' + + ' ' + + ' ' + + ''; + var fs = format.readFeatures(text); + expect(fs).to.have.length(1); + var feature = fs[0]; + expect(feature.getId()).to.be('feature-id'); + }); + + it('can process extensions from rte', function() { + var text = + '' + + ' ' + + ' ' + + ' bar' + + ' feature-id' + + ' ' + + ' ' + + ''; + var fs = format.readFeatures(text); + expect(fs).to.have.length(1); + var feature = fs[0]; + expect(feature.getId()).to.be('feature-id'); + }); + + it('can process extensions from trk, not trkpt', function() { + var text = + '' + + ' ' + + ' ' + + ' feature-id' + + ' ' + + ' ' + + ' ' + + ' ' + + ' another-feature-id' + + ' ' + + ' ' + + ' ' + + ' ' + + ''; + var fs = format.readFeatures(text); + expect(fs).to.have.length(1); + var feature = fs[0]; + expect(feature.getId()).to.be('feature-id'); + }); + + }); + }); });