diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 8c09c5af8039..99dff0024493 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -59,5 +59,6 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu * [Ayush Khandelwal](https://github.com/ayk115) * [Aditya Raisinghani](https://github.com/adi2412) * [Ilia Choly](https://github.com/icholy) +* [Farouk Abdou](https://github.com/kaktus40) Also see [our contributors page](http://cesiumjs.org/contributors.html) for more information. diff --git a/Source/Scene/UrlTemplateImageryProvider.js b/Source/Scene/UrlTemplateImageryProvider.js new file mode 100644 index 000000000000..8a50e7262125 --- /dev/null +++ b/Source/Scene/UrlTemplateImageryProvider.js @@ -0,0 +1,450 @@ +/*global define*/ +define([ + '../Core/Cartesian2', + '../Core/Cartographic', + '../Core/Credit', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + '../Core/GeographicTilingScheme', + '../Core/loadXML', + '../Core/Rectangle', + '../Core/TileProviderError', + '../Core/WebMercatorTilingScheme', + '../ThirdParty/when', + './ImageryProvider' + ], function( + Cartesian2, + Cartographic, + Credit, + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + GeographicTilingScheme, + loadXML, + Rectangle, + TileProviderError, + WebMercatorTilingScheme, + when, + ImageryProvider) { + "use strict"; + + /** + * Provides tiled imagery following a specific URL template + * + * @alias UrlTemplateImageryProvider + * @constructor + * + * @param {Object} [options] Object with the following properties: + * @param {String} [options.url=''] The pattern to request a tile which has the following keywords:
getTileCredits
must not be called before the imagery provider is ready.
+ */
+ UrlTemplateImageryProvider.prototype.getTileCredits = function(x, y, level) {
+ return undefined;
+ };
+
+ /**
+ * Requests the image for a given tile. This function should
+ * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
+ *
+ * @param {Number} x The tile X coordinate.
+ * @param {Number} y The tile Y coordinate.
+ * @param {Number} level The tile level.
+ * @returns {Promise} A promise for the image that will resolve when the image is available, or
+ * undefined if there are too many active requests to the server, and the request
+ * should be retried later. The resolved image may be either an
+ * Image or a Canvas DOM object.
+ */
+ UrlTemplateImageryProvider.prototype.requestImage = function(x, y, level) {
+ //>>includeStart('debug', pragmas.debug);
+ if (!this._ready) {
+ throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
+ }
+ //>>includeEnd('debug');
+
+ var url = buildImageUrl(this, x, y, level);
+ return ImageryProvider.loadImage(this, url);
+ };
+
+ /**
+ * Picking features is not currently supported by this imagery provider, so this function simply returns
+ * undefined.
+ *
+ * @param {Number} x The tile X coordinate.
+ * @param {Number} y The tile Y coordinate.
+ * @param {Number} level The tile level.
+ * @param {Number} longitude The longitude at which to pick features.
+ * @param {Number} latitude The latitude at which to pick features.
+ * @return {Promise} A promise for the picked features that will resolve when the asynchronous
+ * picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
+ * instances. The array may be empty if no features are found at the given location.
+ * It may also be undefined if picking is not supported.
+ */
+ UrlTemplateImageryProvider.prototype.pickFeatures = function() {
+ return undefined;
+ };
+
+ return UrlTemplateImageryProvider;
+});
diff --git a/Specs/Scene/UrlTemplateImageryProviderSpec.js b/Specs/Scene/UrlTemplateImageryProviderSpec.js
new file mode 100644
index 000000000000..fa3d7ef91089
--- /dev/null
+++ b/Specs/Scene/UrlTemplateImageryProviderSpec.js
@@ -0,0 +1,332 @@
+/*global defineSuite*/
+defineSuite([
+ 'Scene/UrlTemplateImageryProvider',
+ 'Core/DefaultProxy',
+ 'Core/GeographicTilingScheme',
+ 'Core/loadImage',
+ 'Core/Math',
+ 'Core/Rectangle',
+ 'Core/WebMercatorTilingScheme',
+ 'Scene/Imagery',
+ 'Scene/ImageryLayer',
+ 'Scene/ImageryProvider',
+ 'Scene/ImageryState',
+ 'Specs/pollToPromise',
+ 'ThirdParty/when'
+], function(
+ UrlTemplateImageryProvider,
+ DefaultProxy,
+ GeographicTilingScheme,
+ loadImage,
+ CesiumMath,
+ Rectangle,
+ WebMercatorTilingScheme,
+ Imagery,
+ ImageryLayer,
+ ImageryProvider,
+ ImageryState,
+ pollToPromise,
+ when) {
+ "use strict";
+ /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn*/
+
+ afterEach(function() {
+ loadImage.createImage = loadImage.defaultCreateImage;
+ });
+
+ it('conforms to ImageryProvider interface', function() {
+ expect(UrlTemplateImageryProvider).toConformToInterface(ImageryProvider);
+ });
+
+ it('requires the url to be specified', function() {
+ function createWithoutUrl() {
+ return new UrlTemplateImageryProvider({});
+ }
+ expect(createWithoutUrl).toThrowDeveloperError();
+ });
+
+ it('returns valid value for hasAlphaChannel', function() {
+ var provider = new UrlTemplateImageryProvider({
+ url: 'made/up/tms/server/'
+ });
+
+ return pollToPromise(function() {
+ return provider.ready;
+ }).then(function() {
+ expect(typeof provider.hasAlphaChannel).toBe('boolean');
+ });
+ });
+
+ it('requestImage returns a promise for an image and loads it for cross-origin use', function() {
+ var provider = new UrlTemplateImageryProvider({
+ url: 'made/up/tms/server/{Z}/{X}/{reverseY}'
+ });
+
+ expect(provider.url).toEqual('made/up/tms/server/{Z}/{X}/{reverseY}');
+
+ return pollToPromise(function() {
+ return provider.ready;
+ }).then(function() {
+ expect(provider.tileWidth).toEqual(256);
+ expect(provider.tileHeight).toEqual(256);
+ expect(provider.maximumLevel).toEqual(18);
+ expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme);
+ expect(provider.rectangle).toEqual(new WebMercatorTilingScheme().rectangle);
+
+ spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) {
+ // Just return any old image.
+ loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
+ });
+
+ return provider.requestImage(0, 0, 0).then(function(image) {
+ expect(loadImage.createImage).toHaveBeenCalled();
+ expect(image).toBeInstanceOf(Image);
+ });
+ });
+ });
+
+ it('when no credit is supplied, the provider has no logo', function() {
+ var provider = new UrlTemplateImageryProvider({
+ url: 'made/up/tms/server'
+ });
+ expect(provider.credit).toBeUndefined();
+ });
+
+ it('turns the supplied credit into a logo', function() {
+ var providerWithCredit = new UrlTemplateImageryProvider({
+ url: 'made/up/gms/server',
+ credit: 'Thanks to our awesome made up source of this imagery!'
+ });
+ expect(providerWithCredit.credit).toBeDefined();
+ });
+
+ it('routes tile requests through a proxy if one is specified', function() {
+ var proxy = new DefaultProxy('/proxy/');
+ var provider = new UrlTemplateImageryProvider({
+ url: 'made/up/tms/server/{Z}/{X}/{reverseY}',
+ proxy: proxy
+ });
+
+ return pollToPromise(function() {
+ return provider.ready;
+ }).then(function() {
+ expect(provider.proxy).toEqual(proxy);
+
+ spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) {
+ expect(url.indexOf(proxy.getURL('made/up/tms/server'))).toEqual(0);
+
+ // Just return any old image.
+ loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
+ });
+
+ return provider.requestImage(0, 0, 0).then(function(image) {
+ expect(loadImage.createImage).toHaveBeenCalled();
+ expect(image).toBeInstanceOf(Image);
+ });
+ });
+ });
+
+ it('rectangle passed to constructor does not affect tile numbering', function() {
+ var rectangle = new Rectangle(0.1, 0.2, 0.3, 0.4);
+ var provider = new UrlTemplateImageryProvider({
+ url: 'made/up/tms/server/{Z}/{X}/{reverseY}',
+ rectangle: rectangle
+ });
+
+ return pollToPromise(function() {
+ return provider.ready;
+ }).then(function() {
+ expect(provider.tileWidth).toEqual(256);
+ expect(provider.tileHeight).toEqual(256);
+ expect(provider.maximumLevel).toEqual(18);
+ expect(provider.tilingScheme).toBeInstanceOf(WebMercatorTilingScheme);
+ expect(provider.rectangle).toEqual(rectangle);
+ expect(provider.tileDiscardPolicy).toBeUndefined();
+
+ spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) {
+ expect(url).toContain('/0/0/0');
+
+ // Just return any old image.
+ loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
+ });
+
+ return provider.requestImage(0, 0, 0).then(function(image) {
+ expect(loadImage.createImage).toHaveBeenCalled();
+ expect(image).toBeInstanceOf(Image);
+ });
+ });
+ });
+
+ it('uses maximumLevel passed to constructor', function() {
+ var provider = new UrlTemplateImageryProvider({
+ url: 'made/up/tms/server',
+ maximumLevel: 5
+ });
+
+ return pollToPromise(function() {
+ return provider.ready;
+ }).then(function() {
+ expect(provider.maximumLevel).toEqual(5);
+ });
+ });
+
+ it('raises error event when image cannot be loaded', function() {
+ var provider = new UrlTemplateImageryProvider({
+ url: 'made/up/tms/server'
+ });
+
+ var layer = new ImageryLayer(provider);
+
+ var tries = 0;
+ provider.errorEvent.addEventListener(function(error) {
+ expect(error.timesRetried).toEqual(tries);
+ ++tries;
+ if (tries < 3) {
+ error.retry = true;
+ }
+ });
+
+ loadImage.createImage = function(url, crossOrigin, deferred) {
+ if (tries === 2) {
+ // Succeed after 2 tries
+ loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
+ } else {
+ // fail
+ setTimeout(function() {
+ deferred.reject();
+ }, 1);
+ }
+ };
+
+ return pollToPromise(function() {
+ return provider.ready;
+ }).then(function() {
+ var imagery = new Imagery(layer, 0, 0, 0);
+ imagery.addReference();
+ layer._requestImagery(imagery);
+
+ return pollToPromise(function() {
+ return imagery.state === ImageryState.RECEIVED;
+ }).then(function() {
+ expect(imagery.image).toBeInstanceOf(Image);
+ expect(tries).toEqual(2);
+ imagery.releaseReference();
+ });
+ });
+ });
+
+ it('evaluation of pattern X Y reverseX reverseY Z', function() {
+ var provider = new UrlTemplateImageryProvider({
+ url: 'made/up/tms/server/{Z}/{reverseY}/{Y}/{reverseX}/{X}.PNG',
+ tilingScheme: new GeographicTilingScheme()
+ });
+
+ return pollToPromise(function() {
+ return provider.ready;
+ }).then(function() {
+ spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) {
+ expect(url).toEqual('made/up/tms/server/2/2/1/4/3.PNG');
+
+ // Just return any old image.
+ loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
+ });
+
+ return provider.requestImage(3, 1, 2).then(function(image) {
+ expect(loadImage.createImage).toHaveBeenCalled();
+ expect(image).toBeInstanceOf(Image);
+ });
+ });
+ });
+
+ it('evaluation of pattern north', function() {
+ var provider = new UrlTemplateImageryProvider({
+ url: '{north}',
+ tilingScheme: new GeographicTilingScheme()
+ });
+
+ pollToPromise(function() {
+ return provider.ready;
+ }).then(function() {
+ spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) {
+ expect(url).toEqualEpsilon(45.088235294117645, CesiumMath.EPSILON11);
+
+ // Just return any old image.
+ loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
+ });
+
+ return provider.requestImage(3, 1, 2).then(function(image) {
+ expect(loadImage.createImage).toHaveBeenCalled();
+ expect(image).toBeInstanceOf(Image);
+ });
+ });
+ });
+
+ it('evaluation of pattern south', function() {
+ var provider = new UrlTemplateImageryProvider({
+ url: '{south}',
+ tilingScheme: new GeographicTilingScheme()
+ });
+
+ pollToPromise(function() {
+ return provider.ready;
+ }).then(function() {
+ spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) {
+ expect(url).toEqualEpsilon(-0.08823529411764706, CesiumMath.EPSILON11);
+
+ // Just return any old image.
+ loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
+ });
+
+ return provider.requestImage(3, 1, 2).then(function(image) {
+ expect(loadImage.createImage).toHaveBeenCalled();
+ expect(image).toBeInstanceOf(Image);
+ });
+ });
+ });
+
+ it('evaluation of pattern east', function() {
+ var provider = new UrlTemplateImageryProvider({
+ url: '{east}',
+ tilingScheme: new GeographicTilingScheme()
+ });
+
+ pollToPromise(function() {
+ return provider.ready;
+ }).then(function() {
+ spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) {
+ expect(url).toEqualEpsilon(0.08823529411764706, CesiumMath.EPSILON11);
+
+ // Just return any old image.
+ loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
+ });
+
+ return provider.requestImage(3, 1, 2).then(function(image) {
+ expect(loadImage.createImage).toHaveBeenCalled();
+ expect(image).toBeInstanceOf(Image);
+ });
+ });
+ });
+
+ it('evaluation of pattern west', function() {
+ var provider = new UrlTemplateImageryProvider({
+ url: '{west}',
+ tilingScheme: new GeographicTilingScheme()
+ });
+
+ pollToPromise(function() {
+ return provider.ready;
+ }).then(function() {
+ spyOn(loadImage, 'createImage').and.callFake(function(url, crossOrigin, deferred) {
+ expect(url).toEqualEpsilon(-45.088235294117645, CesiumMath.EPSILON11);
+
+ // Just return any old image.
+ loadImage.defaultCreateImage('Data/Images/Red16x16.png', crossOrigin, deferred);
+ });
+
+ return provider.requestImage(3, 1, 2).then(function(image) {
+ expect(loadImage.createImage).toHaveBeenCalled();
+ expect(image).toBeInstanceOf(Image);
+ });
+ });
+ });
+});
\ No newline at end of file