From fa65c886b94815ec3709655997e6cf88ef1aa6ee Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Thu, 2 Aug 2018 13:22:57 -0400 Subject: [PATCH 1/3] Make Geocoder terrain-aware Previously, if a GeocoderService returned a single point, the geocoder would slam into the ground as close as possible. If the service returned a rectangle, it would only work with when the rectangle height did not include large elevation changes (and slam into the ground otherwise). This change queries the 4 corners and center of the rectangle (if the terrain supports it) and adjusts the geocoder final position height to be above terrain. --- Source/Core/PeliasGeocoderService.js | 18 ++--- Source/Widgets/Geocoder/GeocoderViewModel.js | 83 ++++++++++++++++++-- Specs/Core/PeliasGeocoderServiceSpec.js | 6 +- 3 files changed, 86 insertions(+), 21 deletions(-) diff --git a/Source/Core/PeliasGeocoderService.js b/Source/Core/PeliasGeocoderService.js index 8d8a4832f4c5..42857d93feda 100644 --- a/Source/Core/PeliasGeocoderService.js +++ b/Source/Core/PeliasGeocoderService.js @@ -1,4 +1,5 @@ define([ + './Cartesian3', './Check', './defined', './defineProperties', @@ -6,6 +7,7 @@ define([ './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 }; }); }); diff --git a/Source/Widgets/Geocoder/GeocoderViewModel.js b/Source/Widgets/Geocoder/GeocoderViewModel.js index e65c1ab09370..0dda192adb23 100644 --- a/Source/Widgets/Geocoder/GeocoderViewModel.js +++ b/Source/Widgets/Geocoder/GeocoderViewModel.js @@ -1,5 +1,6 @@ define([ '../../Core/IonGeocoderService', + '../../Core/Cartesian3', '../../Core/CartographicGeocoderService', '../../Core/defaultValue', '../../Core/defined', @@ -7,13 +8,17 @@ define([ '../../Core/DeveloperError', '../../Core/Event', '../../Core/GeocodeType', + '../../Core/Math', '../../Core/Matrix4', + '../../Core/Rectangle', + '../../Core/sampleTerrainMostDetailed', '../../ThirdParty/knockout', '../../ThirdParty/when', '../createCommand', '../getElement' ], function( IonGeocoderService, + Cartesian3, CartographicGeocoderService, defaultValue, defined, @@ -21,7 +26,10 @@ define([ DeveloperError, Event, GeocodeType, + CesiumMath, Matrix4, + Rectangle, + sampleTerrainMostDetailed, knockout, when, createCommand, @@ -331,14 +339,73 @@ 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); + destination = new Rectangle( + carto.longitude - offset, + carto.latitude - offset, + carto.longitude + offset, + carto.latitude + offset); + } + + var newDestination = destination; + if (defined(availability)) { + var cartographics; + + cartographics = [ + 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 finalPosition = ellipsoid.cartesianToCartographic(camera.getRectangleCameraCoordinates(destination)); + 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) { diff --git a/Specs/Core/PeliasGeocoderServiceSpec.js b/Specs/Core/PeliasGeocoderServiceSpec.js index e3b8117e8e70..67b7594f7156 100644 --- a/Specs/Core/PeliasGeocoderServiceSpec.js +++ b/Specs/Core/PeliasGeocoderServiceSpec.js @@ -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); }); }); From 7182d02cb01e39c74be5cf4ef57c2ea2367a7e52 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Thu, 2 Aug 2018 13:31:51 -0400 Subject: [PATCH 2/3] Update CHANGES --- CHANGES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 4fe6de43324d..b7a14cc7a7d5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,16 @@ Change Log ========== +### 1.49 - 2018-09-03 + +##### Additions :tada: + +##### Deprecated :hourglass_flowing_sand: + +#### Fixes :wrench: + +The Geocoder widget now takes terrain altitude into account when calculating its final destination. + ### 1.48 - 2018-08-01 ##### Additions :tada: From e8da0a808fc30a976fe5c2d3c39d15218f352be5 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Thu, 2 Aug 2018 13:46:08 -0400 Subject: [PATCH 3/3] Fix Geocoder flights for 2D and Columbus View --- Source/Widgets/Geocoder/GeocoderViewModel.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Source/Widgets/Geocoder/GeocoderViewModel.js b/Source/Widgets/Geocoder/GeocoderViewModel.js index 0dda192adb23..2d4c64b282de 100644 --- a/Source/Widgets/Geocoder/GeocoderViewModel.js +++ b/Source/Widgets/Geocoder/GeocoderViewModel.js @@ -12,6 +12,7 @@ define([ '../../Core/Matrix4', '../../Core/Rectangle', '../../Core/sampleTerrainMostDetailed', + '../../Scene/SceneMode', '../../ThirdParty/knockout', '../../ThirdParty/when', '../createCommand', @@ -30,6 +31,7 @@ define([ Matrix4, Rectangle, sampleTerrainMostDetailed, + SceneMode, knockout, when, createCommand, @@ -383,9 +385,15 @@ define([ maxHeight = Math.max(p.height, maxHeight); }); - var finalPosition = ellipsoid.cartesianToCartographic(camera.getRectangleCameraCoordinates(destination)); - finalPosition.height += maxHeight * 2; + 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); }); }