terriajs
Version:
Geospatial data visualization platform.
455 lines (369 loc) • 19.3 kB
JavaScript
;
/*global require,describe,it,expect,beforeEach*/
var Cartographic = require('terriajs-cesium/Source/Core/Cartographic');
var CesiumMath = require('terriajs-cesium/Source/Core/Math');
var CesiumTileLayer = require('../../lib/Map/CesiumTileLayer');
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 ImageryLayerFeatureInfo = require('terriajs-cesium/Source/Scene/ImageryLayerFeatureInfo');
var L = require('leaflet');
var Leaflet = require('../../lib/Models/Leaflet');
var loadJson = require('../../lib/Core/loadJson');
var Terria = require('../../lib/Models/Terria');
var when = require('terriajs-cesium/Source/ThirdParty/when');
var DEFAULT_ZOOM_LEVEL = 5;
describe('Leaflet Model', function() {
var terria;
var leaflet;
var container, map, layers;
beforeEach(function() {
terria = new Terria({
baseUrl: './'
});
container = document.createElement('div');
container.id = 'container';
document.body.appendChild(container);
map = L.map('container').setView([-28.5, 135], DEFAULT_ZOOM_LEVEL);
spyOn(terria.tileLoadProgressEvent, 'raiseEvent');
layers = [
new L.TileLayer('http://example.com'),
new L.TileLayer('http://example.com'),
// Make sure there's a non-tile layer in there to make sure we're able to handle those.
new L.ImageOverlay('http://example.com', L.latLngBounds([1, 1], [3, 3]))
];
});
afterEach(function() {
document.body.removeChild(container);
});
function initLeaflet() {
leaflet = new Leaflet(terria, map);
terria.leaflet = leaflet;
layers.forEach(function(layer) {
map.addLayer(layer);
});
}
describe('should trigger a tileLoadProgressEvent', function() {
['tileloadstart', 'tileload', 'load'].forEach(function(event) {
it('on ' + event, function() {
initLeaflet();
layers[0].fire(event);
expect(terria.tileLoadProgressEvent.raiseEvent).toHaveBeenCalled();
});
});
});
it('should be able to reference its container', function() {
initLeaflet();
expect(leaflet.getContainer()).toBe(container);
});
it('should trigger a tileLoadProgressEvent with the total number of tiles to be loaded for all layers', function() {
initLeaflet();
layers[0]._tiles = {
1: {loaded: undefined},
2: {loaded: undefined},
a: {loaded: +new Date()}, // This is how Leaflet marks loaded tiles
b: {loaded: undefined}
};
layers[1]._tiles = {
3: {loaded: +new Date()},
4: {loaded: undefined},
c: {loaded: +new Date()},
d: {loaded: undefined}
};
layers[1].fire('tileload');
expect(terria.tileLoadProgressEvent.raiseEvent).toHaveBeenCalledWith(5, 5);
});
describe('should change the max', function() {
it('to whatever the highest count of loading tiles so far was', function() {
initLeaflet();
changeTileLoadingCount(3);
changeTileLoadingCount(6);
changeTileLoadingCount(8);
changeTileLoadingCount(2);
expect(terria.tileLoadProgressEvent.raiseEvent.calls.mostRecent().args).toEqual([2, 8]);
});
it('to 0 when loading tile count reaches 0', function() {
initLeaflet();
changeTileLoadingCount(3);
changeTileLoadingCount(6);
changeTileLoadingCount(8);
changeTileLoadingCount(0);
expect(terria.tileLoadProgressEvent.raiseEvent.calls.mostRecent().args).toEqual([0, 0]);
changeTileLoadingCount(3);
expect(terria.tileLoadProgressEvent.raiseEvent.calls.mostRecent().args).toEqual([3, 3]);
});
function changeTileLoadingCount(count) {
var tiles = {};
// Add loading tiles
for (var i = 0; i < count; i++) {
tiles['tile '+i] = {loaded: undefined};
}
layers[0]._tiles = tiles;
layers[1]._tiles = {};
layers[0].fire('tileload');
}
});
describe('feature picking', function() {
var latlng = {lat: 50, lng: 50};
var deferred1, deferred2;
beforeEach(function() {
deferred1 = when.defer();
deferred2 = when.defer();
terria.nowViewing.items = [
{
isEnabled: true,
isShown: true,
imageryLayer: new CesiumTileLayer({
pickFeatures: jasmine.createSpy('pickFeatures').and.returnValue(deferred1.promise),
url: 'http://example.com/1',
ready: true,
tilingScheme: {
positionToTileXY: function() { return {x: 1, y: 2}; }
}
})
},
{
isEnabled: true,
isShown: true,
imageryLayer: new CesiumTileLayer({
pickFeatures: jasmine.createSpy('pickFeatures').and.returnValue(deferred2.promise),
url: 'http://example.com/2',
ready: true,
tilingScheme: {
positionToTileXY: function() { return {x: 4, y: 5}; }
}
})
},
{
isEnabled: false,
isShown: true,
imageryLayer: new CesiumTileLayer({
pickFeatures: jasmine.createSpy('pickFeatures'),
url: 'http://example.com/3',
ready: true,
tilingScheme: {
positionToTileXY: function() { return {x: 1, y: 2}; }
}
})
},
{
isEnabled: false,
isShown: true,
imageryLayer: new CesiumTileLayer({
pickFeatures: jasmine.createSpy('pickFeatures'),
url: 'http://example.com/4',
ready: true,
tilingScheme: {
positionToTileXY: function() { return {x: 1, y: 2}; }
}
})
}
];
});
describe('from location', function() {
beforeEach(function() {
initLeaflet();
});
commonFeaturePickingTests(function() {
leaflet.pickFromLocation(latlng, {});
});
it('uses tileCoordinates when provided', function() {
leaflet.pickFromLocation(latlng, {
'http://example.com/1': {x: 100, y: 200, level: 300},
'http://example.com/2': {x: 400, y: 500, level: 600}
});
expect(terria.nowViewing.items[0].imageryLayer.imageryProvider.pickFeatures.calls.argsFor(0)[0]).toBe(100);
expect(terria.nowViewing.items[0].imageryLayer.imageryProvider.pickFeatures.calls.argsFor(0)[1]).toBe(200);
expect(terria.nowViewing.items[0].imageryLayer.imageryProvider.pickFeatures.calls.argsFor(0)[2]).toBe(300);
expect(terria.nowViewing.items[1].imageryLayer.imageryProvider.pickFeatures.calls.argsFor(0)[0]).toBe(400);
expect(terria.nowViewing.items[1].imageryLayer.imageryProvider.pickFeatures.calls.argsFor(0)[1]).toBe(500);
expect(terria.nowViewing.items[1].imageryLayer.imageryProvider.pickFeatures.calls.argsFor(0)[2]).toBe(600);
});
it('adds existingFeatures to end result', function(done) {
var existing = new ImageryLayerFeatureInfo();
existing.name = 'existing';
leaflet.pickFromLocation(latlng, {}, [existing]);
finishPickingPromise();
terria.pickedFeatures.allFeaturesAvailablePromise.then(function() {
expect(terria.pickedFeatures.features[0].name).toBe('existing');
}).then(done).otherwise(done.fail);
});
});
describe('from click', function() {
var click;
beforeEach(function() {
spyOn(map, 'on').and.callFake(function(type, callback) {
if (type === 'click') {
click = callback;
}
});
initLeaflet();
});
commonFeaturePickingTests(function() {
click({
latlng: latlng
});
});
describe('when combining vector and raster features', function() {
var vectorFeature1, vectorFeature2;
beforeEach(function() {
vectorFeature1 = new Entity({
name: 'vector1'
});
vectorFeature2 = new Entity({
name: 'vector2'
});
});
it('includes vector features with click events both before and after the map click event', function(done) {
// vector and map clicks can come in any order.
leaflet.scene.featureClicked.raiseEvent(vectorFeature1, { latlng: latlng });
click({
latlng: latlng
});
leaflet.scene.featureClicked.raiseEvent(vectorFeature2, { latlng: latlng });
finishPickingPromise();
terria.pickedFeatures.allFeaturesAvailablePromise.then(function() {
expect(terria.pickedFeatures.features.length).toBe(5);
expect(terria.pickedFeatures.features[0].name).toBe('vector1');
expect(terria.pickedFeatures.features[1].name).toBe('vector2');
expect(terria.pickedFeatures.features[2].name).toBe('1');
}).then(done).otherwise(done.fail);
});
it('resets the picked vector features if a subsequent map click is made', function(done) {
leaflet.scene.featureClicked.raiseEvent(vectorFeature1, { latlng: latlng });
click({
latlng: latlng
});
leaflet.scene.featureClicked.raiseEvent(vectorFeature2, { latlng: latlng });
// The reset happens in a runLater, which a second click will always come behind in a browser,
// but this isn't guaranteed in unit tests because they're just two setTimeouts racing each other,
// so give this a healthy 50ms delay to make sure it comes in behind the 0ms delay in Leaflet.js.
setTimeout(function() {
click({
latlng: latlng
});
finishPickingPromise();
terria.pickedFeatures.allFeaturesAvailablePromise.then(function() {
expect(terria.pickedFeatures.features.length).toBe(3);
expect(terria.pickedFeatures.features[0].name).toBe('1');
}).then(done).otherwise(done.fail);
}, 50);
});
});
});
function commonFeaturePickingTests(trigger) {
it('correctly tracks loading state', function(done) {
expect(terria.pickedFeatures).toBeUndefined();
trigger();
expect(terria.pickedFeatures.isLoading).toBe(true);
finishPickingPromise();
terria.pickedFeatures.allFeaturesAvailablePromise.then(function() {
expect(terria.pickedFeatures.isLoading).toBe(false);
}).then(done).otherwise(done.fail);
});
it('records pickPosition', function() {
trigger();
expect(terria.pickedFeatures.pickPosition).toEqual(Ellipsoid.WGS84.cartographicToCartesian(Cartographic.fromDegrees(50, 50)));
});
describe('after feature picked', function() {
beforeEach(trigger);
it('populates terria.pickedFeatures', function() {
expect(terria.pickedFeatures).toBeDefined();
expect(terria.pickedFeatures.allFeaturesAvailablePromise).toBeDefined();
});
it('calls pickFeatures for all enabled and shown layers', function() {
expect(terria.nowViewing.items[0].imageryLayer.imageryProvider.pickFeatures).toHaveBeenCalledWith(
1, 2, DEFAULT_ZOOM_LEVEL, CesiumMath.toRadians(50), CesiumMath.toRadians(50)
);
expect(terria.nowViewing.items[1].imageryLayer.imageryProvider.pickFeatures).toHaveBeenCalledWith(
4, 5, DEFAULT_ZOOM_LEVEL, CesiumMath.toRadians(50), CesiumMath.toRadians(50)
);
expect(terria.nowViewing.items[2].imageryLayer.imageryProvider.pickFeatures).not.toHaveBeenCalled();
expect(terria.nowViewing.items[3].imageryLayer.imageryProvider.pickFeatures).not.toHaveBeenCalled();
});
describe('after pickFeatures returns for all layers', function() {
beforeEach(function(done) {
finishPickingPromise();
terria.pickedFeatures.allFeaturesAvailablePromise.then(done).otherwise(done.fail);
});
it('combines promise results', 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');
});
it('records pick coords', function() {
expect(terria.pickedFeatures.providerCoords).toEqual({
'http://example.com/1': {x: 1, y: 2, level: DEFAULT_ZOOM_LEVEL},
'http://example.com/2': {x: 4, y: 5, level: DEFAULT_ZOOM_LEVEL}
});
});
it('sets imageryLayer on features', function() {
expect(terria.pickedFeatures.features[0].imageryLayer).toBe(terria.nowViewing.items[0].imageryLayer);
expect(terria.pickedFeatures.features[1].imageryLayer).toBe(terria.nowViewing.items[1].imageryLayer);
});
});
});
}
it('should create GeoJSON for polygon when a rasterized polygon feature is selected', function(done) {
loadJson('test/GeoJSON/polygon.geojson').then(function(polygonGeoJson) {
initLeaflet();
var entity = new Entity();
entity.data = polygonGeoJson;
terria.selectedFeature = entity;
expect(terria.leaflet._highlightPromise).toBeDefined();
expect(terria.leaflet._removeHighlightCallback).toBeDefined();
return terria.leaflet._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) {
initLeaflet();
var entity = new Entity();
entity.data = polylineGeoJson;
terria.selectedFeature = entity;
expect(terria.leaflet._highlightPromise).toBeDefined();
expect(terria.leaflet._removeHighlightCallback).toBeDefined();
return terria.leaflet._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) {
initLeaflet();
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) {
initLeaflet();
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 finishPickingPromise() {
var featureInfo1 = new ImageryLayerFeatureInfo();
featureInfo1.name = '1';
var featureInfo2 = new ImageryLayerFeatureInfo();
featureInfo2.name = '2';
var featureInfo3 = new ImageryLayerFeatureInfo();
featureInfo3.name = '3';
deferred1.resolve([featureInfo1]);
deferred2.resolve([featureInfo2, featureInfo3]);
}
});
});