IonResource.js 7.9 KB

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