Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix viewer.zoomTo and viewer.flyTo imagery when using terrain. #6895

Merged
merged 3 commits into from
Aug 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Change Log

##### Fixes :wrench:
* The Geocoder widget now takes terrain altitude into account when calculating its final destination.
* The Viewer widget now takes terrain altitude into account when zooming or flying to imagery layers.
* Fixed bug that caused a new `ClippingPlaneCollection` to be created every frame when used with a model entity [#6872](https://github.com/AnalyticalGraphicsInc/cesium/pull/6872)

### 1.48 - 2018-08-01
Expand Down
68 changes: 68 additions & 0 deletions Source/Scene/computeFlyToLocationForRectangle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
define([
'../Core/defined',
'../Core/Rectangle',
'../Core/sampleTerrainMostDetailed',
'./SceneMode',
'../ThirdParty/when'
], function(
defined,
Rectangle,
sampleTerrainMostDetailed,
SceneMode,
when) {
'use strict';

/**
* Computes the final camera location to view a rectangle adjusted for terrain.
*
* @param {Rectangle} rectangle The rectangle being zoomed to.
* @param {Scene} scene The scene being used.
*
* @returns {Cartographic|Rectangle} The location to place the camera or the original rectangle if terrain does not have availability.
*
* @private
*/
function computeFlyToLocationForRectangle(rectangle, scene) {
var terrainProvider = scene.terrainProvider;
var availability = defined(terrainProvider) ? terrainProvider.availability : undefined;

if (!defined(availability) || scene.mode === SceneMode.SCENE2D) {
return when.resolve(rectangle);
}

var cartographics = [
Rectangle.center(rectangle),
Rectangle.southeast(rectangle),
Rectangle.southwest(rectangle),
Rectangle.northeast(rectangle),
Rectangle.northwest(rectangle)
];

return computeFlyToLocationForRectangle._sampleTerrainMostDetailed(terrainProvider, cartographics)
.then(function(positionsOnTerrain) {
var maxHeight = positionsOnTerrain.reduce(function(currentMax, item) {
return Math.max(item.height, currentMax);
}, -Number.MAX_VALUE);

var finalPosition;

var camera = scene.camera;
var mapProjection = scene.mapProjection;
var ellipsoid = mapProjection.ellipsoid;
var tmp = camera.getRectangleCameraCoordinates(rectangle);
if (scene.mode === SceneMode.SCENE3D) {
finalPosition = ellipsoid.cartesianToCartographic(tmp);
} else {
finalPosition = mapProjection.unproject(tmp);
}

finalPosition.height += maxHeight;
return finalPosition;
});
}

//Exposed for testing.
computeFlyToLocationForRectangle._sampleTerrainMostDetailed = sampleTerrainMostDetailed;

return computeFlyToLocationForRectangle;
});
42 changes: 3 additions & 39 deletions Source/Widgets/Geocoder/GeocoderViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ define([
'../../Core/Matrix4',
'../../Core/Rectangle',
'../../Core/sampleTerrainMostDetailed',
'../../Scene/SceneMode',
'../../Scene/computeFlyToLocationForRectangle',
'../../ThirdParty/knockout',
'../../ThirdParty/when',
'../createCommand',
Expand All @@ -29,7 +29,7 @@ define([
Matrix4,
Rectangle,
sampleTerrainMostDetailed,
SceneMode,
computeFlyToLocationForRectangle,
knockout,
when,
createCommand,
Expand Down Expand Up @@ -341,42 +341,6 @@ define([
adjustSuggestionsScroll(viewModel, next);
}

function computeFlyToLocationForRectangle(rectangle, terrainProvider, camera, mapProjection, sceneMode) {
var availability = defined(terrainProvider) ? terrainProvider.availability : undefined;

if (!defined(availability)) {
return when.resolve(rectangle);
}

var cartographics = [
Rectangle.center(rectangle),
Rectangle.southeast(rectangle),
Rectangle.southwest(rectangle),
Rectangle.northeast(rectangle),
Rectangle.northwest(rectangle)
];

return sampleTerrainMostDetailed(terrainProvider, cartographics)
.then(function(positionsOnTerrain) {
var maxHeight = positionsOnTerrain.reduce(function(currentMax, item) {
return Math.max(item.height, currentMax);
}, -Number.MAX_VALUE);

var finalPosition;

var ellipsoid = mapProjection.ellipsoid;
var tmp = camera.getRectangleCameraCoordinates(rectangle);
if (sceneMode === SceneMode.SCENE3D) {
finalPosition = ellipsoid.cartesianToCartographic(tmp);
} else {
finalPosition = mapProjection.unproject(tmp, tmp);
}

finalPosition.height += maxHeight;
return finalPosition;
});
}

function computeFlyToLocationForCartographic(cartographic, terrainProvider) {
var availability = defined(terrainProvider) ? terrainProvider.availability : undefined;

Expand Down Expand Up @@ -410,7 +374,7 @@ define([
// destination is now a Cartographic
destination = Rectangle.center(destination);
} else {
promise = computeFlyToLocationForRectangle(destination, terrainProvider, camera, mapProjection, scene.mode);
promise = computeFlyToLocationForRectangle(destination, scene);
}
} else { // destination is a Cartesian3
destination = ellipsoid.cartesianToCartographic(destination);
Expand Down
17 changes: 14 additions & 3 deletions Source/Widgets/Viewer/Viewer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
define([
'../../Core/BoundingSphere',
'../../Core/Cartesian3',
'../../Core/Cartographic',
'../../Core/Clock',
'../../Core/defaultValue',
'../../Core/defined',
Expand All @@ -22,6 +23,7 @@ define([
'../../DataSources/EntityView',
'../../DataSources/Property',
'../../Scene/Cesium3DTileset',
'../../Scene/computeFlyToLocationForRectangle',
'../../Scene/ImageryLayer',
'../../Scene/SceneMode',
'../../Scene/TimeDynamicPointCloud',
Expand Down Expand Up @@ -49,6 +51,7 @@ define([
], function(
BoundingSphere,
Cartesian3,
Cartographic,
Clock,
defaultValue,
defined,
Expand All @@ -70,6 +73,7 @@ define([
EntityView,
Property,
Cesium3DTileset,
computeFlyToLocationForRectangle,
ImageryLayer,
SceneMode,
TimeDynamicPointCloud,
Expand Down Expand Up @@ -1810,9 +1814,11 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
//If the zoom target is a rectangular imagery in an ImageLayer
if (zoomTarget instanceof ImageryLayer) {
zoomTarget.getViewableRectangle().then(function(rectangle) {
return computeFlyToLocationForRectangle(rectangle, that.scene);
}).then(function(position) {
//Only perform the zoom if it wasn't cancelled before the promise was resolved
if (that._zoomPromise === zoomPromise) {
that._zoomTarget = rectangle;
that._zoomTarget = position;
}
});
return;
Expand Down Expand Up @@ -1976,9 +1982,14 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
}

// If zoomTarget was an ImageryLayer
if (target instanceof Rectangle) {
var isCartographic = target instanceof Cartographic;
if (target instanceof Rectangle || isCartographic) {
var destination = target;
if (isCartographic) {
destination = scene.mapProjection.ellipsoid.cartographicToCartesian(destination);
}
options = {
destination : target,
destination : destination,
duration : zoomOptions.duration,
maximumHeight : zoomOptions.maximumHeight,
complete : function() {
Expand Down
130 changes: 130 additions & 0 deletions Specs/Scene/computeFlyToLocationForRectangleSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
defineSuite([
'Scene/computeFlyToLocationForRectangle',
'Core/EllipsoidTerrainProvider',
'Core/Rectangle',
'Scene/Globe',
'Scene/SceneMode',
'Specs/createScene',
'ThirdParty/when'
], function(
computeFlyToLocationForRectangle,
EllipsoidTerrainProvider,
Rectangle,
Globe,
SceneMode,
createScene,
when) {
'use strict';

var scene;

beforeEach(function() {
scene = createScene();
});

afterEach(function() {
scene.destroyForSpecs();
});

function sampleTest(sceneMode){
//Pretend we have terrain with availability.
var terrainProvider = new EllipsoidTerrainProvider();
terrainProvider.availability = {};

scene.globe = new Globe();
scene.terrainProvider = terrainProvider;
scene.mode = sceneMode;

var rectangle = new Rectangle(0.2, 0.4, 0.6, 0.8);
var cartographics = [
Rectangle.center(rectangle),
Rectangle.southeast(rectangle),
Rectangle.southwest(rectangle),
Rectangle.northeast(rectangle),
Rectangle.northwest(rectangle)
];

// Mock sampleTerrainMostDetailed with same positions but with heights.
var maxHeight = 1234;
var sampledResults = [
Rectangle.center(rectangle),
Rectangle.southeast(rectangle),
Rectangle.southwest(rectangle),
Rectangle.northeast(rectangle),
Rectangle.northwest(rectangle)
];
sampledResults[0].height = 145;
sampledResults[1].height = 1211;
sampledResults[2].height = -123;
sampledResults[3].height = maxHeight;

spyOn(computeFlyToLocationForRectangle, '_sampleTerrainMostDetailed').and.returnValue(when.resolve(sampledResults));

// Basically do the computation ourselves with our known values;
var expectedResult;
if (sceneMode === SceneMode.SCENE3D) {
expectedResult = scene.mapProjection.ellipsoid.cartesianToCartographic(scene.camera.getRectangleCameraCoordinates(rectangle));
} else {
expectedResult = scene.mapProjection.unproject(scene.camera.getRectangleCameraCoordinates(rectangle));
}
expectedResult.height += maxHeight;

return computeFlyToLocationForRectangle(rectangle, scene)
.then(function(result) {
expect(result).toEqual(expectedResult);
expect(computeFlyToLocationForRectangle._sampleTerrainMostDetailed).toHaveBeenCalledWith(terrainProvider, cartographics);
});
}

it('samples terrain and returns expected result in 3D', function() {
return sampleTest(SceneMode.SCENE3D);
});

it('samples terrain and returns expected result in CV', function() {
return sampleTest(SceneMode.COLUMBUS_VIEW);
});

it('returns original rectangle in 2D', function() {
var terrainProvider = new EllipsoidTerrainProvider();
terrainProvider.availability = {};

scene.globe = new Globe();
scene.terrainProvider = terrainProvider;
scene.mode = SceneMode.SCENE2D;

var rectangle = new Rectangle(0.2, 0.4, 0.6, 0.8);
spyOn(computeFlyToLocationForRectangle, '_sampleTerrainMostDetailed');

return computeFlyToLocationForRectangle(rectangle, scene)
.then(function(result) {
expect(result).toBe(rectangle);
expect(computeFlyToLocationForRectangle._sampleTerrainMostDetailed).not.toHaveBeenCalled();
});
});

it('returns original rectangle when terrain not available', function() {
scene.globe = new Globe();
scene.terrainProvider = new EllipsoidTerrainProvider();

var rectangle = new Rectangle(0.2, 0.4, 0.6, 0.8);
spyOn(computeFlyToLocationForRectangle, '_sampleTerrainMostDetailed');

return computeFlyToLocationForRectangle(rectangle, scene)
.then(function(result) {
expect(result).toBe(rectangle);
expect(computeFlyToLocationForRectangle._sampleTerrainMostDetailed).not.toHaveBeenCalled();
});
});

it('returns original rectangle when terrain undefined', function() {
scene.terrainProvider = undefined;
var rectangle = new Rectangle(0.2, 0.4, 0.6, 0.8);
spyOn(computeFlyToLocationForRectangle, '_sampleTerrainMostDetailed');

return computeFlyToLocationForRectangle(rectangle, scene)
.then(function(result) {
expect(result).toBe(rectangle);
expect(computeFlyToLocationForRectangle._sampleTerrainMostDetailed).not.toHaveBeenCalled();
});
});
});