IonResource.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import Uri from '../ThirdParty/Uri.js';
  2. import when from '../ThirdParty/when.js';
  3. import Check from './Check.js';
  4. import Credit from './Credit.js';
  5. import defaultValue from './defaultValue.js';
  6. import defined from './defined.js';
  7. import defineProperties from './defineProperties.js';
  8. import Ion from './Ion.js';
  9. import Resource from './Resource.js';
  10. import RuntimeError from './RuntimeError.js';
  11. /**
  12. * A {@link Resource} instance that encapsulates Cesium ion asset access.
  13. * This object is normally not instantiated directly, use {@link IonResource.fromAssetId}.
  14. *
  15. * @alias IonResource
  16. * @constructor
  17. * @augments Resource
  18. *
  19. * @param {Object} endpoint The result of the Cesium ion asset endpoint service.
  20. * @param {Resource} endpointResource The resource used to retreive the endpoint.
  21. *
  22. * @see Ion
  23. * @see IonImageryProvider
  24. * @see createWorldTerrain
  25. * @see https://cesium.com
  26. */
  27. function IonResource(endpoint, endpointResource) {
  28. //>>includeStart('debug', pragmas.debug);
  29. Check.defined('endpoint', endpoint);
  30. Check.defined('endpointResource', endpointResource);
  31. //>>includeEnd('debug');
  32. var options;
  33. var externalType = endpoint.externalType;
  34. var isExternal = defined(externalType);
  35. if (!isExternal) {
  36. options = {
  37. url: endpoint.url,
  38. retryAttempts: 1,
  39. retryCallback: retryCallback
  40. };
  41. } else if (externalType === '3DTILES' || externalType === 'STK_TERRAIN_SERVER') {
  42. // 3D Tiles and STK Terrain Server external assets can still be represented as an IonResource
  43. options = { url: endpoint.options.url };
  44. } else {
  45. //External imagery assets have additional configuration that can't be represented as a Resource
  46. throw new RuntimeError('Ion.createResource does not support external imagery assets; use IonImageryProvider instead.');
  47. }
  48. Resource.call(this, options);
  49. // The asset endpoint data returned from ion.
  50. this._ionEndpoint = endpoint;
  51. this._ionEndpointDomain = isExternal ? undefined : new Uri(endpoint.url).authority;
  52. // The endpoint resource to fetch when a new token is needed
  53. this._ionEndpointResource = endpointResource;
  54. // The primary IonResource from which an instance is derived
  55. this._ionRoot = undefined;
  56. // Shared promise for endpooint requests amd credits (only ever set on the root request)
  57. this._pendingPromise = undefined;
  58. this._credits = undefined;
  59. this._isExternal = isExternal;
  60. }
  61. if (defined(Object.create)) {
  62. IonResource.prototype = Object.create(Resource.prototype);
  63. IonResource.prototype.constructor = IonResource;
  64. }
  65. /**
  66. * Asynchronously creates an instance.
  67. *
  68. * @param {Number} assetId The Cesium ion asset id.
  69. * @param {Object} [options] An object with the following properties:
  70. * @param {String} [options.accessToken=Ion.defaultAccessToken] The access token to use.
  71. * @param {String|Resource} [options.server=Ion.defaultServer] The resource to the Cesium ion API server.
  72. * @returns {Promise.<IonResource>} A Promise to am instance representing the Cesium ion Asset.
  73. *
  74. * @example
  75. * //Load a Cesium3DTileset with asset ID of 124624234
  76. * viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ url: Cesium.IonResource.fromAssetId(124624234) }));
  77. *
  78. * @example
  79. * //Load a CZML file with asset ID of 10890
  80. * Cesium.IonResource.fromAssetId(10890)
  81. * .then(function (resource) {
  82. * viewer.dataSources.add(Cesium.CzmlDataSource.load(resource));
  83. * });
  84. */
  85. IonResource.fromAssetId = function(assetId, options) {
  86. var endpointResource = IonResource._createEndpointResource(assetId, options);
  87. return endpointResource.fetchJson()
  88. .then(function (endpoint) {
  89. return new IonResource(endpoint, endpointResource);
  90. });
  91. };
  92. defineProperties(IonResource.prototype, {
  93. /**
  94. * Gets the credits required for attribution of the asset.
  95. *
  96. * @memberof IonResource.prototype
  97. * @type {Credit[]}
  98. * @readonly
  99. */
  100. credits: {
  101. get: function() {
  102. // Only we're not the root, return its credits;
  103. if (defined(this._ionRoot)) {
  104. return this._ionRoot.credits;
  105. }
  106. // We are the root
  107. if (defined(this._credits)) {
  108. return this._credits;
  109. }
  110. this._credits = IonResource.getCreditsFromEndpoint(this._ionEndpoint, this._ionEndpointResource);
  111. return this._credits;
  112. }
  113. }
  114. });
  115. /** @private */
  116. IonResource.getCreditsFromEndpoint = function(endpoint, endpointResource) {
  117. var credits = endpoint.attributions.map(Credit.getIonCredit);
  118. var defaultTokenCredit = Ion.getDefaultTokenCredit(endpointResource.queryParameters.access_token);
  119. if (defined(defaultTokenCredit)) {
  120. credits.push(Credit.clone(defaultTokenCredit));
  121. }
  122. return credits;
  123. };
  124. /** @inheritdoc */
  125. IonResource.prototype.clone = function(result) {
  126. // We always want to use the root's information because it's the most up-to-date
  127. var ionRoot = defaultValue(this._ionRoot, this);
  128. if (!defined(result)) {
  129. result = new IonResource(ionRoot._ionEndpoint, ionRoot._ionEndpointResource);
  130. }
  131. result = Resource.prototype.clone.call(this, result);
  132. result._ionRoot = ionRoot;
  133. result._isExternal = this._isExternal;
  134. return result;
  135. };
  136. IonResource.prototype.fetchImage = function (options) {
  137. if (!this._isExternal) {
  138. var userOptions = options;
  139. options = {
  140. preferBlob : true
  141. };
  142. if (defined(userOptions)) {
  143. options.flipY = userOptions.flipY;
  144. options.preferImageBitmap = userOptions.preferImageBitmap;
  145. }
  146. }
  147. return Resource.prototype.fetchImage.call(this, options);
  148. };
  149. IonResource.prototype._makeRequest = function(options) {
  150. // Don't send ion access token to non-ion servers.
  151. if (this._isExternal || new Uri(this.url).authority !== this._ionEndpointDomain) {
  152. return Resource.prototype._makeRequest.call(this, options);
  153. }
  154. var acceptToken = '*/*;access_token=' + this._ionEndpoint.accessToken;
  155. var existingAccept = acceptToken;
  156. var oldHeaders = this.headers;
  157. if (defined(oldHeaders) && defined(oldHeaders.Accept)) {
  158. existingAccept = oldHeaders.Accept + ',' + acceptToken;
  159. }
  160. if (!defined(options.headers)) {
  161. options.headers = { Accept: existingAccept };
  162. } else if (!defined(options.headers.Accept)) {
  163. options.headers.Accept = existingAccept;
  164. } else {
  165. options.headers.Accept = options.headers.Accept + ',' + acceptToken;
  166. }
  167. return Resource.prototype._makeRequest.call(this, options);
  168. };
  169. /**
  170. * @private
  171. */
  172. IonResource._createEndpointResource = function (assetId, options) {
  173. //>>includeStart('debug', pragmas.debug);
  174. Check.defined('assetId', assetId);
  175. //>>includeEnd('debug');
  176. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  177. var server = defaultValue(options.server, Ion.defaultServer);
  178. var accessToken = defaultValue(options.accessToken, Ion.defaultAccessToken);
  179. server = Resource.createIfNeeded(server);
  180. var resourceOptions = {
  181. url: 'v1/assets/' + assetId + '/endpoint'
  182. };
  183. if (defined(accessToken)) {
  184. resourceOptions.queryParameters = { access_token: accessToken };
  185. }
  186. return server.getDerivedResource(resourceOptions);
  187. };
  188. function retryCallback(that, error) {
  189. var ionRoot = defaultValue(that._ionRoot, that);
  190. var endpointResource = ionRoot._ionEndpointResource;
  191. // We only want to retry in the case of invalid credentials (401) or image
  192. // requests(since Image failures can not provide a status code)
  193. if (!defined(error) || (error.statusCode !== 401 && !(error.target instanceof Image))) {
  194. return when.resolve(false);
  195. }
  196. // We use a shared pending promise for all derived assets, since they share
  197. // a common access_token. If we're already requesting a new token for this
  198. // asset, we wait on the same promise.
  199. if (!defined(ionRoot._pendingPromise)) {
  200. ionRoot._pendingPromise = endpointResource.fetchJson()
  201. .then(function(newEndpoint) {
  202. //Set the token for root resource so new derived resources automatically pick it up
  203. ionRoot._ionEndpoint = newEndpoint;
  204. return newEndpoint;
  205. })
  206. .always(function(newEndpoint) {
  207. // Pass or fail, we're done with this promise, the next failure should use a new one.
  208. ionRoot._pendingPromise = undefined;
  209. return newEndpoint;
  210. });
  211. }
  212. return ionRoot._pendingPromise.then(function(newEndpoint) {
  213. // Set the new token and endpoint for this resource
  214. that._ionEndpoint = newEndpoint;
  215. return true;
  216. });
  217. }
  218. export default IonResource;