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

Make Geocoder terrain-aware #6876

Merged
merged 4 commits into from
Aug 3, 2018
Merged
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
@@ -10,6 +10,7 @@ Change Log
* Added `ClippingPlaneCollection.planeAdded` and `ClippingPlaneCollection.planeRemoved` events. `planeAdded` is raised when a new plane is added to the collection and `planeRemoved` is raised when a plane is removed. [#6875](https://github.com/AnalyticalGraphicsInc/cesium/pull/6875)

##### Fixes :wrench:
* The Geocoder widget now takes terrain altitude into account when calculating its final destination.
* 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
18 changes: 8 additions & 10 deletions Source/Core/PeliasGeocoderService.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
define([
'./Cartesian3',
'./Check',
'./defined',
'./defineProperties',
'./GeocodeType',
'./Rectangle',
'./Resource'
], function (
Cartesian3,
Check,
defined,
defineProperties,
@@ -77,24 +79,20 @@ define([
return resource.fetchJson()
.then(function (results) {
return results.features.map(function (resultObject) {
var destination;
var bboxDegrees = resultObject.bbox;

// Pelias does not always provide bounding information
// so just expand the location slightly.
if (!defined(bboxDegrees)) {
if (defined(bboxDegrees)) {
destination = Rectangle.fromDegrees(bboxDegrees[0], bboxDegrees[1], bboxDegrees[2], bboxDegrees[3]);
} else {
var lon = resultObject.geometry.coordinates[0];
var lat = resultObject.geometry.coordinates[1];
bboxDegrees = [
lon - 0.001,
lat - 0.001,
lon + 0.001,
lat + 0.001
];
destination = Cartesian3.fromDegrees(lon, lat);
}

return {
displayName: resultObject.properties.label,
destination: Rectangle.fromDegrees(bboxDegrees[0], bboxDegrees[1], bboxDegrees[2], bboxDegrees[3])
destination: destination
};
});
});
91 changes: 83 additions & 8 deletions Source/Widgets/Geocoder/GeocoderViewModel.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
define([
'../../Core/IonGeocoderService',
'../../Core/Cartesian3',
'../../Core/CartographicGeocoderService',
'../../Core/defaultValue',
'../../Core/defined',
'../../Core/defineProperties',
'../../Core/DeveloperError',
'../../Core/Event',
'../../Core/GeocodeType',
'../../Core/Math',
'../../Core/Matrix4',
'../../Core/Rectangle',
'../../Core/sampleTerrainMostDetailed',
'../../Scene/SceneMode',
'../../ThirdParty/knockout',
'../../ThirdParty/when',
'../createCommand',
'../getElement'
], function(
IonGeocoderService,
Cartesian3,
CartographicGeocoderService,
defaultValue,
defined,
defineProperties,
DeveloperError,
Event,
GeocodeType,
CesiumMath,
Matrix4,
Rectangle,
sampleTerrainMostDetailed,
SceneMode,
knockout,
when,
createCommand,
@@ -331,14 +341,79 @@ define([
}

function updateCamera(viewModel, destination) {
viewModel._scene.camera.flyTo({
destination : destination,
complete: function() {
viewModel._complete.raiseEvent();
},
duration : viewModel._flightDuration,
endTransform : Matrix4.IDENTITY
});
var scene = viewModel._scene;
var globe = scene.globe;
var ellipsoid;
if (defined(globe)) {
ellipsoid = globe.ellipsoid;
} else {
ellipsoid = scene.mapProjection.ellipsoid;
}

var camera = scene.camera;
var terrainProvider = scene.terrainProvider;
var availability = defined(terrainProvider) ? terrainProvider.availability : undefined;

// Always expand single points so we don't zoom
// directly into the ground, even when not using terrain.
if (destination instanceof Cartesian3) {
var carto = ellipsoid.cartesianToCartographic(destination);
var offset = CesiumMath.toRadians(0.001);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced this is the right thing to do. The rectangle covers a larger area closer to the equator than the poles so the camera destination will have a greater height at the equator. From experimenting, the camera height at the equator is around 500m and at the poles it's 350m.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree. I was just moving code over from PeliasGeocoder and the thought just didn't occur to me. Instead I'm going to apply a fixed offset of 500m to any single point rather than expanding it into a rectangle at all. I'll still query the terrain at that single point, so it will still adjust properly and this will make for consistent zooming with and without terrain.

destination = new Rectangle(
carto.longitude - offset,
carto.latitude - offset,
carto.longitude + offset,
carto.latitude + offset);
}

var newDestination = destination;
if (defined(availability)) {
var cartographics;

cartographics = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mramato These don't need to be on two separate lines but oh well. And shouldn't you use result parameters throughout this? I know it won't really impact performance but it's still a good habit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use result parameters as a necessary evil for performance in hot areas of the code. Not using them makes the code cleaner and is perfectly OK if performance is not going to be an issue.

Rectangle.center(destination),
Rectangle.southeast(destination),
Rectangle.southwest(destination),
Rectangle.northeast(destination),
Rectangle.northwest(destination)
];

newDestination = sampleTerrainMostDetailed(terrainProvider, cartographics)
.then(function(positionsOnTerrain) {
var maxHeight = -Number.MAX_VALUE;
positionsOnTerrain.forEach(function(p) {
maxHeight = Math.max(p.height, maxHeight);
});

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

finalPosition.height += maxHeight * 2;
return ellipsoid.cartographicToCartesian(finalPosition);
});
}

var finalDestination = destination;
when(newDestination)
.then(function(result) {
finalDestination = result;
})
.always(function(){
// Whether terrain querying succeeded or not, fly to the destination.
camera.flyTo({
destination: finalDestination,
complete: function() {
viewModel._complete.raiseEvent();
},
duration: viewModel._flightDuration,
endTransform: Matrix4.IDENTITY
});
});
}

function chainPromise(promise, geocoderService, query, geocodeType) {
6 changes: 3 additions & 3 deletions Specs/Core/PeliasGeocoderServiceSpec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
defineSuite([
'Core/PeliasGeocoderService',
'Core/GeocodeType',
'Core/Rectangle',
'Core/Cartesian3',
'Core/Resource',
'ThirdParty/when'
], function(
PeliasGeocoderService,
GeocodeType,
Rectangle,
Cartesian3,
Resource,
when) {
'use strict';
@@ -40,7 +40,7 @@ defineSuite([
.then(function(results) {
expect(results.length).toEqual(1);
expect(results[0].displayName).toEqual(data.features[0].properties.label);
expect(results[0].destination).toBeInstanceOf(Rectangle);
expect(results[0].destination).toBeInstanceOf(Cartesian3);
});
});