UNPKG

cesium

Version:

CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

1,212 lines (1,102 loc) 92.3 kB
import Uri from '../ThirdParty/Uri.js'; import when from '../ThirdParty/when.js'; import appendForwardSlash from './appendForwardSlash.js'; import Check from './Check.js'; import clone from './clone.js'; import combine from './combine.js'; import defaultValue from './defaultValue.js'; import defined from './defined.js'; import DeveloperError from './DeveloperError.js'; import getAbsoluteUri from './getAbsoluteUri.js'; import getBaseUri from './getBaseUri.js'; import getExtensionFromUri from './getExtensionFromUri.js'; import isBlobUri from './isBlobUri.js'; import isCrossOriginUrl from './isCrossOriginUrl.js'; import isDataUri from './isDataUri.js'; import loadAndExecuteScript from './loadAndExecuteScript.js'; import objectToQuery from './objectToQuery.js'; import queryToObject from './queryToObject.js'; import Request from './Request.js'; import RequestErrorEvent from './RequestErrorEvent.js'; import RequestScheduler from './RequestScheduler.js'; import RequestState from './RequestState.js'; import RuntimeError from './RuntimeError.js'; import TrustedServers from './TrustedServers.js'; var xhrBlobSupported = (function() { try { var xhr = new XMLHttpRequest(); xhr.open('GET', '#', true); xhr.responseType = 'blob'; return xhr.responseType === 'blob'; } catch (e) { return false; } })(); /** * Parses a query string and returns the object equivalent. * * @param {Uri} uri The Uri with a query object. * @param {Resource} resource The Resource that will be assigned queryParameters. * @param {Boolean} merge If true, we'll merge with the resource's existing queryParameters. Otherwise they will be replaced. * @param {Boolean} preserveQueryParameters If true duplicate parameters will be concatenated into an array. If false, keys in uri will take precedence. * * @private */ function parseQuery(uri, resource, merge, preserveQueryParameters) { var queryString = uri.query; if (!defined(queryString) || (queryString.length === 0)) { return {}; } var query; // Special case we run into where the querystring is just a string, not key/value pairs if (queryString.indexOf('=') === -1) { var result = {}; result[queryString] = undefined; query = result; } else { query = queryToObject(queryString); } if (merge) { resource._queryParameters = combineQueryParameters(query, resource._queryParameters, preserveQueryParameters); } else { resource._queryParameters = query; } uri.query = undefined; } /** * Converts a query object into a string. * * @param {Uri} uri The Uri object that will have the query object set. * @param {Resource} resource The resource that has queryParameters * * @private */ function stringifyQuery(uri, resource) { var queryObject = resource._queryParameters; var keys = Object.keys(queryObject); // We have 1 key with an undefined value, so this is just a string, not key/value pairs if (keys.length === 1 && !defined(queryObject[keys[0]])) { uri.query = keys[0]; } else { uri.query = objectToQuery(queryObject); } } /** * Clones a value if it is defined, otherwise returns the default value * * @param {*} [val] The value to clone. * @param {*} [defaultVal] The default value. * * @returns {*} A clone of val or the defaultVal. * * @private */ function defaultClone(val, defaultVal) { if (!defined(val)) { return defaultVal; } return defined(val.clone) ? val.clone() : clone(val); } /** * Checks to make sure the Resource isn't already being requested. * * @param {Request} request The request to check. * * @private */ function checkAndResetRequest(request) { if (request.state === RequestState.ISSUED || request.state === RequestState.ACTIVE) { throw new RuntimeError('The Resource is already being fetched.'); } request.state = RequestState.UNISSUED; request.deferred = undefined; } /** * This combines a map of query parameters. * * @param {Object} q1 The first map of query parameters. Values in this map will take precedence if preserveQueryParameters is false. * @param {Object} q2 The second map of query parameters. * @param {Boolean} preserveQueryParameters If true duplicate parameters will be concatenated into an array. If false, keys in q1 will take precedence. * * @returns {Object} The combined map of query parameters. * * @example * var q1 = { * a: 1, * b: 2 * }; * var q2 = { * a: 3, * c: 4 * }; * var q3 = { * b: [5, 6], * d: 7 * } * * // Returns * // { * // a: [1, 3], * // b: 2, * // c: 4 * // }; * combineQueryParameters(q1, q2, true); * * // Returns * // { * // a: 1, * // b: 2, * // c: 4 * // }; * combineQueryParameters(q1, q2, false); * * // Returns * // { * // a: 1, * // b: [2, 5, 6], * // d: 7 * // }; * combineQueryParameters(q1, q3, true); * * // Returns * // { * // a: 1, * // b: 2, * // d: 7 * // }; * combineQueryParameters(q1, q3, false); * * @private */ function combineQueryParameters(q1, q2, preserveQueryParameters) { if (!preserveQueryParameters) { return combine(q1, q2); } var result = clone(q1, true); for (var param in q2) { if (q2.hasOwnProperty(param)) { var value = result[param]; var q2Value = q2[param]; if (defined(value)) { if (!Array.isArray(value)) { value = result[param] = [value]; } result[param] = value.concat(q2Value); } else { result[param] = Array.isArray(q2Value) ? q2Value.slice() : q2Value; } } } return result; } /** * A resource that includes the location and any other parameters we need to retrieve it or create derived resources. It also provides the ability to retry requests. * * @alias Resource * @constructor * * @param {String|Object} options A url or an object with the following properties * @param {String} options.url The url of the resource. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}). * @param {Object} [options.headers={}] Additional HTTP headers that will be sent. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only. * * @example * function refreshTokenRetryCallback(resource, error) { * if (error.statusCode === 403) { * // 403 status code means a new token should be generated * return getNewAccessToken() * .then(function(token) { * resource.queryParameters.access_token = token; * return true; * }) * .otherwise(function() { * return false; * }); * } * * return false; * } * * var resource = new Resource({ * url: 'http://server.com/path/to/resource.json', * proxy: new DefaultProxy('/proxy/'), * headers: { * 'X-My-Header': 'valueOfHeader' * }, * queryParameters: { * 'access_token': '123-435-456-000' * }, * retryCallback: refreshTokenRetryCallback, * retryAttempts: 1 * }); */ function Resource(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); if (typeof options === 'string') { options = { url: options }; } //>>includeStart('debug', pragmas.debug); Check.typeOf.string('options.url', options.url); //>>includeEnd('debug'); this._url = undefined; this._templateValues = defaultClone(options.templateValues, {}); this._queryParameters = defaultClone(options.queryParameters, {}); /** * Additional HTTP headers that will be sent with the request. * * @type {Object} */ this.headers = defaultClone(options.headers, {}); /** * A Request object that will be used. Intended for internal use only. * * @type {Request} */ this.request = defaultValue(options.request, new Request()); /** * A proxy to be used when loading the resource. * * @type {DefaultProxy} */ this.proxy = options.proxy; /** * Function to call when a request for this resource fails. If it returns true or a Promise that resolves to true, the request will be retried. * * @type {Function} */ this.retryCallback = options.retryCallback; /** * The number of times the retryCallback should be called before giving up. * * @type {Number} */ this.retryAttempts = defaultValue(options.retryAttempts, 0); this._retryCount = 0; var uri = new Uri(options.url); parseQuery(uri, this, true, true); // Remove the fragment as it's not sent with a request uri.fragment = undefined; this._url = uri.toString(); } /** * A helper function to create a resource depending on whether we have a String or a Resource * * @param {Resource|String} resource A Resource or a String to use when creating a new Resource. * * @returns {Resource} If resource is a String, a Resource constructed with the url and options. Otherwise the resource parameter is returned. * * @private */ Resource.createIfNeeded = function(resource) { if (resource instanceof Resource) { // Keep existing request object. This function is used internally to duplicate a Resource, so that it can't // be modified outside of a class that holds it (eg. an imagery or terrain provider). Since the Request objects // are managed outside of the providers, by the tile loading code, we want to keep the request property the same so if it is changed // in the underlying tiling code the requests for this resource will use it. return resource.getDerivedResource({ request: resource.request }); } if (typeof resource !== 'string') { return resource; } return new Resource({ url: resource }); }; var supportsImageBitmapOptionsPromise; /** * A helper function to check whether createImageBitmap supports passing ImageBitmapOptions. * * @returns {Promise<Boolean>} A promise that resolves to true if this browser supports creating an ImageBitmap with options. * * @private */ Resource.supportsImageBitmapOptions = function() { // Until the HTML folks figure out what to do about this, we need to actually try loading an image to // know if this browser supports passing options to the createImageBitmap function. // https://github.com/whatwg/html/pull/4248 if (defined(supportsImageBitmapOptionsPromise)) { return supportsImageBitmapOptionsPromise; } if (typeof createImageBitmap !== 'function') { supportsImageBitmapOptionsPromise = when.resolve(false); return supportsImageBitmapOptionsPromise; } var imageDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg=='; supportsImageBitmapOptionsPromise = Resource.fetchBlob({ url : imageDataUri }) .then(function(blob) { return createImageBitmap(blob, { imageOrientation: 'flipY', premultiplyAlpha: 'none' }); }) .then(function(imageBitmap) { return true; }) .otherwise(function() { return false; }); return supportsImageBitmapOptionsPromise; }; Object.defineProperties(Resource, { /** * Returns true if blobs are supported. * * @memberof Resource * @type {Boolean} * * @readonly */ isBlobSupported : { get : function() { return xhrBlobSupported; } } }); Object.defineProperties(Resource.prototype, { /** * Query parameters appended to the url. * * @memberof Resource.prototype * @type {Object} * * @readonly */ queryParameters: { get: function() { return this._queryParameters; } }, /** * The key/value pairs used to replace template parameters in the url. * * @memberof Resource.prototype * @type {Object} * * @readonly */ templateValues: { get: function() { return this._templateValues; } }, /** * The url to the resource with template values replaced, query string appended and encoded by proxy if one was set. * * @memberof Resource.prototype * @type {String} */ url: { get: function() { return this.getUrlComponent(true, true); }, set: function(value) { var uri = new Uri(value); parseQuery(uri, this, false); // Remove the fragment as it's not sent with a request uri.fragment = undefined; this._url = uri.toString(); } }, /** * The file extension of the resource. * * @memberof Resource.prototype * @type {String} * * @readonly */ extension: { get: function() { return getExtensionFromUri(this._url); } }, /** * True if the Resource refers to a data URI. * * @memberof Resource.prototype * @type {Boolean} */ isDataUri: { get: function() { return isDataUri(this._url); } }, /** * True if the Resource refers to a blob URI. * * @memberof Resource.prototype * @type {Boolean} */ isBlobUri: { get: function() { return isBlobUri(this._url); } }, /** * True if the Resource refers to a cross origin URL. * * @memberof Resource.prototype * @type {Boolean} */ isCrossOriginUrl: { get: function() { return isCrossOriginUrl(this._url); } }, /** * True if the Resource has request headers. This is equivalent to checking if the headers property has any keys. * * @memberof Resource.prototype * @type {Boolean} */ hasHeaders: { get: function() { return (Object.keys(this.headers).length > 0); } } }); /** * Returns the url, optional with the query string and processed by a proxy. * * @param {Boolean} [query=false] If true, the query string is included. * @param {Boolean} [proxy=false] If true, the url is processed the proxy object if defined. * * @returns {String} The url with all the requested components. */ Resource.prototype.getUrlComponent = function(query, proxy) { if(this.isDataUri) { return this._url; } var uri = new Uri(this._url); if (query) { stringifyQuery(uri, this); } // objectToQuery escapes the placeholders. Undo that. var url = uri.toString().replace(/%7B/g, '{').replace(/%7D/g, '}'); var templateValues = this._templateValues; url = url.replace(/{(.*?)}/g, function(match, key) { var replacement = templateValues[key]; if (defined(replacement)) { // use the replacement value from templateValues if there is one... return encodeURIComponent(replacement); } // otherwise leave it unchanged return match; }); if (proxy && defined(this.proxy)) { url = this.proxy.getURL(url); } return url; }; /** * Combines the specified object and the existing query parameters. This allows you to add many parameters at once, * as opposed to adding them one at a time to the queryParameters property. If a value is already set, it will be replaced with the new value. * * @param {Object} params The query parameters * @param {Boolean} [useAsDefault=false] If true the params will be used as the default values, so they will only be set if they are undefined. */ Resource.prototype.setQueryParameters = function(params, useAsDefault) { if (useAsDefault) { this._queryParameters = combineQueryParameters(this._queryParameters, params, false); } else { this._queryParameters = combineQueryParameters(params, this._queryParameters, false); } }; /** * Combines the specified object and the existing query parameters. This allows you to add many parameters at once, * as opposed to adding them one at a time to the queryParameters property. * * @param {Object} params The query parameters */ Resource.prototype.appendQueryParameters = function(params) { this._queryParameters = combineQueryParameters(params, this._queryParameters, true); }; /** * Combines the specified object and the existing template values. This allows you to add many values at once, * as opposed to adding them one at a time to the templateValues property. If a value is already set, it will become an array and the new value will be appended. * * @param {Object} template The template values * @param {Boolean} [useAsDefault=false] If true the values will be used as the default values, so they will only be set if they are undefined. */ Resource.prototype.setTemplateValues = function(template, useAsDefault) { if (useAsDefault) { this._templateValues = combine(this._templateValues, template); } else { this._templateValues = combine(template, this._templateValues); } }; /** * Returns a resource relative to the current instance. All properties remain the same as the current instance unless overridden in options. * * @param {Object} options An object with the following properties * @param {String} [options.url] The url that will be resolved relative to the url of the current instance. * @param {Object} [options.queryParameters] An object containing query parameters that will be combined with those of the current instance. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}). These will be combined with those of the current instance. * @param {Object} [options.headers={}] Additional HTTP headers that will be sent. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource. * @param {Resource~RetryCallback} [options.retryCallback] The function to call when loading the resource fails. * @param {Number} [options.retryAttempts] The number of times the retryCallback should be called before giving up. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only. * @param {Boolean} [options.preserveQueryParameters=false] If true, this will keep all query parameters from the current resource and derived resource. If false, derived parameters will replace those of the current resource. * * @returns {Resource} The resource derived from the current one. */ Resource.prototype.getDerivedResource = function(options) { var resource = this.clone(); resource._retryCount = 0; if (defined(options.url)) { var uri = new Uri(options.url); var preserveQueryParameters = defaultValue(options.preserveQueryParameters, false); parseQuery(uri, resource, true, preserveQueryParameters); // Remove the fragment as it's not sent with a request uri.fragment = undefined; resource._url = uri.resolve(new Uri(getAbsoluteUri(this._url))).toString(); } if (defined(options.queryParameters)) { resource._queryParameters = combine(options.queryParameters, resource._queryParameters); } if (defined(options.templateValues)) { resource._templateValues = combine(options.templateValues, resource.templateValues); } if (defined(options.headers)) { resource.headers = combine(options.headers, resource.headers); } if (defined(options.proxy)) { resource.proxy = options.proxy; } if (defined(options.request)) { resource.request = options.request; } if (defined(options.retryCallback)) { resource.retryCallback = options.retryCallback; } if (defined(options.retryAttempts)) { resource.retryAttempts = options.retryAttempts; } return resource; }; /** * Called when a resource fails to load. This will call the retryCallback function if defined until retryAttempts is reached. * * @param {Error} [error] The error that was encountered. * * @returns {Promise<Boolean>} A promise to a boolean, that if true will cause the resource request to be retried. * * @private */ Resource.prototype.retryOnError = function(error) { var retryCallback = this.retryCallback; if ((typeof retryCallback !== 'function') || (this._retryCount >= this.retryAttempts)) { return when(false); } var that = this; return when(retryCallback(this, error)) .then(function(result) { ++that._retryCount; return result; }); }; /** * Duplicates a Resource instance. * * @param {Resource} [result] The object onto which to store the result. * * @returns {Resource} The modified result parameter or a new Resource instance if one was not provided. */ Resource.prototype.clone = function(result) { if (!defined(result)) { result = new Resource({ url : this._url }); } result._url = this._url; result._queryParameters = clone(this._queryParameters); result._templateValues = clone(this._templateValues); result.headers = clone(this.headers); result.proxy = this.proxy; result.retryCallback = this.retryCallback; result.retryAttempts = this.retryAttempts; result._retryCount = 0; result.request = this.request.clone(); return result; }; /** * Returns the base path of the Resource. * * @param {Boolean} [includeQuery = false] Whether or not to include the query string and fragment form the uri * * @returns {String} The base URI of the resource */ Resource.prototype.getBaseUri = function(includeQuery) { return getBaseUri(this.getUrlComponent(includeQuery), includeQuery); }; /** * Appends a forward slash to the URL. */ Resource.prototype.appendForwardSlash = function() { this._url = appendForwardSlash(this._url); }; /** * Asynchronously loads the resource as raw binary data. Returns a promise that will resolve to * an ArrayBuffer once loaded, or reject if the resource failed to load. The data is loaded * using XMLHttpRequest, which means that in order to make requests to another origin, * the server must have Cross-Origin Resource Sharing (CORS) headers enabled. * * @returns {Promise.<ArrayBuffer>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. * * @example * // load a single URL asynchronously * resource.fetchArrayBuffer().then(function(arrayBuffer) { * // use the data * }).otherwise(function(error) { * // an error occurred * }); * * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} */ Resource.prototype.fetchArrayBuffer = function () { return this.fetch({ responseType : 'arraybuffer' }); }; /** * Creates a Resource and calls fetchArrayBuffer() on it. * * @param {String|Object} options A url or an object with the following properties * @param {String} options.url The url of the resource. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}). * @param {Object} [options.headers={}] Additional HTTP headers that will be sent. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only. * @returns {Promise.<ArrayBuffer>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. */ Resource.fetchArrayBuffer = function (options) { var resource = new Resource(options); return resource.fetchArrayBuffer(); }; /** * Asynchronously loads the given resource as a blob. Returns a promise that will resolve to * a Blob once loaded, or reject if the resource failed to load. The data is loaded * using XMLHttpRequest, which means that in order to make requests to another origin, * the server must have Cross-Origin Resource Sharing (CORS) headers enabled. * * @returns {Promise.<Blob>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. * * @example * // load a single URL asynchronously * resource.fetchBlob().then(function(blob) { * // use the data * }).otherwise(function(error) { * // an error occurred * }); * * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} */ Resource.prototype.fetchBlob = function () { return this.fetch({ responseType : 'blob' }); }; /** * Creates a Resource and calls fetchBlob() on it. * * @param {String|Object} options A url or an object with the following properties * @param {String} options.url The url of the resource. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}). * @param {Object} [options.headers={}] Additional HTTP headers that will be sent. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only. * @returns {Promise.<Blob>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. */ Resource.fetchBlob = function (options) { var resource = new Resource(options); return resource.fetchBlob(); }; /** * Asynchronously loads the given image resource. Returns a promise that will resolve to * an {@link https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap|ImageBitmap} if <code>preferImageBitmap</code> is true and the browser supports <code>createImageBitmap</code> or otherwise an * {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement|Image} once loaded, or reject if the image failed to load. * * @param {Object} [options] An object with the following properties. * @param {Boolean} [options.preferBlob=false] If true, we will load the image via a blob. * @param {Boolean} [options.preferImageBitmap=false] If true, image will be decoded during fetch and an <code>ImageBitmap</code> is returned. * @param {Boolean} [options.flipY=false] If true, image will be vertically flipped during decode. Only applies if the browser supports <code>createImageBitmap</code>. * @returns {Promise.<ImageBitmap>|Promise.<Image>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. * * * @example * // load a single image asynchronously * resource.fetchImage().then(function(image) { * // use the loaded image * }).otherwise(function(error) { * // an error occurred * }); * * // load several images in parallel * when.all([resource1.fetchImage(), resource2.fetchImage()]).then(function(images) { * // images is an array containing all the loaded images * }); * * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} */ Resource.prototype.fetchImage = function (options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var preferImageBitmap = defaultValue(options.preferImageBitmap, false); var preferBlob = defaultValue(options.preferBlob, false); var flipY = defaultValue(options.flipY, false); checkAndResetRequest(this.request); // We try to load the image normally if // 1. Blobs aren't supported // 2. It's a data URI // 3. It's a blob URI // 4. It doesn't have request headers and we preferBlob is false if (!xhrBlobSupported || this.isDataUri || this.isBlobUri || (!this.hasHeaders && !preferBlob)) { return fetchImage({ resource: this, flipY: flipY, preferImageBitmap: preferImageBitmap }); } var blobPromise = this.fetchBlob(); if (!defined(blobPromise)) { return; } var supportsImageBitmap; var useImageBitmap; var generatedBlobResource; var generatedBlob; return Resource.supportsImageBitmapOptions() .then(function(result) { supportsImageBitmap = result; useImageBitmap = supportsImageBitmap && preferImageBitmap; return blobPromise; }) .then(function(blob) { if (!defined(blob)) { return; } generatedBlob = blob; if (useImageBitmap) { return Resource.createImageBitmapFromBlob(blob, { flipY: flipY, premultiplyAlpha: false }); } var blobUrl = window.URL.createObjectURL(blob); generatedBlobResource = new Resource({ url: blobUrl }); return fetchImage({ resource: generatedBlobResource, flipY: flipY, preferImageBitmap: false }); }) .then(function(image) { if (!defined(image)) { return; } // The blob object may be needed for use by a TileDiscardPolicy, // so attach it to the image. image.blob = generatedBlob; if (useImageBitmap) { return image; } window.URL.revokeObjectURL(generatedBlobResource.url); return image; }) .otherwise(function(error) { if (defined(generatedBlobResource)) { window.URL.revokeObjectURL(generatedBlobResource.url); } // If the blob load succeeded but the image decode failed, attach the blob // to the error object for use by a TileDiscardPolicy. // In particular, BingMapsImageryProvider uses this to detect the // zero-length response that is returned when a tile is not available. error.blob = generatedBlob; return when.reject(error); }); }; /** * Fetches an image and returns a promise to it. * * @param {Object} [options] An object with the following properties. * @param {Resource} [options.resource] Resource object that points to an image to fetch. * @param {Boolean} [options.preferImageBitmap] If true, image will be decoded during fetch and an <code>ImageBitmap</code> is returned. * @param {Boolean} [options.flipY] If true, image will be vertically flipped during decode. Only applies if the browser supports <code>createImageBitmap</code>. * * @private */ function fetchImage(options) { var resource = options.resource; var flipY = options.flipY; var preferImageBitmap = options.preferImageBitmap; var request = resource.request; request.url = resource.url; request.requestFunction = function() { var crossOrigin = false; // data URIs can't have crossorigin set. if (!resource.isDataUri && !resource.isBlobUri) { crossOrigin = resource.isCrossOriginUrl; } var deferred = when.defer(); Resource._Implementations.createImage(request, crossOrigin, deferred, flipY, preferImageBitmap); return deferred.promise; }; var promise = RequestScheduler.request(request); if (!defined(promise)) { return; } return promise .otherwise(function(e) { // Don't retry cancelled or otherwise aborted requests if (request.state !== RequestState.FAILED) { return when.reject(e); } return resource.retryOnError(e) .then(function(retry) { if (retry) { // Reset request so it can try again request.state = RequestState.UNISSUED; request.deferred = undefined; return fetchImage({ resource: resource, flipY: flipY, preferImageBitmap: preferImageBitmap }); } return when.reject(e); }); }); } /** * Creates a Resource and calls fetchImage() on it. * * @param {String|Object} options A url or an object with the following properties * @param {String} options.url The url of the resource. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}). * @param {Object} [options.headers={}] Additional HTTP headers that will be sent. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource. * @param {Boolean} [options.flipY=false] Whether to vertically flip the image during fetch and decode. Only applies when requesting an image and the browser supports <code>createImageBitmap</code>. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only. * @param {Boolean} [options.preferBlob=false] If true, we will load the image via a blob. * @param {Boolean} [options.preferImageBitmap=false] If true, image will be decoded during fetch and an <code>ImageBitmap</code> is returned. * @returns {Promise.<ImageBitmap>|Promise.<Image>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. */ Resource.fetchImage = function (options) { var resource = new Resource(options); return resource.fetchImage({ flipY: options.flipY, preferBlob: options.preferBlob, preferImageBitmap: options.preferImageBitmap }); }; /** * Asynchronously loads the given resource as text. Returns a promise that will resolve to * a String once loaded, or reject if the resource failed to load. The data is loaded * using XMLHttpRequest, which means that in order to make requests to another origin, * the server must have Cross-Origin Resource Sharing (CORS) headers enabled. * * @returns {Promise.<String>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. * * @example * // load text from a URL, setting a custom header * var resource = new Resource({ * url: 'http://someUrl.com/someJson.txt', * headers: { * 'X-Custom-Header' : 'some value' * } * }); * resource.fetchText().then(function(text) { * // Do something with the text * }).otherwise(function(error) { * // an error occurred * }); * * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest} * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} */ Resource.prototype.fetchText = function() { return this.fetch({ responseType : 'text' }); }; /** * Creates a Resource and calls fetchText() on it. * * @param {String|Object} options A url or an object with the following properties * @param {String} options.url The url of the resource. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}). * @param {Object} [options.headers={}] Additional HTTP headers that will be sent. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only. * @returns {Promise.<String>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. */ Resource.fetchText = function (options) { var resource = new Resource(options); return resource.fetchText(); }; // note: &#42;&#47;&#42; below is */* but that ends the comment block early /** * Asynchronously loads the given resource as JSON. Returns a promise that will resolve to * a JSON object once loaded, or reject if the resource failed to load. The data is loaded * using XMLHttpRequest, which means that in order to make requests to another origin, * the server must have Cross-Origin Resource Sharing (CORS) headers enabled. This function * adds 'Accept: application/json,&#42;&#47;&#42;;q=0.01' to the request headers, if not * already specified. * * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. * * * @example * resource.fetchJson().then(function(jsonData) { * // Do something with the JSON object * }).otherwise(function(error) { * // an error occurred * }); * * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} */ Resource.prototype.fetchJson = function() { var promise = this.fetch({ responseType : 'text', headers: { Accept : 'application/json,*/*;q=0.01' } }); if (!defined(promise)) { return undefined; } return promise .then(function(value) { if (!defined(value)) { return; } return JSON.parse(value); }); }; /** * Creates a Resource and calls fetchJson() on it. * * @param {String|Object} options A url or an object with the following properties * @param {String} options.url The url of the resource. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}). * @param {Object} [options.headers={}] Additional HTTP headers that will be sent. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. */ Resource.fetchJson = function (options) { var resource = new Resource(options); return resource.fetchJson(); }; /** * Asynchronously loads the given resource as XML. Returns a promise that will resolve to * an XML Document once loaded, or reject if the resource failed to load. The data is loaded * using XMLHttpRequest, which means that in order to make requests to another origin, * the server must have Cross-Origin Resource Sharing (CORS) headers enabled. * * @returns {Promise.<XMLDocument>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. * * * @example * // load XML from a URL, setting a custom header * Cesium.loadXML('http://someUrl.com/someXML.xml', { * 'X-Custom-Header' : 'some value' * }).then(function(document) { * // Do something with the document * }).otherwise(function(error) { * // an error occurred * }); * * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest} * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} */ Resource.prototype.fetchXML = function() { return this.fetch({ responseType : 'document', overrideMimeType : 'text/xml' }); }; /** * Creates a Resource and calls fetchXML() on it. * * @param {String|Object} options A url or an object with the following properties * @param {String} options.url The url of the resource. * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource. * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}). * @param {Object} [options.headers={}] Additional HTTP headers that will be sent. * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource. * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried. * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up. * @param {Request} [options.request] A Request object that will be used. Intended for internal use only. * @returns {Promise.<XMLDocument>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. */ Resource.fetchXML = function (options) { var resource = new Resource(options); return resource.fetchXML(); }; /** * Requests a resource using JSONP. * * @param {String} [callbackParameterName='callback'] The callback parameter name that the server expects. * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority. * * * @example * // load a data asynchronously * resource.fetchJsonp().then(function(data) { * // use the loaded data * }).otherwise(function(error) { * // an error occurred * }); * * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A} */ Resource.prototype.fetchJsonp = function(callbackParameterName) { callbackParameterName = defaultValue(callbackParameterName, 'callback'); checkAndResetRequest(this.request); //generate a unique function name var functionName; do { functionName = 'loadJsonp' + Math.random().toString().substring(2, 8); } while (defined(window[functionName])); return fetchJsonp(this, callbackParameterName, functionName); }; function fetchJsonp(resource, callbackParameterName, functionName) { var callbackQuery = {}; callbackQuery[callbackParameterName] = functionName; resource.setQueryParameters(callbackQuery); var request =