defineSuite([ 'Scene/ImageryLayerCollection', 'Core/Cartesian3', 'Core/Ellipsoid', 'Core/Event', 'Core/GeographicTilingScheme', 'Core/Matrix4', 'Core/Ray', 'Core/Rectangle', 'Core/WebMercatorProjection', 'Core/WebMercatorTilingScheme', 'Scene/Globe', 'Scene/ImageryLayer', 'Scene/ImageryLayerFeatureInfo', 'Scene/ImageryProvider', 'Specs/createScene', 'Specs/pollToPromise', 'ThirdParty/when' ], function( ImageryLayerCollection, Cartesian3, Ellipsoid, Event, GeographicTilingScheme, Matrix4, Ray, Rectangle, WebMercatorProjection, WebMercatorTilingScheme, Globe, ImageryLayer, ImageryLayerFeatureInfo, ImageryProvider, createScene, pollToPromise, when) { 'use strict'; var fakeProvider = { isReady : function() { return false; } }; it('tracks the base layer on add', function() { var layer1 = new ImageryLayer(fakeProvider); var layer2 = new ImageryLayer(fakeProvider); var layer3 = new ImageryLayer(fakeProvider); var collection = new ImageryLayerCollection(); expect(layer1.isBaseLayer()).toEqual(false); collection.add(layer1); expect(layer1.isBaseLayer()).toEqual(true); collection.add(layer2); expect(layer1.isBaseLayer()).toEqual(true); expect(layer2.isBaseLayer()).toEqual(false); collection.add(layer3, 0); expect(layer1.isBaseLayer()).toEqual(false); expect(layer2.isBaseLayer()).toEqual(false); expect(layer3.isBaseLayer()).toEqual(true); }); it('tracks the base layer on remove', function() { var layer1 = new ImageryLayer(fakeProvider); var layer2 = new ImageryLayer(fakeProvider); var layer3 = new ImageryLayer(fakeProvider); var collection = new ImageryLayerCollection(); collection.add(layer1); collection.add(layer2); collection.add(layer3); expect(layer1.isBaseLayer()).toEqual(true); expect(layer2.isBaseLayer()).toEqual(false); expect(layer3.isBaseLayer()).toEqual(false); collection.remove(layer1); expect(layer2.isBaseLayer()).toEqual(true); expect(layer3.isBaseLayer()).toEqual(false); collection.remove(layer3); expect(layer2.isBaseLayer()).toEqual(true); }); it('updates isBaseLayer on re-add', function() { var layer1 = new ImageryLayer(fakeProvider); var layer2 = new ImageryLayer(fakeProvider); var collection = new ImageryLayerCollection(); layer1._isBaseLayer = true; layer2._isBaseLayer = true; collection.add(layer1); collection.add(layer2); expect(layer1.isBaseLayer()).toEqual(true); expect(layer2.isBaseLayer()).toEqual(false); }); it('does not crash when raising and lowering a single layer.', function() { var layer1 = new ImageryLayer(fakeProvider); var collection = new ImageryLayerCollection(); collection.add(layer1); collection.raise(layer1); collection.lower(layer1); collection.raiseToTop(layer1); collection.lowerToBottom(layer1); }); it('tracks the base layer on raise and lower', function() { var layer1 = new ImageryLayer(fakeProvider); var layer2 = new ImageryLayer(fakeProvider); var layer3 = new ImageryLayer(fakeProvider); var collection = new ImageryLayerCollection(); collection.add(layer1); collection.add(layer2); collection.add(layer3); expect(layer1.isBaseLayer()).toEqual(true); expect(layer2.isBaseLayer()).toEqual(false); expect(layer3.isBaseLayer()).toEqual(false); collection.lower(layer1); expect(layer1.isBaseLayer()).toEqual(true); expect(layer2.isBaseLayer()).toEqual(false); expect(layer3.isBaseLayer()).toEqual(false); collection.raise(layer1); expect(layer1.isBaseLayer()).toEqual(false); expect(layer2.isBaseLayer()).toEqual(true); expect(layer3.isBaseLayer()).toEqual(false); collection.lower(layer1); expect(layer1.isBaseLayer()).toEqual(true); expect(layer2.isBaseLayer()).toEqual(false); expect(layer3.isBaseLayer()).toEqual(false); }); it('tracks the base layer on raiseToTop to lowerToBottom', function() { var layer1 = new ImageryLayer(fakeProvider); var layer2 = new ImageryLayer(fakeProvider); var layer3 = new ImageryLayer(fakeProvider); var collection = new ImageryLayerCollection(); collection.add(layer1); collection.add(layer2); collection.add(layer3); expect(layer1.isBaseLayer()).toEqual(true); expect(layer2.isBaseLayer()).toEqual(false); expect(layer3.isBaseLayer()).toEqual(false); collection.raiseToTop(layer1); expect(layer1.isBaseLayer()).toEqual(false); expect(layer2.isBaseLayer()).toEqual(true); expect(layer3.isBaseLayer()).toEqual(false); collection.lowerToBottom(layer1); expect(layer1.isBaseLayer()).toEqual(true); expect(layer2.isBaseLayer()).toEqual(false); expect(layer3.isBaseLayer()).toEqual(false); }); it('add throws when layer is undefined', function() { var collection = new ImageryLayerCollection(); expect(function() { collection.add(undefined); }).toThrowDeveloperError(); }); it('addImageryProvider throws when imageryProvider is undefined', function() { var collection = new ImageryLayerCollection(); expect(function() { collection.addImageryProvider(undefined); }).toThrowDeveloperError(); }); it('add throws when index is outside valid range', function() { var collection = new ImageryLayerCollection(); var layer1 = new ImageryLayer(fakeProvider); var layer2 = new ImageryLayer(fakeProvider); expect(function() { collection.add(layer1, 1); }).toThrowDeveloperError(); expect(function() { collection.add(layer1, -1); }).toThrowDeveloperError(); collection.add(layer1, 0); expect(function() { collection.add(layer2, -1); }).toThrowDeveloperError(); expect(function() { collection.add(layer2, 2); }).toThrowDeveloperError(); collection.add(layer2, 0); }); it('remove ignores request to remove a layer that does not exist in the collection', function() { var collection = new ImageryLayerCollection(); var layer1 = new ImageryLayer(fakeProvider); expect(collection.remove(layer1)).toBe(false); }); it('contains works as expected', function() { var collection = new ImageryLayerCollection(); var layer1 = new ImageryLayer(fakeProvider); var layer2 = new ImageryLayer(fakeProvider); expect(collection.contains(layer1)).toEqual(false); expect(collection.contains(layer2)).toEqual(false); collection.add(layer1); expect(collection.contains(layer1)).toEqual(true); expect(collection.contains(layer2)).toEqual(false); collection.add(layer2); expect(collection.contains(layer1)).toEqual(true); expect(collection.contains(layer2)).toEqual(true); collection.remove(layer1); expect(collection.contains(layer1)).toEqual(false); expect(collection.contains(layer2)).toEqual(true); collection.remove(layer2); expect(collection.contains(layer1)).toEqual(false); expect(collection.contains(layer2)).toEqual(false); }); it('get throws if index is not provided', function() { var collection = new ImageryLayerCollection(); expect(function() { collection.get(); }).toThrowDeveloperError(); }); it('throws when raising an undefined layer', function() { var collection = new ImageryLayerCollection(); expect(function() { collection.raise(undefined); }).toThrowDeveloperError(); }); it('throws when raising a layer not in the collection', function() { var collection = new ImageryLayerCollection(); var layer1 = new ImageryLayer(fakeProvider); expect(function() { collection.raise(layer1); }).toThrowDeveloperError(); }); it('reports whether or not it is destroyed', function() { var collection = new ImageryLayerCollection(); expect(collection.isDestroyed()).toEqual(false); collection.destroy(); expect(collection.isDestroyed()).toEqual(true); }); describe('pickImageryLayerFeatures', function() { var scene; var globe; var camera; beforeAll(function() { scene = createScene(); globe = scene.globe = new Globe(); camera = scene.camera; scene.frameState.passes.render = true; }); afterAll(function() { scene.destroyForSpecs(); }); beforeEach(function() { globe.imageryLayers.removeAll(); }); /** * Repeatedly calls update until the load queue is empty. Returns a promise that resolves * once the load queue is empty. */ function updateUntilDone(globe) { // update until the load queue is empty. return pollToPromise(function() { globe._surface._debug.enableDebugOutput = true; scene.render(); return globe._surface.tileProvider.ready && globe._surface._tileLoadQueueHigh.length === 0 && globe._surface._tileLoadQueueMedium.length === 0 && globe._surface._tileLoadQueueLow.length === 0 && globe._surface._debug.tilesWaitingForChildren === 0; }); } it('returns undefined when pick ray does not intersect surface', function() { var ellipsoid = Ellipsoid.WGS84; camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0)); var ray = new Ray(camera.position, Cartesian3.negate(camera.direction, new Cartesian3())); var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene); expect(featuresPromise).toBeUndefined(); }); it('returns undefined when globe has no pickable layers', function() { var ellipsoid = Ellipsoid.WGS84; camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0)); var ray = new Ray(camera.position, camera.direction); var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene); expect(featuresPromise).toBeUndefined(); }); it('returns undefined when ImageryProvider does not implement pickFeatures', function() { var provider = { ready : true, rectangle : Rectangle.MAX_VALUE, tileWidth : 256, tileHeight : 256, maximumLevel : 0, minimumLevel : 0, tilingScheme : new GeographicTilingScheme(), errorEvent : new Event(), hasAlphaChannel : true, requestImage : function(x, y, level) { return ImageryProvider.loadImage(this, 'Data/Images/Blue.png'); } }; globe.imageryLayers.addImageryProvider(provider); return updateUntilDone(globe).then(function() { var ellipsoid = Ellipsoid.WGS84; camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0)); var ray = new Ray(camera.position, camera.direction); var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene); expect(featuresPromise).toBeUndefined(); }); }); it('returns undefined when ImageryProvider.pickFeatures returns undefined', function() { var provider = { ready : true, rectangle : Rectangle.MAX_VALUE, tileWidth : 256, tileHeight : 256, maximumLevel : 0, minimumLevel : 0, tilingScheme : new GeographicTilingScheme(), errorEvent : new Event(), hasAlphaChannel : true, pickFeatures : function(x, y, level, longitude, latitude) { return undefined; }, requestImage : function(x, y, level) { return ImageryProvider.loadImage(this, 'Data/Images/Blue.png'); } }; globe.imageryLayers.addImageryProvider(provider); return updateUntilDone(globe).then(function() { var ellipsoid = Ellipsoid.WGS84; camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0)); var ray = new Ray(camera.position, camera.direction); var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene); expect(featuresPromise).toBeUndefined(); }); }); it('returns features from one layer', function() { var provider = { ready : true, rectangle : Rectangle.MAX_VALUE, tileWidth : 256, tileHeight : 256, maximumLevel : 0, minimumLevel : 0, tilingScheme : new GeographicTilingScheme(), errorEvent : new Event(), hasAlphaChannel : true, pickFeatures : function(x, y, level, longitude, latitude) { var deferred = when.defer(); setTimeout(function() { var featureInfo = new ImageryLayerFeatureInfo(); featureInfo.name = 'Foo'; featureInfo.description = 'Foo!'; deferred.resolve([featureInfo]); }, 1); return deferred.promise; }, requestImage : function(x, y, level) { return ImageryProvider.loadImage(this, 'Data/Images/Blue.png'); } }; var currentLayer = globe.imageryLayers.addImageryProvider(provider); return updateUntilDone(globe).then(function() { var ellipsoid = Ellipsoid.WGS84; camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0)); camera.lookAtTransform(Matrix4.IDENTITY); var ray = new Ray(camera.position, camera.direction); var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene); expect(featuresPromise).toBeDefined(); return featuresPromise.then(function(features) { expect(features.length).toBe(1); expect(features[0].name).toEqual('Foo'); expect(features[0].description).toContain('Foo!'); expect(features[0].imageryLayer).toBe(currentLayer); }); }); }); it('returns features from two layers', function() { var provider1 = { ready : true, rectangle : Rectangle.MAX_VALUE, tileWidth : 256, tileHeight : 256, maximumLevel : 0, minimumLevel : 0, tilingScheme : new GeographicTilingScheme(), errorEvent : new Event(), hasAlphaChannel : true, pickFeatures : function(x, y, level, longitude, latitude) { var deferred = when.defer(); setTimeout(function() { var featureInfo = new ImageryLayerFeatureInfo(); featureInfo.name = 'Foo'; featureInfo.description = 'Foo!'; deferred.resolve([featureInfo]); }, 1); return deferred.promise; }, requestImage : function(x, y, level) { return ImageryProvider.loadImage(this, 'Data/Images/Blue.png'); } }; var currentLayer1 = globe.imageryLayers.addImageryProvider(provider1); var provider2 = { ready : true, rectangle : Rectangle.MAX_VALUE, tileWidth : 256, tileHeight : 256, maximumLevel : 0, minimumLevel : 0, tilingScheme : new GeographicTilingScheme(), errorEvent : new Event(), hasAlphaChannel : true, pickFeatures : function(x, y, level, longitude, latitude) { var deferred = when.defer(); setTimeout(function() { var featureInfo = new ImageryLayerFeatureInfo(); featureInfo.name = 'Bar'; featureInfo.description = 'Bar!'; deferred.resolve([featureInfo]); }, 1); return deferred.promise; }, requestImage : function(x, y, level) { return ImageryProvider.loadImage(this, 'Data/Images/Green.png'); } }; var currentLayer2 = globe.imageryLayers.addImageryProvider(provider2); return updateUntilDone(globe).then(function() { var ellipsoid = Ellipsoid.WGS84; camera.lookAt(new Cartesian3(ellipsoid.maximumRadius, 0.0, 0.0), new Cartesian3(0.0, 0.0, 100.0)); camera.lookAtTransform(Matrix4.IDENTITY); var ray = new Ray(camera.position, camera.direction); var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene); expect(featuresPromise).toBeDefined(); return featuresPromise.then(function(features) { expect(features.length).toBe(2); expect(features[0].name).toEqual('Bar'); expect(features[0].description).toContain('Bar!'); expect(features[0].imageryLayer).toBe(currentLayer2); expect(features[1].name).toEqual('Foo'); expect(features[1].description).toContain('Foo!'); expect(features[1].imageryLayer).toBe(currentLayer1); }); }); }); it('correctly picks from a terrain tile that is partially covered by correct-level imagery and partially covered by imagery from an ancestor level', function() { var provider = { ready : true, rectangle : new Rectangle(-Math.PI, -WebMercatorProjection.MaximumLatitude, Math.PI, WebMercatorProjection.MaximumLatitude), tileWidth : 256, tileHeight : 256, maximumLevel : 1, minimumLevel : 1, tilingScheme : new WebMercatorTilingScheme(), errorEvent : new Event(), hasAlphaChannel : true, pickFeatures : function(x, y, level, longitude, latitude) { var deferred = when.defer(); setTimeout(function() { var featureInfo = new ImageryLayerFeatureInfo(); featureInfo.name = 'L' + level + 'X' + x + 'Y' + y; deferred.resolve([featureInfo]); }, 1); return deferred.promise; }, requestImage : function(x, y, level) { // At level 1, only the northwest quadrant has a valid tile. if (level !== 1 || (x === 0 && y === 0)) { return ImageryProvider.loadImage(this, 'Data/Images/Blue.png'); } return when.reject(); } }; globe.imageryLayers.addImageryProvider(provider); camera.setView({ destination : Rectangle.fromDegrees(-180.0, 0, 0, 90) }); return updateUntilDone(globe).then(function() { var ray = new Ray(camera.position, camera.direction); var featuresPromise = scene.imageryLayers.pickImageryLayerFeatures(ray, scene); expect(featuresPromise).toBeDefined(); return featuresPromise.then(function(features) { // Verify that we don't end up picking from imagery level 0. expect(features.length).toBe(1); expect(features[0].name).toEqual('L1X0Y0'); }); }); }); }); }, 'WebGL');