You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
8.1 KiB
JavaScript

import Uri from "../ThirdParty/Uri.js";
import when from "../ThirdParty/when.js";
import Check from "./Check.js";
import Credit from "./Credit.js";
import defaultValue from "./defaultValue.js";
import defined from "./defined.js";
import Ion from "./Ion.js";
import Resource from "./Resource.js";
import RuntimeError from "./RuntimeError.js";
/**
* A {@link Resource} instance that encapsulates Cesium ion asset access.
* This object is normally not instantiated directly, use {@link IonResource.fromAssetId}.
*
* @alias IonResource
* @constructor
* @augments Resource
*
* @param {Object} endpoint The result of the Cesium ion asset endpoint service.
* @param {Resource} endpointResource The resource used to retreive the endpoint.
*
* @see Ion
* @see IonImageryProvider
* @see createWorldTerrain
* @see https://cesium.com
*/
function IonResource(endpoint, endpointResource) {
//>>includeStart('debug', pragmas.debug);
Check.defined("endpoint", endpoint);
Check.defined("endpointResource", endpointResource);
//>>includeEnd('debug');
var options;
var externalType = endpoint.externalType;
var isExternal = defined(externalType);
if (!isExternal) {
options = {
url: endpoint.url,
retryAttempts: 1,
retryCallback: retryCallback,
};
} else if (
externalType === "3DTILES" ||
externalType === "STK_TERRAIN_SERVER"
) {
// 3D Tiles and STK Terrain Server external assets can still be represented as an IonResource
options = { url: endpoint.options.url };
} else {
//External imagery assets have additional configuration that can't be represented as a Resource
throw new RuntimeError(
"Ion.createResource does not support external imagery assets; use IonImageryProvider instead."
);
}
Resource.call(this, options);
// The asset endpoint data returned from ion.
this._ionEndpoint = endpoint;
this._ionEndpointDomain = isExternal
? undefined
: new Uri(endpoint.url).authority;
// The endpoint resource to fetch when a new token is needed
this._ionEndpointResource = endpointResource;
// The primary IonResource from which an instance is derived
this._ionRoot = undefined;
// Shared promise for endpooint requests amd credits (only ever set on the root request)
this._pendingPromise = undefined;
this._credits = undefined;
this._isExternal = isExternal;
}
if (defined(Object.create)) {
IonResource.prototype = Object.create(Resource.prototype);
IonResource.prototype.constructor = IonResource;
}
/**
* Asynchronously creates an instance.
*
* @param {Number} assetId The Cesium ion asset id.
* @param {Object} [options] An object with the following properties:
* @param {String} [options.accessToken=Ion.defaultAccessToken] The access token to use.
* @param {String|Resource} [options.server=Ion.defaultServer] The resource to the Cesium ion API server.
* @returns {Promise.<IonResource>} A Promise to am instance representing the Cesium ion Asset.
*
* @example
* //Load a Cesium3DTileset with asset ID of 124624234
* viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ url: Cesium.IonResource.fromAssetId(124624234) }));
*
* @example
* //Load a CZML file with asset ID of 10890
* Cesium.IonResource.fromAssetId(10890)
* .then(function (resource) {
* viewer.dataSources.add(Cesium.CzmlDataSource.load(resource));
* });
*/
IonResource.fromAssetId = function (assetId, options) {
var endpointResource = IonResource._createEndpointResource(assetId, options);
return endpointResource.fetchJson().then(function (endpoint) {
return new IonResource(endpoint, endpointResource);
});
};
Object.defineProperties(IonResource.prototype, {
/**
* Gets the credits required for attribution of the asset.
*
* @memberof IonResource.prototype
* @type {Credit[]}
* @readonly
*/
credits: {
get: function () {
// Only we're not the root, return its credits;
if (defined(this._ionRoot)) {
return this._ionRoot.credits;
}
// We are the root
if (defined(this._credits)) {
return this._credits;
}
this._credits = IonResource.getCreditsFromEndpoint(
this._ionEndpoint,
this._ionEndpointResource
);
return this._credits;
},
},
});
/** @private */
IonResource.getCreditsFromEndpoint = function (endpoint, endpointResource) {
var credits = endpoint.attributions.map(Credit.getIonCredit);
var defaultTokenCredit = Ion.getDefaultTokenCredit(
endpointResource.queryParameters.access_token
);
if (defined(defaultTokenCredit)) {
credits.push(Credit.clone(defaultTokenCredit));
}
return credits;
};
/** @inheritdoc */
IonResource.prototype.clone = function (result) {
// We always want to use the root's information because it's the most up-to-date
var ionRoot = defaultValue(this._ionRoot, this);
if (!defined(result)) {
result = new IonResource(
ionRoot._ionEndpoint,
ionRoot._ionEndpointResource
);
}
result = Resource.prototype.clone.call(this, result);
result._ionRoot = ionRoot;
result._isExternal = this._isExternal;
return result;
};
IonResource.prototype.fetchImage = function (options) {
if (!this._isExternal) {
var userOptions = options;
options = {
preferBlob: true,
};
if (defined(userOptions)) {
options.flipY = userOptions.flipY;
options.preferImageBitmap = userOptions.preferImageBitmap;
}
}
return Resource.prototype.fetchImage.call(this, options);
};
IonResource.prototype._makeRequest = function (options) {
// Don't send ion access token to non-ion servers.
if (
this._isExternal ||
new Uri(this.url).authority !== this._ionEndpointDomain
) {
return Resource.prototype._makeRequest.call(this, options);
}
if (!defined(options.headers)) {
options.headers = {};
}
options.headers.Authorization = "Bearer " + this._ionEndpoint.accessToken;
return Resource.prototype._makeRequest.call(this, options);
};
/**
* @private
*/
IonResource._createEndpointResource = function (assetId, options) {
//>>includeStart('debug', pragmas.debug);
Check.defined("assetId", assetId);
//>>includeEnd('debug');
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var server = defaultValue(options.server, Ion.defaultServer);
var accessToken = defaultValue(options.accessToken, Ion.defaultAccessToken);
server = Resource.createIfNeeded(server);
var resourceOptions = {
url: "v1/assets/" + assetId + "/endpoint",
};
if (defined(accessToken)) {
resourceOptions.queryParameters = { access_token: accessToken };
}
return server.getDerivedResource(resourceOptions);
};
function retryCallback(that, error) {
var ionRoot = defaultValue(that._ionRoot, that);
var endpointResource = ionRoot._ionEndpointResource;
// Image is not available in worker threads, so this avoids
// a ReferenceError
var imageDefined = typeof Image !== "undefined";
// We only want to retry in the case of invalid credentials (401) or image
// requests(since Image failures can not provide a status code)
if (
!defined(error) ||
(error.statusCode !== 401 &&
!(imageDefined && error.target instanceof Image))
) {
return when.resolve(false);
}
// We use a shared pending promise for all derived assets, since they share
// a common access_token. If we're already requesting a new token for this
// asset, we wait on the same promise.
if (!defined(ionRoot._pendingPromise)) {
ionRoot._pendingPromise = endpointResource
.fetchJson()
.then(function (newEndpoint) {
//Set the token for root resource so new derived resources automatically pick it up
ionRoot._ionEndpoint = newEndpoint;
return newEndpoint;
})
.always(function (newEndpoint) {
// Pass or fail, we're done with this promise, the next failure should use a new one.
ionRoot._pendingPromise = undefined;
return newEndpoint;
});
}
return ionRoot._pendingPromise.then(function (newEndpoint) {
// Set the new token and endpoint for this resource
that._ionEndpoint = newEndpoint;
return true;
});
}
export default IonResource;