UNPKG

terriajs

Version:

Geospatial data visualization platform.

455 lines (369 loc) 19.3 kB
'use strict'; /*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]); } }); });