terriajs
Version:
Geospatial data visualization platform.
427 lines (337 loc) • 18.7 kB
JavaScript
;
/*global require,describe,xdescribe,it,expect,beforeEach*/
var Cartographic = require('terriajs-cesium/Source/Core/Cartographic');
var Cesium = require('../../lib/Models/Cesium');
var CesiumMath = require('terriajs-cesium/Source/Core/Math');
var CesiumWidget = require('terriajs-cesium/Source/Widgets/CesiumWidget/CesiumWidget');
var Color = require('terriajs-cesium/Source/Core/Color');
var Ellipsoid = require('terriajs-cesium/Source/Core/Ellipsoid');
var Entity = require('terriajs-cesium/Source/DataSources/Entity');
var GeoJsonDataSource = require('terriajs-cesium/Source/DataSources/GeoJsonDataSource');
var ImageryLayer = require('terriajs-cesium/Source/Scene/ImageryLayer');
var ImageryLayerFeatureInfo = require('terriajs-cesium/Source/Scene/ImageryLayerFeatureInfo');
var loadJson = require('../../lib/Core/loadJson');
var Rectangle = require('terriajs-cesium/Source/Core/Rectangle');
var SceneTransforms = require('terriajs-cesium/Source/Scene/SceneTransforms');
var supportsWebGL = require('../../lib/Core/supportsWebGL');
var Terria = require('../../lib/Models/Terria');
var TileBoundingRegion = require('terriajs-cesium/Source/Scene/TileBoundingRegion');
var TileCoordinatesImageryProvider = require('terriajs-cesium/Source/Scene/TileCoordinatesImageryProvider');
var when = require('terriajs-cesium/Source/ThirdParty/when');
var describeIfSupported = supportsWebGL() ? describe : xdescribe;
describeIfSupported('Cesium Model', function() {
var terria;
var cesium;
var container;
var LAT_DEGREES = 50;
var LONG_DEGREES = 100;
var HEIGHT = 1000;
var LAT_RAD = CesiumMath.toRadians(LAT_DEGREES);
var LONG_RAD = CesiumMath.toRadians(LONG_DEGREES);
var EXPECTED_POS = Ellipsoid.WGS84.cartographicToCartesian(Cartographic.fromDegrees(LONG_DEGREES, LAT_DEGREES, HEIGHT));
var RECTANGLE_CONTAINING_EXPECTED_POS = new Rectangle(LONG_RAD - 0.1, LAT_RAD - 0.1, LONG_RAD + 0.1, LAT_RAD + 0.1);
var expectedPosScreenCoords;
var imageryLayers, imageryLayerPromises;
beforeEach(function() {
terria = new Terria({
baseUrl: './'
});
container = document.createElement('div');
container.id = 'container';
document.body.appendChild(container);
spyOn(terria.tileLoadProgressEvent, 'raiseEvent');
var cesiumWidget = new CesiumWidget(container);
spyOn(cesiumWidget.screenSpaceEventHandler, 'setInputAction');
cesium = new Cesium(terria, cesiumWidget);
cesium.scene.globe._imageryLayerCollection.removeAll(true);
expectedPosScreenCoords = SceneTransforms.wgs84ToWindowCoordinates(cesium.scene, EXPECTED_POS);
imageryLayers = [];
imageryLayerPromises = [];
var IMAGERY_PROVIDER_URLS = ['http://example.com/1', 'http://example.com/2'];
IMAGERY_PROVIDER_URLS.forEach(function(url) {
var provider = new TileCoordinatesImageryProvider();
provider.url = url;
var deferred = when.defer();
spyOn(provider, 'pickFeatures').and.returnValue(deferred.promise);
imageryLayerPromises.push(deferred);
var layer = new ImageryLayer(provider);
layer._rectangle = RECTANGLE_CONTAINING_EXPECTED_POS;
imageryLayers.push(layer);
cesium.scene.globe._imageryLayerCollection.add(layer);
});
terria.cesium = cesium;
});
afterEach(function() {
cesium.viewer.destroy();
document.body.removeChild(container);
});
it('should be able to reference its container', function() {
expect(cesium.getContainer()).toBe(container);
});
it('should trigger terria.tileLoadProgressEvent on globe tileLoadProgressEvent', function() {
cesium.scene.globe.tileLoadProgressEvent.raiseEvent(3);
expect(terria.tileLoadProgressEvent.raiseEvent).toHaveBeenCalledWith(3, 3);
});
it('should retain the maximum length of tiles to be loaded', function() {
cesium.scene.globe.tileLoadProgressEvent.raiseEvent(3);
cesium.scene.globe.tileLoadProgressEvent.raiseEvent(7);
cesium.scene.globe.tileLoadProgressEvent.raiseEvent(4);
cesium.scene.globe.tileLoadProgressEvent.raiseEvent(2);
expect(terria.tileLoadProgressEvent.raiseEvent).toHaveBeenCalledWith(2, 7);
});
it('should reset maximum length when the number of tiles to be loaded reaches 0', function() {
cesium.scene.globe.tileLoadProgressEvent.raiseEvent(3);
cesium.scene.globe.tileLoadProgressEvent.raiseEvent(7);
cesium.scene.globe.tileLoadProgressEvent.raiseEvent(4);
cesium.scene.globe.tileLoadProgressEvent.raiseEvent(0);
expect(terria.tileLoadProgressEvent.raiseEvent.calls.mostRecent().args).toEqual([0, 0]);
cesium.scene.globe.tileLoadProgressEvent.raiseEvent(2);
expect(terria.tileLoadProgressEvent.raiseEvent.calls.mostRecent().args).toEqual([2, 2]);
});
describe('feature picking', function() {
describe('via pickFromLocation', function() {
it('should populate terria.pickedFeatures', function() {
expect(terria.pickedFeatures).toBeUndefined();
cesium.pickFromLocation({lat: LAT_DEGREES, lng: LONG_DEGREES, height: HEIGHT}, {});
expect(terria.pickedFeatures).not.toBeUndefined();
expect(terria.pickedFeatures.pickPosition).toEqual(EXPECTED_POS);
expect(terria.pickedFeatures.allFeaturesAvailablePromise).not.toBeUndefined();
});
it('should pass tile coordinates to associated imagery provider', function() {
cesium.pickFromLocation({lat: LAT_DEGREES, lng: LONG_DEGREES, height: HEIGHT}, {
'http://example.com/1': {x: 1, y: 2, level: 3},
'http://example.com/2': {x: 4, y: 5, level: 6}
});
expect(imageryLayers[0]._imageryProvider.pickFeatures.calls.argsFor(0)).toEqual([1, 2, 3, LONG_RAD, LAT_RAD]);
expect(imageryLayers[1]._imageryProvider.pickFeatures.calls.argsFor(0)).toEqual([4, 5, 6, LONG_RAD, LAT_RAD]);
});
testFeatureInfoProcessing(function() {
cesium.pickFromLocation({lat: LAT_DEGREES, lng: LONG_DEGREES, height: HEIGHT}, {
'http://example.com/1': {x: 1, y: 2, level: 3},
'http://example.com/2': {x: 4, y: 5, level: 6}
});
});
it('existing features are included in pickFeatures along with ones got from imagery layers', function(done) {
var existingFeatures = [new ImageryLayerFeatureInfo(), new ImageryLayerFeatureInfo()];
existingFeatures[0].name = '1';
existingFeatures[1].name = '2';
cesium.pickFromLocation({lat: LAT_DEGREES, lng: LONG_DEGREES, height: HEIGHT}, {
'http://example.com/1': {
x: 1,
y: 2,
level: 3
}
}, existingFeatures);
var imageryLayerFeatureInfo = new ImageryLayerFeatureInfo();
imageryLayerFeatureInfo.name = '3';
imageryLayerPromises[0].resolve([imageryLayerFeatureInfo]);
terria.pickedFeatures.allFeaturesAvailablePromise.then(function() {
expect(terria.pickedFeatures.features[0].name).toBe('1');
expect(terria.pickedFeatures.features[1].name).toBe('2');
expect(terria.pickedFeatures.features[2].name).toBe('3');
}).then(done).otherwise(done.fail);
});
stateTests(function() {
cesium.pickFromLocation({lat: LAT_DEGREES, lng: LONG_DEGREES, height: HEIGHT}, {
'http://example.com/1': {
x: 1,
y: 2,
level: 3
}
});
});
});
describe('via clicking the screen', function() {
var doClick;
beforeEach(function() {
// There should be some way to make this work without cheating like this but I can't figure out how.
spyOn(cesium.scene.globe, 'pick').and.returnValue(EXPECTED_POS);
doClick = cesium.viewer.screenSpaceEventHandler.setInputAction.calls.argsFor(0)[0];
var tile = new TileBoundingRegion({
rectangle: RECTANGLE_CONTAINING_EXPECTED_POS
});
tile.data = {
imagery: imageryLayers.map(function(layer, i) {
return {
readyImagery: {
imageryLayer: layer,
rectangle: RECTANGLE_CONTAINING_EXPECTED_POS,
x: i + 1,
y: i + 2,
level: i + 3
},
textureCoordinateRectangle: {
x: 0.5,
z: 0.5,
y: 0.5,
w: 0.5
}
};
})
};
cesium.scene.globe._surface._tilesToRender = [tile];
});
stateTests(function() {
doClick({position: expectedPosScreenCoords});
});
testFeatureInfoProcessing(function() {
doClick({position: expectedPosScreenCoords});
});
it('includes vector features alongside raster ones', function(done) {
var vectorFeatures = [{
id: new Entity({
name: 'entity1'
})
}, {
primitive: {
id: new Entity({
name: 'entity2'
})
}
}];
spyOn(cesium.scene, 'drillPick').and.returnValue(vectorFeatures);
var rasterFeature = new ImageryLayerFeatureInfo();
rasterFeature.name = 'entity3';
imageryLayerPromises[0].resolve([rasterFeature]);
imageryLayerPromises[1].resolve([]);
doClick({position: expectedPosScreenCoords});
terria.pickedFeatures.allFeaturesAvailablePromise.then(function() {
expect(cesium.scene.drillPick).toHaveBeenCalledWith(expectedPosScreenCoords);
expect(terria.pickedFeatures.features[0].name).toBe('entity1');
expect(terria.pickedFeatures.features[1].name).toBe('entity2');
expect(terria.pickedFeatures.features[2].name).toBe('entity3');
}).then(done).otherwise(done.fail);
});
it('records tile coordinates when getting raster features', function(done) {
doClick({position: expectedPosScreenCoords});
imageryLayerPromises[0].resolve([]);
imageryLayerPromises[1].resolve([]);
terria.pickedFeatures.allFeaturesAvailablePromise.then(function() {
expect(terria.pickedFeatures.providerCoords['http://example.com/1']).toEqual({
x: 1,
y: 2,
level: 3
});
expect(terria.pickedFeatures.providerCoords['http://example.com/2']).toEqual({
x: 2,
y: 3,
level: 4
});
}).then(done).otherwise(done.fail);
});
});
});
it('should create GeoJSON for polygon when a rasterized polygon feature is selected', function(done) {
loadJson('test/GeoJSON/polygon.geojson').then(function(polygonGeoJson) {
var entity = new Entity();
entity.data = polygonGeoJson;
terria.selectedFeature = entity;
expect(terria.cesium._highlightPromise).toBeDefined();
expect(terria.cesium._removeHighlightCallback).toBeDefined();
return terria.cesium._highlightPromise.then(function() {
expect(terria.dataSources.length).toBe(1);
expect(terria.dataSources.get(0) instanceof GeoJsonDataSource).toBe(true);
});
}).then(done).otherwise(done.fail);
});
it('should create GeoJSON for polyline when a rasterized polyline feature is selected', function(done) {
loadJson('test/GeoJSON/polyline.geojson').then(function(polylineGeoJson) {
var entity = new Entity();
entity.data = polylineGeoJson;
terria.selectedFeature = entity;
expect(terria.cesium._highlightPromise).toBeDefined();
expect(terria.cesium._removeHighlightCallback).toBeDefined();
return terria.cesium._highlightPromise.then(function() {
expect(terria.dataSources.length).toBe(1);
expect(terria.dataSources.get(0) instanceof GeoJsonDataSource).toBe(true);
});
}).then(done).otherwise(done.fail);
});
it('should update the style of a vector polygon when selected', function(done) {
GeoJsonDataSource.load('test/GeoJSON/polygon.geojson').then(function(dataSource) {
terria.dataSources.add(dataSource);
var entity = dataSource.entities.values[0];
terria.selectedFeature = entity;
expect(entity.polygon.outlineColor.getValue()).toEqual(Color.WHITE);
terria.selectedFeature = undefined;
expect(entity.polygon.outlineColor.getValue()).not.toEqual(Color.WHITE);
}).then(done).otherwise(done.fail);
});
it('should update the style of a vector polyline when selected', function(done) {
GeoJsonDataSource.load('test/GeoJSON/polyline.geojson').then(function(dataSource) {
terria.dataSources.add(dataSource);
var entity = dataSource.entities.values[0];
terria.selectedFeature = entity;
expect(entity.polyline.width.getValue()).toEqual(2);
terria.selectedFeature = undefined;
expect(entity.polyline.width.getValue()).not.toEqual(2);
}).then(done).otherwise(done.fail);
});
function testFeatureInfoProcessing(beforeFn) {
describe('correctly processes feature info', function() {
beforeEach(beforeFn);
it('from imagery providers to a single list of entities', function(done) {
var featureInfo1 = new ImageryLayerFeatureInfo();
var featureInfo2 = new ImageryLayerFeatureInfo();
var featureInfo3 = new ImageryLayerFeatureInfo();
featureInfo1.name = 'name1';
featureInfo2.name = 'name2';
featureInfo3.name = 'name3';
imageryLayerPromises[0].resolve([featureInfo1]);
imageryLayerPromises[1].resolve([featureInfo2, featureInfo3]);
terria.pickedFeatures.allFeaturesAvailablePromise.then(function() {
expect(terria.pickedFeatures.features[0].name).toBe('name2');
expect(terria.pickedFeatures.features[1].name).toBe('name3');
expect(terria.pickedFeatures.features[2].name).toBe('name1');
expect(terria.pickedFeatures.features[0].imageryLayer).toBe(imageryLayers[1]);
expect(terria.pickedFeatures.features[1].imageryLayer).toBe(imageryLayers[1]);
expect(terria.pickedFeatures.features[2].imageryLayer).toBe(imageryLayers[0]);
}).then(done).otherwise(done.fail);
});
it('from an ImageryLayerFeatureInfo into an Entity', function(done) {
var featureInfo = new ImageryLayerFeatureInfo();
featureInfo.name = 'name1';
featureInfo.description = 'a description';
featureInfo.properties = {
foo: 'bar'
};
featureInfo.position = Cartographic.fromDegrees(LONG_DEGREES, LAT_DEGREES, HEIGHT);
featureInfo.coords = {x: 1, y: 2, level: 3};
imageryLayerPromises[0].resolve([featureInfo]);
imageryLayerPromises[1].resolve([]);
terria.pickedFeatures.allFeaturesAvailablePromise.then(function() {
var entity = terria.pickedFeatures.features[0];
expect(entity.id).toBe('name1');
expect(entity.name).toBe('name1');
expect(entity.properties.getValue().foo).toBe('bar');
expect(entity.imageryLayer).toBe(imageryLayers[0]);
expect(entity.position._value).toEqual(EXPECTED_POS);
expect(entity.coords).toEqual({x: 1, y: 2, level: 3});
done();
}).otherwise(done.fail);
});
});
}
function stateTests(beforeFn) {
describe('sets state', function() {
beforeEach(beforeFn);
it('to loading while load is in progress', function(done) {
expect(terria.pickedFeatures.isLoading).toBe(true);
imageryLayerPromises[0].resolve([]);
imageryLayerPromises[1].resolve([]);
terria.pickedFeatures.allFeaturesAvailablePromise.then(function() {
expect(terria.pickedFeatures.isLoading).toBe(false);
done();
});
});
it('to not loading and error if error occurs', function(done) {
imageryLayerPromises[0].reject(new Error('test'));
terria.pickedFeatures.allFeaturesAvailablePromise.then(function() {
expect(terria.pickedFeatures.isLoading).toBe(false);
expect(terria.pickedFeatures.error).toBeDefined();
done();
});
});
});
}
});