diff --git a/src/ol/format/kmlformat.js b/src/ol/format/kmlformat.js index 2319cf4291..291482a23f 100644 --- a/src/ol/format/kmlformat.js +++ b/src/ol/format/kmlformat.js @@ -3,7 +3,6 @@ // FIXME handle highlighted keys in StyleMaps - use styleFunctions // FIXME extractAttributes // FIXME extractStyles -// FIXME gx:Track // FIXME http://earth.google.com/kml/1.0 namespace? // FIXME why does node.getAttribute return an unknown type? // FIXME text @@ -49,6 +48,13 @@ ol.KML_RESPECT_VISIBILITY = false; ol.format.KMLVec2_; +/** + * @typedef {{flatCoordinates: Array., + * whens: Array.}} + */ +ol.format.KMLGxTrackObject_; + + /** * @constructor @@ -108,6 +114,15 @@ goog.inherits(ol.format.KML, ol.format.XML); ol.format.KML.EXTENSIONS_ = ['.kml']; +/** + * @const {Array.} + * @private + */ +ol.format.KML.GX_NAMESPACE_URIS_ = [ + 'http://www.google.com/kml/ext/2.2' +]; + + /** * @const {Array.} * @private @@ -553,6 +568,91 @@ ol.format.KML.readFlatLinearRing_ = function(node, objectStack) { }; +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.gxCoordParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); + goog.asserts.assert(goog.array.indexOf( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI) != -1); + goog.asserts.assert(node.localName == 'coord'); + var gxTrackObject = /** @type {ol.format.KMLGxTrackObject_} */ + (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isObject(gxTrackObject)); + var flatCoordinates = gxTrackObject.flatCoordinates; + var s = ol.xml.getAllTextContent(node, false); + var re = + /^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i; + var m = re.exec(s); + if (m) { + var x = parseFloat(m[1]); + var y = parseFloat(m[2]); + var z = parseFloat(m[3]); + flatCoordinates.push(x, y, z, 0); + } else { + flatCoordinates.push(0, 0, 0, 0); + } +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.MultiLineString|undefined} MultiLineString. + */ +ol.format.KML.readGxMultiTrack_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); + goog.asserts.assert(goog.array.indexOf( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI) != -1); + goog.asserts.assert(node.localName == 'MultiTrack'); + var lineStrings = ol.xml.pushAndParse( + /** @type {Array.} */ ([]), + ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_, node, objectStack); + if (!goog.isDef(lineStrings)) { + return undefined; + } + var multiLineString = new ol.geom.MultiLineString(null); + multiLineString.setLineStrings(lineStrings); + return multiLineString; +}; + + +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + * @return {ol.geom.LineString|undefined} LineString. + */ +ol.format.KML.readGxTrack_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); + goog.asserts.assert(goog.array.indexOf( + ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI) != -1); + goog.asserts.assert(node.localName == 'Track'); + var gxTrackObject = ol.xml.pushAndParse( + /** @type {ol.format.KMLGxTrackObject_} */ ({ + flatCoordinates: [], + whens: [] + }), ol.format.KML.GX_TRACK_PARSERS_, node, objectStack); + if (!goog.isDef(gxTrackObject)) { + return undefined; + } + var flatCoordinates = gxTrackObject.flatCoordinates; + var whens = gxTrackObject.whens; + goog.asserts.assert(flatCoordinates.length / 4 == whens.length); + var i, ii; + for (i = 0, ii = Math.min(flatCoordinates.length, whens.length); i < ii; + ++i) { + flatCoordinates[4 * i + 3] = whens[i]; + } + var lineString = new ol.geom.LineString(null); + lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates); + return lineString; +}; + + /** * @param {Node} node Node. * @param {Array.<*>} objectStack Object stack. @@ -951,6 +1051,45 @@ ol.format.KML.outerBoundaryIsParser_ = function(node, objectStack) { }; +/** + * @param {Node} node Node. + * @param {Array.<*>} objectStack Object stack. + * @private + */ +ol.format.KML.whenParser_ = function(node, objectStack) { + goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT); + goog.asserts.assert(node.localName == 'when'); + var gxTrackObject = /** @type {ol.format.KMLGxTrackObject_} */ + (objectStack[objectStack.length - 1]); + goog.asserts.assert(goog.isObject(gxTrackObject)); + var whens = gxTrackObject.whens; + var s = ol.xml.getAllTextContent(node, false); + var re = + /^\s*(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|(?:([+\-])(\d{2})(?::(\d{2}))?))\s*$/; + var m = re.exec(s); + if (m) { + var year = parseInt(m[1], 10); + var month = parseInt(m[2], 10) - 1; + var day = parseInt(m[3], 10); + var hour = parseInt(m[4], 10); + var minute = parseInt(m[5], 10); + var second = parseInt(m[6], 10); + var date = new Date(year, month, day, hour, minute, second, 0); + var t = date.getTime() / 1000; + if (m[7] != 'Z') { + var sign = m[8] == '-' ? -1 : 1; + t += sign * 60 * parseInt(m[9], 10); + if (goog.isDef(m[10])) { + t += sign * 60 * 60 * parseInt(m[10], 10); + } + } + whens.push(t); + } else { + whens.push(0); + } +}; + + /** * @const {Object.>} * @private @@ -993,6 +1132,19 @@ ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_ = ol.xml.makeParsersNS( }); +/** + * @const {Object.>} + * @private + */ +ol.format.KML.GX_TRACK_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.NAMESPACE_URIS_, { + 'when': ol.format.KML.whenParser_ + }, ol.xml.makeParsersNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'coord': ol.format.KML.gxCoordParser_ + })); + + /** * @const {Object.>} * @private @@ -1060,6 +1212,16 @@ ol.format.KML.MULTI_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS( }); +/** + * @const {Object.>} + * @private + */ +ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeParsersNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_) + }); + + /** * @const {Object.>} * @private @@ -1106,7 +1268,12 @@ ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeParsersNS( 'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.KML.readString_), 'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_), 'visibility': ol.xml.makeObjectPropertySetter(ol.format.KML.readBoolean_) - }); + }, ol.xml.makeParsersNS( + ol.format.KML.GX_NAMESPACE_URIS_, { + 'MultiTrack': ol.xml.makeObjectPropertySetter( + ol.format.KML.readGxMultiTrack_, 'geometry') + } + )); /** diff --git a/src/ol/geom/multilinestring.js b/src/ol/geom/multilinestring.js index 18b00c2eb6..98cfb3402d 100644 --- a/src/ol/geom/multilinestring.js +++ b/src/ol/geom/multilinestring.js @@ -1,5 +1,7 @@ goog.provide('ol.geom.MultiLineString'); +goog.require('goog.array'); +goog.require('goog.asserts'); goog.require('ol.extent'); goog.require('ol.geom.LineString'); goog.require('ol.geom.SimpleGeometry'); @@ -163,3 +165,26 @@ ol.geom.MultiLineString.prototype.setFlatCoordinates = this.ends_ = ends; this.dispatchChangeEvent(); }; + + +/** + * @param {Array.} lineStrings LineStrings. + */ +ol.geom.MultiLineString.prototype.setLineStrings = function(lineStrings) { + var layout = ol.geom.GeometryLayout.XY; + var flatCoordinates = []; + var ends = []; + var i, ii; + for (i = 0, ii = lineStrings.length; i < ii; ++i) { + var lineString = lineStrings[i]; + if (i === 0) { + layout = lineString.getLayout(); + } else { + // FIXME better handle the case of non-matching layouts + goog.asserts.assert(lineString.getLayout() == layout); + } + goog.array.extend(flatCoordinates, lineString.getFlatCoordinates()); + ends.push(flatCoordinates.length); + } + this.setFlatCoordinates(layout, flatCoordinates, ends); +}; diff --git a/src/ol/xml.js b/src/ol/xml.js index 5d1a954e25..e166ce94ca 100644 --- a/src/ol/xml.js +++ b/src/ol/xml.js @@ -149,11 +149,13 @@ ol.xml.makeObjectPropertySetter = function(valueReader, opt_property, opt_obj) { /** * @param {Array.} namespaceURIs Namespace URIs. * @param {Object.} parsers Parsers. + * @param {Object.>=} opt_parsersNS + * ParsersNS. * @return {Object.>} Parsers NS. */ -ol.xml.makeParsersNS = function(namespaceURIs, parsers) { +ol.xml.makeParsersNS = function(namespaceURIs, parsers, opt_parsersNS) { /** @type {Object.>} */ - var parsersNS = {}; + var parsersNS = goog.isDef(opt_parsersNS) ? opt_parsersNS : {}; var i, ii; for (i = 0, ii = namespaceURIs.length; i < ii; ++i) { parsersNS[namespaceURIs[i]] = parsers; diff --git a/test/spec/ol/geom/multilinestring.test.js b/test/spec/ol/geom/multilinestring.test.js index 1b7e67a3f2..b92928eb14 100644 --- a/test/spec/ol/geom/multilinestring.test.js +++ b/test/spec/ol/geom/multilinestring.test.js @@ -168,8 +168,25 @@ describe('ol.geom.MultiLineString', function() { }); + describe('#setLineStrings', function() { + + it('sets the line strings', function() { + var multiLineString = new ol.geom.MultiLineString(null); + var lineString1 = new ol.geom.LineString([[1, 2], [3, 4]]); + var lineString2 = new ol.geom.LineString([[5, 6], [7, 8]]); + multiLineString.setLineStrings([lineString1, lineString2]); + expect(multiLineString.getFlatCoordinates()).to.eql( + [1, 2, 3, 4, 5, 6, 7, 8]); + expect(multiLineString.getEnds()).to.eql([4, 8]); + var coordinates = multiLineString.getCoordinates(); + expect(coordinates[0]).to.eql(lineString1.getCoordinates()); + expect(coordinates[1]).to.eql(lineString2.getCoordinates()); + }); + }); + }); goog.require('ol.extent'); +goog.require('ol.geom.LineString'); goog.require('ol.geom.MultiLineString');