IonResource.js 8.0 KB

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