From b00498ddadbfe3bef5466b9781c293c5446bb963 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Thu, 28 Sep 2017 18:32:22 -0400 Subject: [PATCH 1/6] Changed Cesium terrain provider to be able to handle a parentUrl in layer.json. --- Source/Core/CesiumTerrainProvider.js | 167 ++++++++++++++++++++------- 1 file changed, 128 insertions(+), 39 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 9816f5adb63b..66e75cde7367 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -50,6 +50,15 @@ define([ TileProviderError) { 'use strict'; + function LayerInformation(layer) { + this.isHeightmap = layer.isHeightmap; + this.tileUrlTemplates = layer.tileUrlTemplates; + this.availability = layer.availability; + this.hasVertexNormals = layer.hasVertexNormals; + this.hasWaterMask = layer.hasWaterMask; + this.littleEndianExtensionSize = layer.littleEndianExtensionSize; + } + /** * A {@link TerrainProvider} that accesses terrain data in a Cesium terrain format. * The format is described on the @@ -114,14 +123,8 @@ define([ this._heightmapStructure = undefined; this._hasWaterMask = false; - - /** - * Boolean flag that indicates if the Terrain Server can provide vertex normals. - * @type {Boolean} - * @default false - * @private - */ this._hasVertexNormals = false; + /** * Boolean flag that indicates if the client should request vertex normals from the server. * @type {Boolean} @@ -129,7 +132,7 @@ define([ * @private */ this._requestVertexNormals = defaultValue(options.requestVertexNormals, false); - this._littleEndianExtensionSize = true; + /** * Boolean flag that indicates if the client should request tile watermasks from the server. * @type {Boolean} @@ -139,7 +142,6 @@ define([ this._requestWaterMask = defaultValue(options.requestWaterMask, false); this._errorEvent = new Event(); - this._availability = undefined; var credit = options.credit; if (typeof credit === 'string') { @@ -150,6 +152,7 @@ define([ this._ready = false; this._readyPromise = when.defer(); + var lastUrl = this._url; var metadataUrl = joinUrls(this._url, 'layer.json'); if (defined(this._proxy)) { metadataUrl = this._proxy.getURL(metadataUrl); @@ -158,7 +161,10 @@ define([ var that = this; var metadataError; - function metadataSuccess(data) { + var layers = this._layers = []; + var attribution = ''; + + function parseMetadataSuccess(data) { var message; if (!data.format) { @@ -173,8 +179,14 @@ define([ return; } + var hasVertexNormals = false; + var hasWaterMask = false; + var littleEndianExtensionSize = true; + var isHeightmap = false; if (data.format === 'heightmap-1.0') { - that._heightmapStructure = { + isHeightmap = true; + if (!defined(that._heightmapStructure)) { + that._heightmapStructure = { heightScale : 1.0 / 5.0, heightOffset : -1000.0, elementsPerHeight : 1, @@ -184,7 +196,8 @@ define([ lowestEncodedHeight : 0, highestEncodedHeight : 256 * 256 - 1 }; - that._hasWaterMask = true; + } + hasWaterMask = true; that._requestWaterMask = true; } else if (data.format.indexOf('quantized-mesh-1.') !== 0) { message = 'The tile format "' + data.format + '" is invalid or not supported.'; @@ -192,21 +205,21 @@ define([ return; } - that._tileUrlTemplates = data.tiles; - for (var i = 0; i < that._tileUrlTemplates.length; ++i) { - var template = new Uri(that._tileUrlTemplates[i]); - var baseUri = new Uri(that._url); + var tileUrlTemplates = data.tiles; + for (var i = 0; i < tileUrlTemplates.length; ++i) { + var template = new Uri(tileUrlTemplates[i]); + var baseUri = new Uri(lastUrl); if (template.authority && !baseUri.authority) { baseUri.authority = template.authority; baseUri.scheme = template.scheme; } - that._tileUrlTemplates[i] = joinUrls(baseUri, template).toString().replace('{version}', data.version); + tileUrlTemplates[i] = joinUrls(baseUri, template).toString().replace('{version}', data.version); } var availableTiles = data.available; - + var availability; if (defined(availableTiles)) { - that._availability = new TileAvailability(that._tilingScheme, availableTiles.length); + availability = new TileAvailability(that._tilingScheme, availableTiles.length); for (var level = 0; level < availableTiles.length; ++level) { var rangesAtLevel = availableTiles[level]; @@ -214,15 +227,11 @@ define([ for (var rangeIndex = 0; rangeIndex < rangesAtLevel.length; ++rangeIndex) { var range = rangesAtLevel[rangeIndex]; - that._availability.addAvailableTileRange(level, range.startX, yTiles - range.endY - 1, range.endX, yTiles - range.startY - 1); + availability.addAvailableTileRange(level, range.startX, yTiles - range.endY - 1, range.endX, yTiles - range.startY - 1); } } } - if (!defined(that._credit) && defined(data.attribution) && data.attribution !== null) { - that._credit = new Credit(data.attribution); - } - // The vertex normals defined in the 'octvertexnormals' extension is identical to the original // contents of the original 'vertexnormals' extension. 'vertexnormals' extension is now // deprecated, as the extensionLength for this extension was incorrectly using big endian. @@ -230,17 +239,63 @@ define([ // by setting the _littleEndianExtensionSize to false. Always prefer 'octvertexnormals' // over 'vertexnormals' if both extensions are supported by the server. if (defined(data.extensions) && data.extensions.indexOf('octvertexnormals') !== -1) { - that._hasVertexNormals = true; + hasVertexNormals = true; } else if (defined(data.extensions) && data.extensions.indexOf('vertexnormals') !== -1) { - that._hasVertexNormals = true; - that._littleEndianExtensionSize = false; + hasVertexNormals = true; + littleEndianExtensionSize = false; } if (defined(data.extensions) && data.extensions.indexOf('watermask') !== -1) { - that._hasWaterMask = true; + hasWaterMask = true; + } + + that._hasWaterMask = that._hasWaterMask && hasWaterMask; + that._hasVertexNormals = that._hasVertexNormals && hasVertexNormals; + if (defined(data.attribution)) { + attribution += data.attribution + ' '; + } + + layers.push(new LayerInformation({ + isHeightmap: isHeightmap, + tileUrlTemplates: tileUrlTemplates, + availability: availability, + hasVertexNormals: hasVertexNormals, + hasWaterMask: hasWaterMask, + littleEndianExtensionSize: littleEndianExtensionSize + })); + + var parentUrl = data.parentUrl; + if (defined(parentUrl)) { + lastUrl = joinUrls(lastUrl, parentUrl); + metadataUrl = joinUrls(lastUrl, 'layer.json'); + if (defined(that._proxy)) { + metadataUrl = that._proxy.getURL(metadataUrl); + } + var parentMetadata = loadJson(metadataUrl); + return when(parentMetadata, parseMetadataSuccess, parseMetadataFailure); } - that._ready = true; - that._readyPromise.resolve(true); + return when.resolve(); + } + + function parseMetadataFailure(data) { + var message = 'An error occurred while accessing ' + metadataUrl + '.'; + metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); + } + + function metadataSuccess(data) { + parseMetadataSuccess(data) + .then(function() { + if (defined(metadataError)) { + return; + } + + if (!defined(that._credit) && attribution.length > 0) { + that._credit = new Credit(attribution); + } + + that._ready = true; + that._readyPromise.resolve(true); + }); } function metadataFailure(data) { @@ -257,8 +312,7 @@ define([ }); return; } - var message = 'An error occurred while accessing ' + metadataUrl + '.'; - metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata); + parseMetadataFailure(data); } function requestMetadata() { @@ -476,11 +530,23 @@ define([ southSkirtHeight : skirtHeight, eastSkirtHeight : skirtHeight, northSkirtHeight : skirtHeight, - childTileMask: provider.availability.computeChildMaskForTile(level, x, y), + childTileMask: computeChildMaskForTile(provider, level, x, y), waterMask: waterMaskBuffer }); } + function computeChildMaskForTile(provider, level, x, y) { + var childLevel = level + 1; + var mask = 0; + + mask |= provider.getTileDataAvailable(2 * x, 2 * y + 1, childLevel) ? 1 : 0; + mask |= provider.getTileDataAvailable(2 * x + 1, 2 * y + 1, childLevel) ? 2 : 0; + mask |= provider.getTileDataAvailable(2 * x, 2 * y, childLevel) ? 4 : 0; + mask |= provider.getTileDataAvailable(2 * x + 1, 2 * y, childLevel) ? 8 : 0; + + return mask; + } + /** * Requests the geometry for a given tile. This function should not be called before * {@link CesiumTerrainProvider#ready} returns true. The result must include terrain data and @@ -505,7 +571,21 @@ define([ } //>>includeEnd('debug'); - var urlTemplates = this._tileUrlTemplates; + var layerToUse; + var layers = this._layers; + var layerCount = layers.length; + for (var i=0;i Date: Fri, 29 Sep 2017 13:37:35 -0400 Subject: [PATCH 2/6] Tweaks to availability. --- Source/Core/CesiumTerrainProvider.js | 65 +++++++++++++++------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 66e75cde7367..2bebfe082993 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -149,6 +149,8 @@ define([ } this._credit = credit; + this._availability = undefined; + this._ready = false; this._readyPromise = when.defer(); @@ -163,6 +165,7 @@ define([ var layers = this._layers = []; var attribution = ''; + var overallAvailability = []; function parseMetadataSuccess(data) { var message; @@ -224,10 +227,16 @@ define([ for (var level = 0; level < availableTiles.length; ++level) { var rangesAtLevel = availableTiles[level]; var yTiles = that._tilingScheme.getNumberOfYTilesAtLevel(level); + if (!defined(overallAvailability[level])) { + overallAvailability[level] = []; + } for (var rangeIndex = 0; rangeIndex < rangesAtLevel.length; ++rangeIndex) { var range = rangesAtLevel[rangeIndex]; - availability.addAvailableTileRange(level, range.startX, yTiles - range.endY - 1, range.endX, yTiles - range.startY - 1); + var yStart = yTiles - range.endY - 1; + var yEnd = yTiles - range.startY - 1; + overallAvailability[level].push([range.startX, yStart, range.endX, yEnd]); + availability.addAvailableTileRange(level, range.startX, yStart, range.endX, yEnd); } } } @@ -248,10 +257,13 @@ define([ hasWaterMask = true; } - that._hasWaterMask = that._hasWaterMask && hasWaterMask; - that._hasVertexNormals = that._hasVertexNormals && hasVertexNormals; + that._hasWaterMask = that._hasWaterMask || hasWaterMask; + that._hasVertexNormals = that._hasVertexNormals || hasVertexNormals; if (defined(data.attribution)) { - attribution += data.attribution + ' '; + if (attribution.length > 0) { + attribution += ' '; + } + attribution += data.attribution; } layers.push(new LayerInformation({ @@ -265,6 +277,10 @@ define([ var parentUrl = data.parentUrl; if (defined(parentUrl)) { + if (!defined(availability)) { + console.log('A layer.json can\'t have a parentUrl if it does\'t have an available array.'); + return when.resolve(); + } lastUrl = joinUrls(lastUrl, parentUrl); metadataUrl = joinUrls(lastUrl, 'layer.json'); if (defined(that._proxy)) { @@ -289,6 +305,16 @@ define([ return; } + var length = overallAvailability.length; + var availability = that._availability = new TileAvailability(that._tilingScheme, length); + for(var level = 0;level 0) { that._credit = new Credit(attribution); } @@ -530,23 +556,11 @@ define([ southSkirtHeight : skirtHeight, eastSkirtHeight : skirtHeight, northSkirtHeight : skirtHeight, - childTileMask: computeChildMaskForTile(provider, level, x, y), + childTileMask: provider.availability.computeChildMaskForTile(level, x, y), waterMask: waterMaskBuffer }); } - function computeChildMaskForTile(provider, level, x, y) { - var childLevel = level + 1; - var mask = 0; - - mask |= provider.getTileDataAvailable(2 * x, 2 * y + 1, childLevel) ? 1 : 0; - mask |= provider.getTileDataAvailable(2 * x + 1, 2 * y + 1, childLevel) ? 2 : 0; - mask |= provider.getTileDataAvailable(2 * x, 2 * y, childLevel) ? 4 : 0; - mask |= provider.getTileDataAvailable(2 * x + 1, 2 * y, childLevel) ? 8 : 0; - - return mask; - } - /** * Requests the geometry for a given tile. This function should not be called before * {@link CesiumTerrainProvider#ready} returns true. The result must include terrain data and @@ -571,13 +585,14 @@ define([ } //>>includeEnd('debug'); - var layerToUse; var layers = this._layers; + var layerToUse = layers[0]; var layerCount = layers.length; for (var i=0;i Date: Fri, 29 Sep 2017 14:19:00 -0400 Subject: [PATCH 3/6] Fixed tests. --- Source/Core/CesiumTerrainProvider.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 2bebfe082993..218dfd08fb82 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -306,12 +306,14 @@ define([ } var length = overallAvailability.length; - var availability = that._availability = new TileAvailability(that._tilingScheme, length); - for(var level = 0;level 0) { + var availability = that._availability = new TileAvailability(that._tilingScheme, length); + for (var level = 0; level < length; ++level) { + var levelRanges = overallAvailability[level]; + for (var i = 0; i < levelRanges.length; ++i) { + var range = levelRanges[i]; + availability.addAvailableTileRange(level, range[0], range[1], range[2], range[3]); + } } } @@ -400,7 +402,7 @@ define([ }); } - function createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY) { + function createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY, littleEndianExtensionSize) { var pos = 0; var cartesian3Elements = 3; var boundingSphereElements = cartesian3Elements + 1; @@ -511,7 +513,7 @@ define([ while (pos < view.byteLength) { var extensionId = view.getUint8(pos, true); pos += Uint8Array.BYTES_PER_ELEMENT; - var extensionLength = view.getUint32(pos, provider._littleEndianExtensionSize); + var extensionLength = view.getUint32(pos, littleEndianExtensionSize); pos += Uint32Array.BYTES_PER_ELEMENT; if (extensionId === QuantizedMeshExtensionIds.OCT_VERTEX_NORMALS && provider._requestVertexNormals) { @@ -635,7 +637,7 @@ define([ if (defined(that._heightmapStructure)) { return createHeightmapTerrainData(that, buffer, level, x, y, tmsY); } - return createQuantizedMeshTerrainData(that, buffer, level, x, y, tmsY); + return createQuantizedMeshTerrainData(that, buffer, level, x, y, tmsY, layerToUse.littleEndianExtensionSize); }); }; From 323cf284a5929f049e8b753dba69d15b1d23f24c Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Fri, 29 Sep 2017 14:54:44 -0400 Subject: [PATCH 4/6] Added test for parentUrl --- Specs/Core/CesiumTerrainProviderSpec.js | 41 +++++++++++++++++++ .../CesiumTerrainTileJson/Parent.tile.json | 28 +++++++++++++ .../CesiumTerrainTileJson/ParentUrl.tile.json | 33 +++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 Specs/Data/CesiumTerrainTileJson/Parent.tile.json create mode 100644 Specs/Data/CesiumTerrainTileJson/ParentUrl.tile.json diff --git a/Specs/Core/CesiumTerrainProviderSpec.js b/Specs/Core/CesiumTerrainProviderSpec.js index c9c8a0b32b02..0e74a140a51b 100644 --- a/Specs/Core/CesiumTerrainProviderSpec.js +++ b/Specs/Core/CesiumTerrainProviderSpec.js @@ -73,6 +73,20 @@ defineSuite([ return returnTileJson('Data/CesiumTerrainTileJson/PartialAvailability.tile.json'); } + function returnParentUrlTileJson() { + var paths = ['Data/CesiumTerrainTileJson/ParentUrl.tile.json', + 'Data/CesiumTerrainTileJson/Parent.tile.json']; + var i = 0; + var oldLoad = loadWithXhr.load; + loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) { + if (url.indexOf('layer.json') >= 0) { + loadWithXhr.defaultLoad(paths[i++], responseType, method, data, headers, deferred); + } else { + return oldLoad(url, responseType, method, data, headers, deferred, overrideMimeType); + } + }; + } + function waitForTile(level, x, y, requestNormals, requestWaterMask, f) { var terrainProvider = new CesiumTerrainProvider({ url : 'made/up/url', @@ -247,6 +261,33 @@ defineSuite([ }); }); + it('requests parent layer.json', function() { + returnParentUrlTileJson(); + + var provider = new CesiumTerrainProvider({ + url : 'made/up/url', + requestVertexNormals : true, + requestWaterMask : true + }); + + return pollToPromise(function() { + return provider.ready; + }).then(function() { + expect(provider.credit.text).toBe('This is a child tileset! This amazing data is courtesy The Amazing Data Source!'); + expect(provider.requestVertexNormals).toBe(true); + expect(provider.requestWaterMask).toBe(true); + expect(provider.hasVertexNormals).toBe(false); // Neither tileset has them + expect(provider.hasWaterMask).toBe(true); // The top level tileset has them + + var layers = provider._layers; + expect(layers.length).toBe(2); + expect(layers[0].hasVertexNormals).toBe(false); + expect(layers[0].hasWaterMask).toBe(true); + expect(layers[1].hasVertexNormals).toBe(false); + expect(layers[1].hasWaterMask).toBe(false); + }); + }); + it('raises an error if layer.json does not specify a format', function() { returnTileJson('Data/CesiumTerrainTileJson/NoFormat.tile.json'); diff --git a/Specs/Data/CesiumTerrainTileJson/Parent.tile.json b/Specs/Data/CesiumTerrainTileJson/Parent.tile.json new file mode 100644 index 000000000000..7651c494254b --- /dev/null +++ b/Specs/Data/CesiumTerrainTileJson/Parent.tile.json @@ -0,0 +1,28 @@ +{ + "tilejson": "2.1.0", + "format" : "quantized-mesh-1.0", + "version" : "1.0.0", + "scheme" : "tms", + "attribution" : "This amazing data is courtesy The Amazing Data Source!", + "tiles" : [ + "{z}/{x}/{y}.terrain?v={version}" + ], + "available" : [ + [ + { + "startX" : 0, + "startY" : 0, + "endX" : 1, + "endY" : 0 + } + ], + [ + { + "startX" : 0, + "startY" : 0, + "endX" : 3, + "endY" : 1 + } + ] + ] +} diff --git a/Specs/Data/CesiumTerrainTileJson/ParentUrl.tile.json b/Specs/Data/CesiumTerrainTileJson/ParentUrl.tile.json new file mode 100644 index 000000000000..baf406ba4b0c --- /dev/null +++ b/Specs/Data/CesiumTerrainTileJson/ParentUrl.tile.json @@ -0,0 +1,33 @@ +{ + "tilejson": "2.1.0", + "format" : "quantized-mesh-1.0", + "version" : "1.0.0", + "scheme" : "tms", + "attribution" : "This is a child tileset!", + "tiles" : [ + "{z}/{x}/{y}.terrain?v={version}" + ], + "extensions" : [ + "watermask" + ], + "available" : [ + [ + { + "startX" : 0, + "startY" : 0, + "endX" : 1, + "endY" : 0 + } + ], + [ + { + "startX" : 0, + "startY" : 0, + "endX" : 2, + "endY" : 1 + } + ] + ], + "parentUrl": "./" +} + From ef333870871b43553fe673d82ba9e3e4e2a7d185 Mon Sep 17 00:00:00 2001 From: Tom Fili Date: Tue, 10 Oct 2017 10:10:44 -0400 Subject: [PATCH 5/6] Tweaks from PR comments. --- Source/Core/CesiumTerrainProvider.js | 21 ++++++++++++++------- Specs/Core/CesiumTerrainProviderSpec.js | 11 ++++++++++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 218dfd08fb82..762c3ca5a5cb 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -20,6 +20,7 @@ define([ './QuantizedMeshTerrainData', './Request', './RequestType', + './RuntimeError', './TerrainProvider', './TileAvailability', './TileProviderError' @@ -45,6 +46,7 @@ define([ QuantizedMeshTerrainData, Request, RequestType, + RuntimeError, TerrainProvider, TileAvailability, TileProviderError) { @@ -588,18 +590,23 @@ define([ //>>includeEnd('debug'); var layers = this._layers; - var layerToUse = layers[0]; + var layerToUse; var layerCount = layers.length; - for (var i=0;i Date: Tue, 10 Oct 2017 10:19:59 -0400 Subject: [PATCH 6/6] Update CHANGES.md --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 432dff7bc1a0..164db8c154d8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ Change Log ========== +### 1.39 - 2017-11-01 +* Added support for the layer.json `parentUrl` property in `CesiumTerrainProvider` to allow for compositing of tilesets. + ### 1.38 - 2017-10-02 * Breaking changes