From de5632109e70422f794a60cf26fbff0154abefb8 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 21 Oct 2016 11:03:51 -0400 Subject: [PATCH 1/5] Got multiple levels of KML documents within a KMZ working. --- Source/DataSources/KmlDataSource.js | 81 ++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/Source/DataSources/KmlDataSource.js b/Source/DataSources/KmlDataSource.js index f76e088c9e9f..f44e99d3a51c 100644 --- a/Source/DataSources/KmlDataSource.js +++ b/Source/DataSources/KmlDataSource.js @@ -469,6 +469,14 @@ define([ if (defined(blob)) { hrefResolved = true; href = blob; + } else { + // Needed for multiple levels of KML files in a KMZ + var tmpHref = getAbsoluteUri(href, sourceUri); + blob = uriResolver[tmpHref]; + if (defined(blob)) { + hrefResolved = true; + href = blob; + } } } if (!hrefResolved && defined(sourceUri)) { @@ -1930,19 +1938,39 @@ define([ if (defined(link)) { var href = queryStringValue(link, 'href', namespaces.kml); if (defined(href)) { + var newSourceUri = href; href = resolveHref(href, undefined, sourceUri, uriResolver); - var viewRefreshMode = queryStringValue(link, 'viewRefreshMode', namespaces.kml); - var viewBoundScale = defaultValue(queryStringValue(link, 'viewBoundScale', namespaces.kml), 1.0); - var defaultViewFormat = (viewRefreshMode === 'onStop') ? 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]' : ''; - var viewFormat = defaultValue(queryStringValue(link, 'viewFormat', namespaces.kml), defaultViewFormat); - var httpQuery = queryStringValue(link, 'httpQuery', namespaces.kml); - var queryString = makeQueryString(viewFormat, httpQuery); + var linkUrl; - var networkLinkCollection = new EntityCollection(); - var linkUrl = processNetworkLinkQueryString(dataSource._camera, dataSource._canvas, joinUrls(href, queryString, false), - viewBoundScale, dataSource._lastCameraView.bbox); + // We need to pass in the original path if resolveHref returns a data uri because the network link + // references a document in a KMZ archive + if (/^data:/.test(href)) { + // No need to build a query string for a data uri, just use as is + linkUrl = href; + + // So if sourceUri isn't the kmz file, then its another kml in the archive, so resolve it + if (!/\.kmz/i.test(sourceUri)) { + newSourceUri = getAbsoluteUri(newSourceUri, sourceUri); + } + } else { + newSourceUri = href; // Not a data uri so use the fully qualified uri + var viewRefreshMode = queryStringValue(link, 'viewRefreshMode', namespaces.kml); + var viewBoundScale = defaultValue(queryStringValue(link, 'viewBoundScale', namespaces.kml), 1.0); + var defaultViewFormat = (viewRefreshMode === 'onStop') ? 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]' : ''; + var viewFormat = defaultValue(queryStringValue(link, 'viewFormat', namespaces.kml), defaultViewFormat); + var httpQuery = queryStringValue(link, 'httpQuery', namespaces.kml); + var queryString = makeQueryString(viewFormat, httpQuery); + + linkUrl = processNetworkLinkQueryString(dataSource._camera, dataSource._canvas, joinUrls(href, queryString, false), + viewBoundScale, dataSource._lastCameraView.bbox); + } - var promise = when(load(dataSource, networkLinkCollection, linkUrl), function(rootElement) { + var options = { + sourceUri : newSourceUri, + uriResolver : uriResolver + }; + var networkLinkCollection = new EntityCollection(); + var promise = when(load(dataSource, networkLinkCollection, linkUrl, options), function(rootElement) { var entities = dataSource._entityCollection; var newEntities = networkLinkCollection.values; var networkLinkAvailability = networkEntity.availability; @@ -2110,23 +2138,39 @@ define([ zip.createReader(new zip.BlobReader(blob), function(reader) { reader.getEntries(function(entries) { var promises = []; - var foundKML = false; var uriResolver = {}; + var docEntry; + var docDefer; for (var i = 0; i < entries.length; i++) { var entry = entries[i]; if (!entry.directory) { var innerDefer = when.defer(); promises.push(innerDefer.promise); - if (!foundKML && /\.kml$/i.test(entry.filename)) { - //Only the first KML file found in the zip is used. - //https://developers.google.com/kml/documentation/kmzarchives - foundKML = true; - loadXmlFromZip(reader, entry, uriResolver, innerDefer); + if (/\.kml$/i.test(entry.filename)) { + // We use the first KML document we come across + // https://developers.google.com/kml/documentation/kmzarchives + // Unless we come across a .kml file at the root of the archive because GE does this + if (!defined(docEntry) || !/\//i.test(entry.filename)) { + if (defined(docEntry)) { + // We found one at the root so load the initial kml as a data uri + loadDataUriFromZip(reader, docEntry, uriResolver, docDefer); + } + docEntry = entry; + docDefer = innerDefer; + } else { + // Wasn't the first kml and wasn't at the root + loadDataUriFromZip(reader, entry, uriResolver, innerDefer); + } } else { loadDataUriFromZip(reader, entry, uriResolver, innerDefer); } } } + + // Now load the root KML document + if (defined(docEntry)) { + loadXmlFromZip(reader, docEntry, uriResolver, docDefer); + } when.all(promises).then(function() { reader.close(); if (!defined(uriResolver.kml)) { @@ -2147,6 +2191,7 @@ define([ function load(dataSource, entityCollection, data, options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var sourceUri = options.sourceUri; + var uriResolver = options.uriResolver; var promise = data; if (typeof data === 'string') { @@ -2187,11 +2232,11 @@ define([ //Return the error throw new RuntimeError(msg); } - return loadKml(dataSource, entityCollection, kml, sourceUri, undefined); + return loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver); }); }); } else { - return when(loadKml(dataSource, entityCollection, dataToLoad, sourceUri, undefined)); + return when(loadKml(dataSource, entityCollection, dataToLoad, sourceUri, uriResolver)); } }).otherwise(function(error) { dataSource._error.raiseEvent(dataSource, error); From fdf75291d6c9a98a137ec603bf2c5734cee99ca8 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 21 Oct 2016 12:12:12 -0400 Subject: [PATCH 2/5] Added tests for multilevel kmz files. --- Source/DataSources/KmlDataSource.js | 133 +++++++++++++------------ Specs/Data/KML/multilevel.kmz | Bin 0 -> 2850 bytes Specs/DataSources/KmlDataSourceSpec.js | 11 +- 3 files changed, 79 insertions(+), 65 deletions(-) create mode 100644 Specs/Data/KML/multilevel.kmz diff --git a/Source/DataSources/KmlDataSource.js b/Source/DataSources/KmlDataSource.js index f44e99d3a51c..55aae399e887 100644 --- a/Source/DataSources/KmlDataSource.js +++ b/Source/DataSources/KmlDataSource.js @@ -878,7 +878,7 @@ define([ //Asynchronously processes an external style file. function processExternalStyles(dataSource, uri, styleCollection) { - return when(loadXML(proxyUrl(uri, dataSource._proxy)), function(styleKml) { + return loadXML(proxyUrl(uri, dataSource._proxy)).then(function(styleKml) { return processStyles(dataSource, styleKml, styleCollection, uri, true); }); } @@ -1532,8 +1532,8 @@ define([ entity.description = tmp; } - function processFeature(dataSource, parent, featureNode, entityCollection, styleCollection, sourceUri, uriResolver) { - var entity = createEntity(featureNode, entityCollection); + function processFeature(dataSource, parent, featureNode, entityCollection, styleCollection, sourceUri, uriResolver, context) { + var entity = createEntity(featureNode, entityCollection, context); var kmlData = entity.kml; var styleEntity = computeFinalStyle(entity, dataSource, featureNode, styleCollection, sourceUri, uriResolver); @@ -1619,7 +1619,7 @@ define([ Model : processUnsupportedGeometry }; - function processDocument(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver) { + function processDocument(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context) { var featureTypeNames = Object.keys(featureTypes); var featureTypeNamesLength = featureTypeNames.length; @@ -1633,19 +1633,19 @@ define([ var child = childNodes[q]; if (child.localName === featureName && ((namespaces.kml.indexOf(child.namespaceURI) !== -1) || (namespaces.gx.indexOf(child.namespaceURI) !== -1))) { - processFeatureNode(dataSource, parent, child, entityCollection, styleCollection, sourceUri, uriResolver); + processFeatureNode(dataSource, parent, child, entityCollection, styleCollection, sourceUri, uriResolver, context); } } } } - function processFolder(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver) { - var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver); - processDocument(dataSource, r.entity, node, entityCollection, styleCollection, sourceUri, uriResolver); + function processFolder(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context) { + var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context); + processDocument(dataSource, r.entity, node, entityCollection, styleCollection, sourceUri, uriResolver, context); } - function processPlacemark(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver) { - var r = processFeature(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver); + function processPlacemark(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, context) { + var r = processFeature(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, context); var entity = r.entity; var styleEntity = r.styleEntity; @@ -1668,8 +1668,8 @@ define([ } } - function processGroundOverlay(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver) { - var r = processFeature(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver); + function processGroundOverlay(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, context) { + var r = processFeature(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, context); var entity = r.entity; var geometry; @@ -1758,8 +1758,8 @@ define([ } } - function processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver) { - dataSource._unsupportedNode.raiseEvent(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver); + function processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context) { + dataSource._unsupportedNode.raiseEvent(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context); console.log('KML - Unsupported feature: ' + node.localName); } @@ -1926,8 +1926,8 @@ define([ return queryString; } - function processNetworkLink(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver) { - var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver); + function processNetworkLink(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context) { + var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context); var networkEntity = r.entity; var link = queryFirstNode(node, 'Link', namespaces.kml); @@ -1967,10 +1967,11 @@ define([ var options = { sourceUri : newSourceUri, - uriResolver : uriResolver + uriResolver : uriResolver, + context : networkEntity.id }; var networkLinkCollection = new EntityCollection(); - var promise = when(load(dataSource, networkLinkCollection, linkUrl, options), function(rootElement) { + var promise = load(dataSource, networkLinkCollection, linkUrl, options).then(function(rootElement) { var entities = dataSource._entityCollection; var newEntities = networkLinkCollection.values; var networkLinkAvailability = networkEntity.availability; @@ -1989,6 +1990,7 @@ define([ } } } + entities.add(newEntity); } entities.resumeEvents(); @@ -2084,16 +2086,16 @@ define([ Tour : processUnsupportedFeature }; - function processFeatureNode(dataSource, node, parent, entityCollection, styleCollection, sourceUri, uriResolver) { + function processFeatureNode(dataSource, node, parent, entityCollection, styleCollection, sourceUri, uriResolver, context) { var featureProcessor = featureTypes[node.localName]; if (defined(featureProcessor)) { - featureProcessor(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver); + featureProcessor(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context); } else { - processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver); + processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context); } } - function loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver) { + function loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver, context) { var deferred = when.defer(); entityCollection.removeAll(); @@ -2111,7 +2113,7 @@ define([ } var styleCollection = new EntityCollection(dataSource); - when.all(processStyles(dataSource, kml, styleCollection, sourceUri, false, uriResolver), function() { + when.all(processStyles(dataSource, kml, styleCollection, sourceUri, false, uriResolver, context), function() { var element = kml.documentElement; if (element.localName === 'kml') { var childNodes = element.childNodes; @@ -2124,7 +2126,7 @@ define([ } } entityCollection.suspendEvents(); - processFeatureNode(dataSource, element, undefined, entityCollection, styleCollection, sourceUri, uriResolver); + processFeatureNode(dataSource, element, undefined, entityCollection, styleCollection, sourceUri, uriResolver, context); entityCollection.resumeEvents(); deferred.resolve(kml.documentElement); @@ -2192,6 +2194,7 @@ define([ options = defaultValue(options, defaultValue.EMPTY_OBJECT); var sourceUri = options.sourceUri; var uriResolver = options.uriResolver; + var context = options.context; var promise = data; if (typeof data === 'string') { @@ -2199,50 +2202,52 @@ define([ sourceUri = defaultValue(sourceUri, data); } - return when(promise, function(dataToLoad) { - if (dataToLoad instanceof Blob) { - return isZipFile(dataToLoad).then(function(isZip) { - if (isZip) { - return loadKmz(dataSource, entityCollection, dataToLoad, sourceUri); - } - return when(readBlobAsText(dataToLoad)).then(function(text) { - //There's no official way to validate if a parse was successful. - //The following check detects the error on various browsers. - - //IE raises an exception - var kml; - var error; - try { - kml = parser.parseFromString(text, 'application/xml'); - } catch (e) { - error = e.toString(); + return when(promise) + .then(function(dataToLoad) { + if (dataToLoad instanceof Blob) { + return isZipFile(dataToLoad).then(function(isZip) { + if (isZip) { + return loadKmz(dataSource, entityCollection, dataToLoad, sourceUri); } + return readBlobAsText(dataToLoad).then(function(text) { + //There's no official way to validate if a parse was successful. + //The following check detects the error on various browsers. + + //IE raises an exception + var kml; + var error; + try { + kml = parser.parseFromString(text, 'application/xml'); + } catch (e) { + error = e.toString(); + } - //The parse succeeds on Chrome and Firefox, but the error - //handling is different in each. - if (defined(error) || kml.body || kml.documentElement.tagName === 'parsererror') { - //Firefox has error information as the firstChild nodeValue. - var msg = defined(error) ? error : kml.documentElement.firstChild.nodeValue; + //The parse succeeds on Chrome and Firefox, but the error + //handling is different in each. + if (defined(error) || kml.body || kml.documentElement.tagName === 'parsererror') { + //Firefox has error information as the firstChild nodeValue. + var msg = defined(error) ? error : kml.documentElement.firstChild.nodeValue; - //Chrome has it in the body text. - if (!msg) { - msg = kml.body.innerText; - } + //Chrome has it in the body text. + if (!msg) { + msg = kml.body.innerText; + } - //Return the error - throw new RuntimeError(msg); - } - return loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver); + //Return the error + throw new RuntimeError(msg); + } + return loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver, context); + }); }); - }); - } else { - return when(loadKml(dataSource, entityCollection, dataToLoad, sourceUri, uriResolver)); - } - }).otherwise(function(error) { - dataSource._error.raiseEvent(dataSource, error); - console.log(error); - return when.reject(error); - }); + } else { + return loadKml(dataSource, entityCollection, dataToLoad, sourceUri, uriResolver, context); + } + }) + .otherwise(function(error) { + dataSource._error.raiseEvent(dataSource, error); + console.log(error); + return when.reject(error); + }); } /** @@ -2767,7 +2772,7 @@ define([ var newEntityCollection = new EntityCollection(); var href = joinUrls(networkLink.href, makeQueryString(networkLink.cookie, networkLink.queryString), false); href = processNetworkLinkQueryString(that._camera, that._canvas, href, networkLink.viewBoundScale, lastCameraView.bbox); - load(that, newEntityCollection, href) + load(that, newEntityCollection, href, {context: entity.id}) .then(getNetworkLinkUpdateCallback(that, networkLink, newEntityCollection, newNetworkLinks, href)) .otherwise(function(error) { var msg = 'NetworkLink ' + networkLink.href + ' refresh failed: ' + error; diff --git a/Specs/Data/KML/multilevel.kmz b/Specs/Data/KML/multilevel.kmz new file mode 100644 index 0000000000000000000000000000000000000000..888379494eda3a66684c44f0c66be1751692b397 GIT binary patch literal 2850 zcmWIWW@Zs#-~htMk)fUpNPwL|fFUJ6SuZ;`Cp3bGLDi5e;+Fwe#8*ZUhRqw-pEw(+ z;jOE6?#%hkp+;AX1A;uxdY|(@dB%HtC>L+x<*fmmwm;tV(OXyZy#A?gC(oP?JK?i2 z^c17fls7vpavhW$W?fcWtYLcgocdNB)8hg5s`iok*5=3EC^OkdoeGC`Ih zz?+?ep)lZ1CeU5AKpX&cEf?60Oi`$=iXY!#i4mXFveXan6Ftql1q^u!t|vKnFtCO%_01K*0bn0-%WoDJ-$49rUnN!fl9NJkDUnorpF{0}BXIhcgV}iU%5) zE!tXG9A}Ce#J+&!`!AB$_m}`ZRsqfTkU|PQZHQvTFIrB9B@y4=gM3ZE*ggMAbV>Rf zuDG7e(?=`5Es%G3#U`CF{eo8AzV6P0xA&Yp-Lvw(^ZfkBYvO7}Y(mohoY z?Az*c-sYtDWP@FsR&?!c{GYY);d1Y&be=W8({`51gQD(mU5!~k(051i`cMI1sN?V~ z?l7lGq0Go6%8XQpAXQ523_L*3fq}!4Mi32mF$cWb4DbdSfUE9881UC|9nb)HG7gR4 zfY)RYBM@#tt~7*D-0&3H2&9q^yK~TsPh|hRb)3Y=0INShj)7G=$c_OSfLyNgp*ZF^ z!T_Xjffd2{!bJedoWG7WOi1AZ&EB|z2jn8;;E_Z!X-VTQEGD6Z5;n)7XG&yGzIDuF zhKCZo$l+js72n8#1uAfm%QJBlr>(*;2dT{AfEVTXLJir#zm6MOFhdQ=g|I>q-G#^n zlM;#xzu`6zrGP|tETU*bE)qmh%P4ue( literal 0 HcmV?d00001 diff --git a/Specs/DataSources/KmlDataSourceSpec.js b/Specs/DataSources/KmlDataSourceSpec.js index ba88a616ec05..35735b9a2a40 100644 --- a/Specs/DataSources/KmlDataSourceSpec.js +++ b/Specs/DataSources/KmlDataSourceSpec.js @@ -338,7 +338,7 @@ defineSuite([ expect(spy.calls.count()).toEqual(3); for (var i = 0; i < nodeNames.length; i++) { var args = spy.calls.argsFor(i); - expect(args.length).toEqual(7); + expect(args.length).toEqual(8); expect(args[0]).toBe(dataSource); expect(args[2].localName).toEqual(nodeNames[i]); expect(args[3]).toBeInstanceOf(EntityCollection); @@ -3596,6 +3596,15 @@ defineSuite([ }); }); + it('NetworkLink: within a kmz file', function() { + return KmlDataSource.load('Data/KML/multilevel.kmz', options).then(function(dataSource) { + var entities = dataSource.entities.values; + expect(entities.length).toBe(3); + expect(entities[3].billboard).not.toBeNull(); + expect(entities[3].position.getValue(Iso8601.MINIMUM_VALUE)).toEqual(Cartesian3.fromDegrees(1,2,3)); + }); + }); + it('can load a KML file with explicit namespaces', function() { return KmlDataSource.load('Data/KML/namespaced.kml', options).then(function(dataSource) { expect(dataSource.entities.values.length).toBe(3); From f435318148a863f0defa0aa96e813fd2fdec8ba6 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 21 Oct 2016 14:21:23 -0400 Subject: [PATCH 3/5] Fixed tests. --- Source/DataSources/KmlDataSource.js | 142 +++++++++++-------------- Specs/DataSources/KmlDataSourceSpec.js | 13 ++- 2 files changed, 75 insertions(+), 80 deletions(-) diff --git a/Source/DataSources/KmlDataSource.js b/Source/DataSources/KmlDataSource.js index 55aae399e887..99ca1f14e7b8 100644 --- a/Source/DataSources/KmlDataSource.js +++ b/Source/DataSources/KmlDataSource.js @@ -1532,7 +1532,7 @@ define([ entity.description = tmp; } - function processFeature(dataSource, parent, featureNode, entityCollection, styleCollection, sourceUri, uriResolver, context) { + function processFeature(dataSource, parent, featureNode, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) { var entity = createEntity(featureNode, entityCollection, context); var kmlData = entity.kml; var styleEntity = computeFinalStyle(entity, dataSource, featureNode, styleCollection, sourceUri, uriResolver); @@ -1619,7 +1619,7 @@ define([ Model : processUnsupportedGeometry }; - function processDocument(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context) { + function processDocument(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) { var featureTypeNames = Object.keys(featureTypes); var featureTypeNamesLength = featureTypeNames.length; @@ -1633,19 +1633,19 @@ define([ var child = childNodes[q]; if (child.localName === featureName && ((namespaces.kml.indexOf(child.namespaceURI) !== -1) || (namespaces.gx.indexOf(child.namespaceURI) !== -1))) { - processFeatureNode(dataSource, parent, child, entityCollection, styleCollection, sourceUri, uriResolver, context); + processFeatureNode(dataSource, parent, child, entityCollection, styleCollection, sourceUri, uriResolver, promises, context); } } } } - function processFolder(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context) { - var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context); - processDocument(dataSource, r.entity, node, entityCollection, styleCollection, sourceUri, uriResolver, context); + function processFolder(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) { + var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context); + processDocument(dataSource, r.entity, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context); } - function processPlacemark(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, context) { - var r = processFeature(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, context); + function processPlacemark(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) { + var r = processFeature(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, promises, context); var entity = r.entity; var styleEntity = r.styleEntity; @@ -1668,8 +1668,8 @@ define([ } } - function processGroundOverlay(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, context) { - var r = processFeature(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, context); + function processGroundOverlay(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) { + var r = processFeature(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, promises, context); var entity = r.entity; var geometry; @@ -1758,8 +1758,8 @@ define([ } } - function processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context) { - dataSource._unsupportedNode.raiseEvent(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context); + function processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) { + dataSource._unsupportedNode.raiseEvent(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver); console.log('KML - Unsupported feature: ' + node.localName); } @@ -1926,8 +1926,8 @@ define([ return queryString; } - function processNetworkLink(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context) { - var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context); + function processNetworkLink(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) { + var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context); var networkEntity = r.entity; var link = queryFirstNode(node, 'Link', namespaces.kml); @@ -2065,11 +2065,7 @@ define([ } }); - // _promises is only defined during the initial load to make sure we wait for all - // NetworkLinks to finish loading. - if (defined(dataSource._promises)) { - dataSource._promises.push(promise); - } + promises.push(promise); } } } @@ -2086,20 +2082,19 @@ define([ Tour : processUnsupportedFeature }; - function processFeatureNode(dataSource, node, parent, entityCollection, styleCollection, sourceUri, uriResolver, context) { + function processFeatureNode(dataSource, node, parent, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) { var featureProcessor = featureTypes[node.localName]; if (defined(featureProcessor)) { - featureProcessor(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context); + featureProcessor(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context); } else { - processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, context); + processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context); } } function loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver, context) { - var deferred = when.defer(); - entityCollection.removeAll(); + var promises = []; var documentElement = kml.documentElement; var document = documentElement.localName === 'Document' ? documentElement : queryFirstNode(documentElement, 'Document', namespaces.kml); var name = queryStringValue(document, 'name', namespaces.kml); @@ -2113,7 +2108,7 @@ define([ } var styleCollection = new EntityCollection(dataSource); - when.all(processStyles(dataSource, kml, styleCollection, sourceUri, false, uriResolver, context), function() { + return when.all(processStyles(dataSource, kml, styleCollection, sourceUri, false, uriResolver, context)).then(function() { var element = kml.documentElement; if (element.localName === 'kml') { var childNodes = element.childNodes; @@ -2126,13 +2121,13 @@ define([ } } entityCollection.suspendEvents(); - processFeatureNode(dataSource, element, undefined, entityCollection, styleCollection, sourceUri, uriResolver, context); + processFeatureNode(dataSource, element, undefined, entityCollection, styleCollection, sourceUri, uriResolver, promises, context); entityCollection.resumeEvents(); - deferred.resolve(kml.documentElement); + return when.all(promises).then(function() { + return kml.documentElement; + }); }); - - return deferred.promise; } function loadKmz(dataSource, entityCollection, blob, sourceUri) { @@ -2311,7 +2306,6 @@ define([ this._isLoading = false; this._proxy = options.proxy; this._pinBuilder = new PinBuilder(); - this._promises = []; this._networkLinks = new AssociativeArray(); this._entityCluster = new EntityCluster(); @@ -2494,67 +2488,61 @@ define([ var oldName = this._name; this._name = undefined; - this._promises = []; this._clampToGround = defaultValue(options.clampToGround, false); var that = this; return load(this, this._entityCollection, data, options).then(function() { - return when.all(that._promises, function() { - var clock; - - var availability = that._entityCollection.computeAvailability(); - - var start = availability.start; - var stop = availability.stop; - var isMinStart = JulianDate.equals(start, Iso8601.MINIMUM_VALUE); - var isMaxStop = JulianDate.equals(stop, Iso8601.MAXIMUM_VALUE); - if (!isMinStart || !isMaxStop) { - var date; - - //If start is min time just start at midnight this morning, local time - if (isMinStart) { - date = new Date(); - date.setHours(0, 0, 0, 0); - start = JulianDate.fromDate(date); - } + var clock; - //If stop is max value just stop at midnight tonight, local time - if (isMaxStop) { - date = new Date(); - date.setHours(24, 0, 0, 0); - stop = JulianDate.fromDate(date); - } + var availability = that._entityCollection.computeAvailability(); - clock = new DataSourceClock(); - clock.startTime = start; - clock.stopTime = stop; - clock.currentTime = JulianDate.clone(start); - clock.clockRange = ClockRange.LOOP_STOP; - clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER; - clock.multiplier = Math.round(Math.min(Math.max(JulianDate.secondsDifference(stop, start) / 60, 1), 3.15569e7)); - } + var start = availability.start; + var stop = availability.stop; + var isMinStart = JulianDate.equals(start, Iso8601.MINIMUM_VALUE); + var isMaxStop = JulianDate.equals(stop, Iso8601.MAXIMUM_VALUE); + if (!isMinStart || !isMaxStop) { + var date; - var changed = false; - if (clock !== that._clock) { - that._clock = clock; - changed = true; + //If start is min time just start at midnight this morning, local time + if (isMinStart) { + date = new Date(); + date.setHours(0, 0, 0, 0); + start = JulianDate.fromDate(date); } - if (oldName !== that._name) { - changed = true; + //If stop is max value just stop at midnight tonight, local time + if (isMaxStop) { + date = new Date(); + date.setHours(24, 0, 0, 0); + stop = JulianDate.fromDate(date); } - if (changed) { - that._changed.raiseEvent(that); - } + clock = new DataSourceClock(); + clock.startTime = start; + clock.stopTime = stop; + clock.currentTime = JulianDate.clone(start); + clock.clockRange = ClockRange.LOOP_STOP; + clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER; + clock.multiplier = Math.round(Math.min(Math.max(JulianDate.secondsDifference(stop, start) / 60, 1), 3.15569e7)); + } - DataSource.setLoading(that, false); + var changed = false; + if (clock !== that._clock) { + that._clock = clock; + changed = true; + } - // We don't need to add anymore - that._promises = undefined; + if (oldName !== that._name) { + changed = true; + } - return that; - }); + if (changed) { + that._changed.raiseEvent(that); + } + + DataSource.setLoading(that, false); + + return that; }).otherwise(function(error) { DataSource.setLoading(that, false); that._error.raiseEvent(that, error); diff --git a/Specs/DataSources/KmlDataSourceSpec.js b/Specs/DataSources/KmlDataSourceSpec.js index 35735b9a2a40..6047c568d1a1 100644 --- a/Specs/DataSources/KmlDataSourceSpec.js +++ b/Specs/DataSources/KmlDataSourceSpec.js @@ -338,7 +338,7 @@ defineSuite([ expect(spy.calls.count()).toEqual(3); for (var i = 0; i < nodeNames.length; i++) { var args = spy.calls.argsFor(i); - expect(args.length).toEqual(8); + expect(args.length).toEqual(7); expect(args[0]).toBe(dataSource); expect(args[2].localName).toEqual(nodeNames[i]); expect(args[3]).toBeInstanceOf(EntityCollection); @@ -3600,8 +3600,15 @@ defineSuite([ return KmlDataSource.load('Data/KML/multilevel.kmz', options).then(function(dataSource) { var entities = dataSource.entities.values; expect(entities.length).toBe(3); - expect(entities[3].billboard).not.toBeNull(); - expect(entities[3].position.getValue(Iso8601.MINIMUM_VALUE)).toEqual(Cartesian3.fromDegrees(1,2,3)); + expect(entities[1].billboard).not.toBeNull(); + expect(entities[1].position.getValue(Iso8601.MINIMUM_VALUE)).toEqual(Cartesian3.fromDegrees(1,2,3)); + + // The root network link is loaded, then the children + // since its done recursively the lowest level entities + // end up in the collection first. + expect(entities[0].parent).toBeUndefined(); + expect(entities[2].parent).toBe(entities[0]); + expect(entities[1].parent).toBe(entities[2]); }); }); From 9f5a99df02a223c4b4787fcb7148f887535a03b7 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 21 Oct 2016 14:45:44 -0400 Subject: [PATCH 4/5] Fixed duplicate code. --- Source/DataSources/KmlDataSource.js | 48 +++++++++++------------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/Source/DataSources/KmlDataSource.js b/Source/DataSources/KmlDataSource.js index 99ca1f14e7b8..8f9342107d32 100644 --- a/Source/DataSources/KmlDataSource.js +++ b/Source/DataSources/KmlDataSource.js @@ -1547,16 +1547,7 @@ define([ } entity.availability = availability; - if (defined(parent)) { - var parentAvailability = parent.availability; - if (defined(parentAvailability)) { - if (defined(availability)) { - availability.intersect(parentAvailability); - } else { - entity.availability = parentAvailability; - } - } - } + mergeAvailabilityWithParent(entity); // Per KML spec "A Feature is visible only if it and all its ancestors are visible." function ancestryIsVisible(parentEntity) { @@ -1974,21 +1965,12 @@ define([ var promise = load(dataSource, networkLinkCollection, linkUrl, options).then(function(rootElement) { var entities = dataSource._entityCollection; var newEntities = networkLinkCollection.values; - var networkLinkAvailability = networkEntity.availability; entities.suspendEvents(); for (var i = 0; i < newEntities.length; i++) { var newEntity = newEntities[i]; if (!defined(newEntity.parent)) { newEntity.parent = networkEntity; - - if (defined(networkLinkAvailability)) { - var childAvailability = newEntity.availability; - if (defined(childAvailability)) { - childAvailability.intersect(networkLinkAvailability); - } else { - newEntity.availability = networkLinkAvailability; - } - } + mergeAvailabilityWithParent(newEntity); } entities.add(newEntity); @@ -2551,6 +2533,21 @@ define([ }); }; + function mergeAvailabilityWithParent(child) { + var parent = child.parent; + if (defined(parent)) { + var parentAvailability = parent.availability; + if (defined(parentAvailability)) { + var childAvailability = child.availability; + if (defined(childAvailability)) { + childAvailability.intersect(parentAvailability); + } else { + child.availability = parentAvailability; + } + } + } + } + function getNetworkLinkUpdateCallback(dataSource, networkLink, newEntityCollection, networkLinks, processedHref) { return function(rootElement) { if (!networkLinks.contains(networkLink.id)) { @@ -2604,7 +2601,6 @@ define([ } var networkLinkEntity = networkLink.entity; - var networkLinkAvailability = networkLinkEntity.availability; var entityCollection = dataSource._entityCollection; var newEntities = newEntityCollection.values; @@ -2635,15 +2631,7 @@ define([ var newEntity = newEntities[i]; if (!defined(newEntity.parent)) { newEntity.parent = networkLinkEntity; - - if (defined(networkLinkAvailability)) { - var childAvailability = newEntity.availability; - if (defined(childAvailability)) { - childAvailability.intersect(networkLinkAvailability); - } else { - newEntity.availability = networkLinkAvailability; - } - } + mergeAvailabilityWithParent(newEntity); } entityCollection.add(newEntity); } From 9aa6fe957b2f3f36b5a7176e8b648e766d8458ec Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 21 Oct 2016 14:49:36 -0400 Subject: [PATCH 5/5] Update Changes. --- CHANGES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1bb08403c40d..6e2f384b2ad3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,7 +15,8 @@ Change Log * Fixed a bug with rotated, textured rectangles. [#4430](https://github.com/AnalyticalGraphicsInc/cesium/pull/4430) * Fixed a bug when morphing from 2D to 3D. [#4388](https://github.com/AnalyticalGraphicsInc/cesium/pull/4388) * Fixed a bug where when KML features had duplicate IDs, only one was drawn. [#3941](https://github.com/AnalyticalGraphicsInc/cesium/issues/3941) -* Fixed `KmlDataSource` features to respect `timespan` and `timestamp` properties of it's parents (eg. Folders or NetworkLinks). +* Fixed `KmlDataSource` features to respect `timespan` and `timestamp` properties of it's parents (eg. Folders or NetworkLinks). [#4041](https://github.com/AnalyticalGraphicsInc/cesium/issues/4041) +* Added the ability for KML files to load network links to other KML files within the same KMZ archive. [#4477](https://github.com/AnalyticalGraphicsInc/cesium/issues/4477) * `GeoJsonDataSource` now treats null crs values as a no-op instead of failing to load. * `GeoJsonDataSource` now gracefully handles missing style icons instead of failing to load. * Improve `Geocoder` usability by selecting text on click [#4464](https://github.com/AnalyticalGraphicsInc/cesium/pull/4464)