diff --git a/Apps/Sandcastle/gallery/3D Tiles NGA GPM Visualization.html b/Apps/Sandcastle/gallery/3D Tiles NGA GPM Visualization.html new file mode 100644 index 000000000000..40f008469ef3 --- /dev/null +++ b/Apps/Sandcastle/gallery/3D Tiles NGA GPM Visualization.html @@ -0,0 +1,1351 @@ + + + + + + + + + Cesium Demo + + + + + + + +
+
+

Loading...

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Cesium GPM Visualization

+
Data set + + + +
Anchor point ellipsoid + + + +
Shader mode + +
+
+ Uncertainty information is stored as GPM metadata in the tiles.
+
+ The anchor points provide information about the low-frequency + error.
+ This error is visualized as ellipsoids.
+
+ The high-frequency error is represented as + Per-Point Error textures.
+ This error can be visualized with custom shaders. The 'uncertainty'
+ shaders visualize the horizontal or vertical uncertainty with a color
+ scale. The 'threshold' shaders highlight areas where the selected
+ error threshold is exceeded. Picking a point on the tileset will show
+ a label with the actual error values. +
+
+ + + diff --git a/Apps/Sandcastle/gallery/3D Tiles NGA GPM Visualization.jpg b/Apps/Sandcastle/gallery/3D Tiles NGA GPM Visualization.jpg new file mode 100644 index 000000000000..5b2daea52ce0 Binary files /dev/null and b/Apps/Sandcastle/gallery/3D Tiles NGA GPM Visualization.jpg differ diff --git a/Apps/Sandcastle/gallery/3D Tiles Vertical Exaggeration.html b/Apps/Sandcastle/gallery/3D Tiles Vertical Exaggeration.html index 4e636b2f001c..41f67a60a1b6 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Vertical Exaggeration.html +++ b/Apps/Sandcastle/gallery/3D Tiles Vertical Exaggeration.html @@ -78,6 +78,7 @@

Loading...

animation: false, sceneModePicker: false, baseLayerPicker: false, + geocoder: Cesium.IonGeocodeProviderType.GOOGLE, // The globe does not need to be displayed, // since the Photorealistic 3D Tiles include terrain globe: false, @@ -104,7 +105,10 @@

Loading...

// Add Photorealistic 3D Tiles try { - const tileset = await Cesium.createGooglePhotorealistic3DTileset(); + const tileset = await Cesium.createGooglePhotorealistic3DTileset({ + // Only the Google Geocoder can be used with Google Photorealistic 3D Tiles. Set the `geocode` property of the viewer constructor options to IonGeocodeProviderType.GOOGLE. + onlyUsingWithGoogleGeocoder: true, + }); scene.primitives.add(tileset); } catch (error) { console.log(`Error loading Photorealistic 3D Tiles tileset. diff --git a/Apps/Sandcastle/gallery/AEC Clipping.html b/Apps/Sandcastle/gallery/AEC Clipping.html index 0fa60bf14964..d373bd2a589a 100644 --- a/Apps/Sandcastle/gallery/AEC Clipping.html +++ b/Apps/Sandcastle/gallery/AEC Clipping.html @@ -32,6 +32,7 @@ animation: false, sceneModePicker: false, baseLayerPicker: false, + geocoder: Cesium.IonGeocodeProviderType.GOOGLE, // The globe does not need to be displayed, // since the Photorealistic 3D Tiles include terrain globe: false, @@ -48,7 +49,10 @@ // Add Photorealistic 3D Tiles let googleTileset; try { - googleTileset = await Cesium.createGooglePhotorealistic3DTileset(); + googleTileset = await Cesium.createGooglePhotorealistic3DTileset({ + // Only the Google Geocoder can be used with Google Photorealistic 3D Tiles. Set the `geocode` property of the viewer constructor options to IonGeocodeProviderType.GOOGLE. + onlyUsingWithGoogleGeocoder: true, + }); viewer.scene.primitives.add(googleTileset); } catch (error) { console.log(`Error loading Photorealistic 3D Tiles tileset. diff --git a/Apps/Sandcastle/gallery/Bing Maps Labels Only.html b/Apps/Sandcastle/gallery/Bing Maps Labels Only.html index f4c12739f24f..7f92188196ba 100644 --- a/Apps/Sandcastle/gallery/Bing Maps Labels Only.html +++ b/Apps/Sandcastle/gallery/Bing Maps Labels Only.html @@ -67,6 +67,7 @@ baseLayer: false, baseLayerPicker: false, infoBox: false, + geocoder: Cesium.IonGeocodeProviderType.BING, }); const layers = viewer.imageryLayers; diff --git a/Apps/Sandcastle/gallery/Clamp Entities to Ground.html b/Apps/Sandcastle/gallery/Clamp Entities to Ground.html index 0a048fbe0d93..e38e8709c464 100644 --- a/Apps/Sandcastle/gallery/Clamp Entities to Ground.html +++ b/Apps/Sandcastle/gallery/Clamp Entities to Ground.html @@ -41,6 +41,7 @@ timeline: false, animation: false, baseLayerPicker: false, + geocoder: Cesium.IonGeocodeProviderType.GOOGLE, }); const scene = viewer.scene; scene.globe.depthTestAgainstTerrain = true; @@ -56,7 +57,10 @@ let worldTileset; try { - worldTileset = await Cesium.createGooglePhotorealistic3DTileset(); + worldTileset = await Cesium.createGooglePhotorealistic3DTileset({ + // Only the Google Geocoder can be used with Google Photorealistic 3D Tiles. Set the `geocode` property of the viewer constructor options to IonGeocodeProviderType.GOOGLE. + onlyUsingWithGoogleGeocoder: true, + }); viewer.scene.primitives.add(worldTileset); } catch (error) { console.log(`Error loading Photorealistic 3D Tiles tileset. diff --git a/Apps/Sandcastle/gallery/Clipping Regions.html b/Apps/Sandcastle/gallery/Clipping Regions.html index 97dba26b3ce9..b7116fc29f17 100644 --- a/Apps/Sandcastle/gallery/Clipping Regions.html +++ b/Apps/Sandcastle/gallery/Clipping Regions.html @@ -43,6 +43,7 @@ animation: false, sceneModePicker: false, baseLayerPicker: false, + geocoder: Cesium.IonGeocoderProviderType.GOOGLE, }); const scene = viewer.scene; @@ -60,7 +61,10 @@ let worldTileset; try { - worldTileset = await Cesium.createGooglePhotorealistic3DTileset(); + worldTileset = await Cesium.createGooglePhotorealistic3DTileset({ + // Only the Google Geocoder can be used with Google Photorealistic 3D Tiles. Set the `geocode` property of the viewer constructor options to IonGeocodeProviderType.GOOGLE. + onlyUsingWithGoogleGeocoder: true, + }); scene.primitives.add(worldTileset); } catch (error) { console.log(`Error loading Photorealistic 3D Tiles tileset. diff --git a/Apps/Sandcastle/gallery/Entity tracking.html b/Apps/Sandcastle/gallery/Entity tracking.html index f5f21aa22a47..6e894c642e9d 100644 --- a/Apps/Sandcastle/gallery/Entity tracking.html +++ b/Apps/Sandcastle/gallery/Entity tracking.html @@ -94,6 +94,13 @@ drone.trackingReferenceFrame = Cesium.TrackingReferenceFrame.VELOCITY; }, }, + { + text: "Tracking reference frame: East-North-Up", + onselect: function () { + satellite.trackingReferenceFrame = Cesium.TrackingReferenceFrame.ENU; + drone.trackingReferenceFrame = Cesium.TrackingReferenceFrame.ENU; + }, + }, ]); //Sandcastle_End }; diff --git a/Apps/Sandcastle/gallery/Globe Interior.html b/Apps/Sandcastle/gallery/Globe Interior.html index 0deb07a51e34..9a4ff467d784 100644 --- a/Apps/Sandcastle/gallery/Globe Interior.html +++ b/Apps/Sandcastle/gallery/Globe Interior.html @@ -34,6 +34,7 @@ //Sandcastle_Begin const viewer = new Cesium.Viewer("cesiumContainer", { orderIndependentTranslucency: false, + geocoder: Cesium.IonGeocodeProviderType.BING, }); const scene = viewer.scene; diff --git a/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles with Building Insert.html b/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles with Building Insert.html index 6437a2fb7c19..a75c993a8afa 100644 --- a/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles with Building Insert.html +++ b/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles with Building Insert.html @@ -32,6 +32,7 @@ animation: false, sceneModePicker: false, baseLayerPicker: false, + geocoder: Cesium.IonGeocodeProviderType.GOOGLE, // The globe does not need to be displayed, // since the Photorealistic 3D Tiles include terrain globe: false, @@ -42,7 +43,10 @@ // Add Photorealistic 3D Tiles try { - const googleTileset = await Cesium.createGooglePhotorealistic3DTileset(); + const googleTileset = await Cesium.createGooglePhotorealistic3DTileset({ + // Only the Google Geocoder can be used with Google Photorealistic 3D Tiles. Set the `geocode` property of the viewer constructor options to IonGeocodeProviderType.GOOGLE. + onlyUsingWithGoogleGeocoder: true, + }); viewer.scene.primitives.add(googleTileset); } catch (error) { console.log(`Error loading Photorealistic 3D Tiles tileset. diff --git a/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles.html b/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles.html index d4d68c1c97d6..2f9da653dc9c 100644 --- a/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles.html +++ b/Apps/Sandcastle/gallery/Google Photorealistic 3D Tiles.html @@ -32,6 +32,7 @@ animation: false, sceneModePicker: false, baseLayerPicker: false, + geocoder: Cesium.IonGeocodeProviderType.GOOGLE, // The globe does not need to be displayed, // since the Photorealistic 3D Tiles include terrain globe: false, @@ -42,7 +43,10 @@ // Add Photorealistic 3D Tiles try { - const tileset = await Cesium.createGooglePhotorealistic3DTileset(); + const tileset = await Cesium.createGooglePhotorealistic3DTileset({ + // Only the Google Geocoder can be used with Google Photorealistic 3D Tiles. Set the `geocode` property of the viewer constructor options to IonGeocodeProviderType.GOOGLE. + onlyUsingWithGoogleGeocoder: true, + }); viewer.scene.primitives.add(tileset); } catch (error) { console.log(`Error loading Photorealistic 3D Tiles tileset. diff --git a/Apps/Sandcastle/gallery/Imagery Color To Alpha.html b/Apps/Sandcastle/gallery/Imagery Color To Alpha.html index 8fa3ed032883..c28533a3c4a9 100644 --- a/Apps/Sandcastle/gallery/Imagery Color To Alpha.html +++ b/Apps/Sandcastle/gallery/Imagery Color To Alpha.html @@ -51,7 +51,9 @@ window.startup = async function (Cesium) { "use strict"; //Sandcastle_Begin - const viewer = new Cesium.Viewer("cesiumContainer"); + const viewer = new Cesium.Viewer("cesiumContainer", { + geocoder: Cesium.IonGeocodeProviderType.BING, + }); const layers = viewer.scene.imageryLayers; diff --git a/Apps/Sandcastle/gallery/Imagery Layers Manipulation.html b/Apps/Sandcastle/gallery/Imagery Layers Manipulation.html index 4e309a339dd7..9b1f026cdbab 100644 --- a/Apps/Sandcastle/gallery/Imagery Layers Manipulation.html +++ b/Apps/Sandcastle/gallery/Imagery Layers Manipulation.html @@ -106,6 +106,7 @@ //Sandcastle_Begin const viewer = new Cesium.Viewer("cesiumContainer", { baseLayerPicker: false, + geocoder: false, }); const imageryLayers = viewer.imageryLayers; diff --git a/Apps/Sandcastle/gallery/Imagery Layers Split.html b/Apps/Sandcastle/gallery/Imagery Layers Split.html index 37927116fc29..15375661bb5f 100644 --- a/Apps/Sandcastle/gallery/Imagery Layers Split.html +++ b/Apps/Sandcastle/gallery/Imagery Layers Split.html @@ -58,6 +58,7 @@ ), baseLayerPicker: false, infoBox: false, + geocoder: false, }); const layers = viewer.imageryLayers; diff --git a/Apps/Sandcastle/gallery/Interpolation.html b/Apps/Sandcastle/gallery/Interpolation.html index 567ec12627ea..f21a5dd259c3 100644 --- a/Apps/Sandcastle/gallery/Interpolation.html +++ b/Apps/Sandcastle/gallery/Interpolation.html @@ -158,7 +158,7 @@ { text: "Tracking reference frame: East-North-Up", onselect: function () { - entity.trackingReferenceFrame = Cesium.TrackingReferenceFrame.EAST_NORTH_UP; + entity.trackingReferenceFrame = Cesium.TrackingReferenceFrame.ENU; }, }, { diff --git a/Apps/Sandcastle/gallery/development/3D Tiles Picking.html b/Apps/Sandcastle/gallery/development/3D Tiles Picking.html index 10c3b2367a66..ce77a1cc5f35 100644 --- a/Apps/Sandcastle/gallery/development/3D Tiles Picking.html +++ b/Apps/Sandcastle/gallery/development/3D Tiles Picking.html @@ -34,6 +34,7 @@ animation: false, baseLayerPicker: false, globe: false, + geocoder: false, }); const scene = viewer.scene; @@ -44,7 +45,10 @@ onselect: async () => { scene.primitives.remove(tileset); try { - tileset = await Cesium.createGooglePhotorealistic3DTileset(); + tileset = await Cesium.createGooglePhotorealistic3DTileset({ + // Only the Google Geocoder can be used with Google Photorealistic 3D Tiles. Set the `geocode` property of the viewer constructor options to IonGeocodeProviderType.GOOGLE. + onlyUsingWithGoogleGeocoder: true, + }); scene.primitives.add(tileset); } catch (error) { console.log(error); diff --git a/CHANGES.md b/CHANGES.md index 3509bc126b3a..98047d22162c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,7 +7,12 @@ ##### Additions :tada: - Added `getSample` to `SampledProperty` to get the time of samples. [#12253](https://github.com/CesiumGS/cesium/pull/12253) -- Added `Entity.trackingReferenceFrame` property to allow tracking entities in their own inertial reference frame. [#12194](https://github.com/CesiumGS/cesium/pull/12194) +- Added `Entity.trackingReferenceFrame` property to allow tracking entities in various reference frames. [#12194](https://github.com/CesiumGS/cesium/pull/12194), [#12314](https://github.com/CesiumGS/cesium/pull/12314) + - `TrackingReferenceFrame.AUTODETECT` (default): uses either VVLH or ENU dependeding on entity's dynamic. Use `TrackingReferenceFrame.ENU` if your camera orientation flips abruptly from time to time. + - `TrackingReferenceFrame.ENU`: uses the entity's local East-North-Up reference frame. + - `TrackingReferenceFrame.INERTIAL`: uses the entity's inertial reference frame. + - `TrackingReferenceFrame.VELOCITY`: uses entity's `VelocityOrientationProperty` as orientation. +- Added `GoogleGeocoderService` for standalone usage of Google geocoder. [#12299](https://github.com/CesiumGS/cesium/pull/12299) ##### Fixes :wrench: @@ -15,8 +20,16 @@ - Updated WMS example URL in UrlTemplateImageryProvider documentation to use an active service. [#12323](https://github.com/CesiumGS/cesium/pull/12323) - Fix point cloud filtering performance on certain hardware [#12317](https://github.com/CesiumGS/cesium/pull/12317) +##### Deprecated :hourglass_flowing_sand: + +- `createGooglePhotorealistic3DTileset(key)` has been deprecated. Use `createGooglePhotorealistic3DTileset({key})` instead. It will be removed in 1.126. + #### @cesium/widgets +##### Additions :tada: + +- Added the ability to choose between Bing and Google geocoders. Updated `Viewer` constructor to also accept `IonGeocoderProvider` [#12299](https://github.com/CesiumGS/cesium/pull/12299) + ##### Fixes :wrench: - Added a `DeveloperError` when `globe` is set to `false` and a `baseLayer` is provided in `Viewer` options. This prevents errors caused by attempting to use a `baseLayer` without a globe. [#12274](https://github.com/CesiumGS/cesium/pull/12274) diff --git a/packages/engine/Source/Core/BingMapsGeocoderService.js b/packages/engine/Source/Core/BingMapsGeocoderService.js index 647658fcd2d3..bd299d874026 100644 --- a/packages/engine/Source/Core/BingMapsGeocoderService.js +++ b/packages/engine/Source/Core/BingMapsGeocoderService.js @@ -10,6 +10,8 @@ const url = "https://dev.virtualearth.net/REST/v1/Locations"; /** * Provides geocoding through Bing Maps. + * + * @see {@link https://www.microsoft.com/en-us/maps/bing-maps/product|Microsoft Bing Maps Platform APIs Terms Of Use} * @alias BingMapsGeocoderService * @constructor * diff --git a/packages/engine/Source/Core/GoogleGeocoderService.js b/packages/engine/Source/Core/GoogleGeocoderService.js new file mode 100644 index 000000000000..67326e496e1c --- /dev/null +++ b/packages/engine/Source/Core/GoogleGeocoderService.js @@ -0,0 +1,110 @@ +import Check from "./Check.js"; +import Credit from "./Credit.js"; +import defaultValue from "./defaultValue.js"; +import Rectangle from "./Rectangle.js"; +import Resource from "./Resource.js"; +import defined from "./defined.js"; +import DeveloperError from "./DeveloperError.js"; +import RuntimeError from "./RuntimeError.js"; + +const API_URL = "https://maps.googleapis.com/maps/api/geocode/json"; +const CREDIT_HTML = `Google`; + +/** + * Provides geocoding through Google. + * + * @see {@link https://developers.google.com/maps/documentation/geocoding/policies|Google Geocoding Policies} + * @alias GoogleGeocoderService + * @constructor + * + * @param {object} options Object with the following properties: + * @param {string} options.key An API key to use with the Google geocoding service + */ +function GoogleGeocoderService(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + const key = options.key; + //>>includeStart('debug', pragmas.debug); + if (!defined(key)) { + throw new DeveloperError("options.key is required."); + } + //>>includeEnd('debug'); + + this._resource = new Resource({ + url: API_URL, + queryParameters: { key }, + }); + + this._credit = new Credit(CREDIT_HTML, true); +} + +Object.defineProperties(GoogleGeocoderService.prototype, { + /** + * Gets the credit to display after a geocode is performed. Typically this is used to credit + * the geocoder service. + * @memberof GoogleGeocoderService.prototype + * @type {Credit|undefined} + * @readonly + */ + credit: { + get: function () { + return this._credit; + }, + }, +}); + +/** + * Get a list of possible locations that match a search string. + * + * @function + * + * @param {string} query The query to be sent to the geocoder service + * @returns {Promise} + * @throws {RuntimeError} If the services returns a status other than OK or ZERO_RESULTS + */ +GoogleGeocoderService.prototype.geocode = async function (query) { + // See API documentation at https://developers.google.com/maps/documentation/geocoding/requests-geocoding + + //>>includeStart('debug', pragmas.debug); + Check.typeOf.string("query", query); + //>>includeEnd('debug'); + + const resource = this._resource.getDerivedResource({ + queryParameters: { + address: query, + }, + }); + + const response = await resource.fetchJson(); + + if (response.status === "ZERO_RESULTS") { + return []; + } + + if (response.status !== "OK") { + throw new RuntimeError( + `GoogleGeocoderService got a bad response ${response.status}: ${response.error_message}`, + ); + } + + const results = response.results.map((result) => { + const southWest = result.geometry.viewport.southwest; + const northEast = result.geometry.viewport.northeast; + return { + displayName: result.formatted_address, + destination: Rectangle.fromDegrees( + southWest.lng, + southWest.lat, + northEast.lng, + northEast.lat, + ), + attribution: { + html: CREDIT_HTML, + collapsible: false, + }, + }; + }); + + return results; +}; + +export default GoogleGeocoderService; diff --git a/packages/engine/Source/Core/IonGeocodeProviderType.js b/packages/engine/Source/Core/IonGeocodeProviderType.js new file mode 100644 index 000000000000..2dc9bf35d775 --- /dev/null +++ b/packages/engine/Source/Core/IonGeocodeProviderType.js @@ -0,0 +1,33 @@ +/** + * Underlying geocoding services that can be used via Cesium ion. + * + * @enum {string} + */ +const IonGeocodeProviderType = { + /** + * Google geocoder, for use with Google data. + * + * @type {string} + * @constant + */ + GOOGLE: "GOOGLE", + + /** + * Bing geocoder, for use with Bing data. + * + * @type {string} + * @constant + */ + BING: "BING", + + /** + * Use the default geocoder as set on the server. Used when neither Bing or + * Google data is used. + * + * @type {string} + * @constant + */ + DEFAULT: "DEFAULT", +}; + +export default Object.freeze(IonGeocodeProviderType); diff --git a/packages/engine/Source/Core/IonGeocoderService.js b/packages/engine/Source/Core/IonGeocoderService.js index 81da96b7dbba..f473d80005b3 100644 --- a/packages/engine/Source/Core/IonGeocoderService.js +++ b/packages/engine/Source/Core/IonGeocoderService.js @@ -2,10 +2,45 @@ import Check from "./Check.js"; import Credit from "./Credit.js"; import defaultValue from "./defaultValue.js"; import defined from "./defined.js"; +import DeveloperError from "./DeveloperError.js"; import Ion from "./Ion.js"; +import IonGeocodeProviderType from "./IonGeocodeProviderType.js"; import PeliasGeocoderService from "./PeliasGeocoderService.js"; import Resource from "./Resource.js"; +/** + * @param {*} geocodeProviderType + * @throws {DeveloperError} + * @private + */ +function validateIonGeocodeProviderType(geocodeProviderType) { + if ( + !Object.values(IonGeocodeProviderType).some( + (value) => value === geocodeProviderType, + ) + ) { + throw new DeveloperError( + `Invalid geocodeProviderType: "${geocodeProviderType}"`, + ); + } +} + +const providerToParameterMap = Object.freeze({ + [IonGeocodeProviderType.GOOGLE]: "google", + [IonGeocodeProviderType.BING]: "bing", + [IonGeocodeProviderType.DEFAULT]: undefined, +}); + +function providerToQueryParameter(provider) { + return providerToParameterMap[provider]; +} + +function queryParameterToProvider(parameter) { + return Object.entries(providerToParameterMap).find( + (entry) => entry[1] === parameter, + )[0]; +} + /** * Provides geocoding through Cesium ion. * @alias IonGeocoderService @@ -15,6 +50,7 @@ import Resource from "./Resource.js"; * @param {Scene} options.scene The scene * @param {string} [options.accessToken=Ion.defaultAccessToken] The access token to use. * @param {string|Resource} [options.server=Ion.defaultServer] The resource to the Cesium ion API server. + * @param {IonGeocodeProviderType} [options.geocodeProviderType=IonGeocodeProviderType.DEFAULT] The geocoder the Cesium ion API server should use to fulfill this request. * * @see Ion */ @@ -25,6 +61,14 @@ function IonGeocoderService(options) { Check.typeOf.object("options.scene", options.scene); //>>includeEnd('debug'); + const geocodeProviderType = defaultValue( + options.geocodeProviderType, + IonGeocodeProviderType.DEFAULT, + ); + //>>includeStart('debug', pragmas.debug); + validateIonGeocodeProviderType(geocodeProviderType); + //>>includeEnd('debug'); + const accessToken = defaultValue(options.accessToken, Ion.defaultAccessToken); const server = Resource.createIfNeeded( defaultValue(options.server, Ion.defaultServer), @@ -49,6 +93,9 @@ function IonGeocoderService(options) { this._accessToken = accessToken; this._server = server; this._pelias = new PeliasGeocoderService(searchEndpoint); + // geocoderProviderType isn't stored here directly but instead relies on the + // query parameters of this._pelias.url. Use the setter logic to update value. + this.geocodeProviderType = geocodeProviderType; } Object.defineProperties(IonGeocoderService.prototype, { @@ -64,6 +111,31 @@ Object.defineProperties(IonGeocoderService.prototype, { return undefined; }, }, + /** + * The geocoding service that Cesium ion API server should use to fulfill geocding requests. + * @memberof IonGeocoderService.prototype + * @type {IonGeocodeProviderType} + * @default IonGeocodeProviderType.DEFAULT + */ + geocodeProviderType: { + get: function () { + return queryParameterToProvider( + this._pelias.url.queryParameters["geocoder"], + ); + }, + set: function (geocodeProviderType) { + validateIonGeocodeProviderType(geocodeProviderType); + const query = { + ...this._pelias.url.queryParameters, + geocoder: providerToQueryParameter(geocodeProviderType), + }; + // Delete the geocoder parameter to prevent sending &geocoder=undefined in the query + if (!defined(query.geocoder)) { + delete query.geocoder; + } + this._pelias.url.setQueryParameters(query); + }, + }, }); /** diff --git a/packages/engine/Source/Core/TrackingReferenceFrame.js b/packages/engine/Source/Core/TrackingReferenceFrame.js index 4ad418e96b7a..d16be08b853b 100644 --- a/packages/engine/Source/Core/TrackingReferenceFrame.js +++ b/packages/engine/Source/Core/TrackingReferenceFrame.js @@ -16,25 +16,30 @@ const TrackingReferenceFrame = { */ AUTODETECT: 0, + /** + * The entity's local East-North-Up reference frame. + * + * @type {number} + * @constant + */ + ENU: 1, + /** * The entity's inertial reference frame. If entity has no defined orientation - * property, a {@link VelocityOrientationProperty} is used instead, thus - * falling back to TrackingReferenceFrame.VELOCITY. - * When selected, the auto-detect algorithm is overridden. + * property, it falls back to auto-detect algorithm. * * @type {number} * @constant */ - INERTIAL: 1, + INERTIAL: 2, /** * The entity's inertial reference frame with orientation fixed to its * {@link VelocityOrientationProperty}, ignoring its own orientation. - * When selected, the auto-detect algorithm is overridden. * * @type {number} * @constant */ - VELOCITY: 2, + VELOCITY: 3, }; export default Object.freeze(TrackingReferenceFrame); diff --git a/packages/engine/Source/DataSources/EntityView.js b/packages/engine/Source/DataSources/EntityView.js index bc759b53dbd6..9e7ae33234b5 100644 --- a/packages/engine/Source/DataSources/EntityView.js +++ b/packages/engine/Source/DataSources/EntityView.js @@ -269,7 +269,12 @@ function updateTransform( rotationScratch, ); Matrix4.fromRotationTranslation(rotation, cartesian, transform); - } else if (hasBasis) { + } else if ( + trackingReferenceFrame === TrackingReferenceFrame.ENU || + !hasBasis + ) { + Transforms.eastNorthUpToFixedFrame(cartesian, ellipsoid, transform); + } else { transform[0] = xBasis.x; transform[1] = xBasis.y; transform[2] = xBasis.z; @@ -286,9 +291,6 @@ function updateTransform( transform[13] = cartesian.y; transform[14] = cartesian.z; transform[15] = 0.0; - } else { - // Stationary or slow-moving, low-altitude objects use East-North-Up. - Transforms.eastNorthUpToFixedFrame(cartesian, ellipsoid, transform); } camera._setTransform(transform); diff --git a/packages/engine/Source/Scene/createGooglePhotorealistic3DTileset.js b/packages/engine/Source/Scene/createGooglePhotorealistic3DTileset.js index f762d748dc36..9c7bad99e30d 100644 --- a/packages/engine/Source/Scene/createGooglePhotorealistic3DTileset.js +++ b/packages/engine/Source/Scene/createGooglePhotorealistic3DTileset.js @@ -4,23 +4,37 @@ import defined from "../Core/defined.js"; import IonResource from "../Core/IonResource.js"; import GoogleMaps from "../Core/GoogleMaps.js"; import Resource from "../Core/Resource.js"; +import oneTimeWarning from "../Core/oneTimeWarning.js"; +import deprecationWarning from "../Core/deprecationWarning.js"; /** - * Creates a {@link Cesium3DTileset} instance for the Google Photorealistic 3D Tiles tileset. + * Creates a {@link Cesium3DTileset} instance for the Google Photorealistic 3D + * Tiles tileset. + * + * Google Photorealistic 3D Tiles can only be used with the Google geocoder. To + * confirm that you are aware of this restriction pass + * `usingOnlyWithGoogleGeocoder: true` to the apiOptions. Otherwise a one time + * warning will be displayed when this function is called. * * @function * - * @param {string} [key=GoogleMaps.defaultApiKey] Your API key to access Google Photorealistic 3D Tiles. See {@link https://developers.google.com/maps/documentation/javascript/get-api-key} for instructions on how to create your own key. - * @param {Cesium3DTileset.ConstructorOptions} [options] An object describing initialization options. + * @param {object} [apiOptions] + * @param {string} [apiOptions.key=GoogleMaps.defaultApiKey] Your API key to access Google Photorealistic 3D Tiles. See {@link https://developers.google.com/maps/documentation/javascript/get-api-key} for instructions on how to create your own key. + * @param {true} [apiOptions.onlyUsingWithGoogleGeocoder] Confirmation that this tileset will only be used with the Google geocoder. + * @param {Cesium3DTileset.ConstructorOptions} [tilesetOptions] An object describing initialization options. * @returns {Promise} * * @see GoogleMaps * * @example - * const viewer = new Cesium.Viewer("cesiumContainer"); + * const viewer = new Cesium.Viewer("cesiumContainer", { + * geocoder: Cesium.IonGeocodeProviderType.GOOGLE + * }); * * try { - * const tileset = await Cesium.createGooglePhotorealistic3DTileset(); + * const tileset = await Cesium.createGooglePhotorealistic3DTileset({ + * onlyUsingWithGoogleGeocoder: true, + * }); * viewer.scene.primitives.add(tileset)); * } catch (error) { * console.log(`Error creating tileset: ${error}`); @@ -30,27 +44,52 @@ import Resource from "../Core/Resource.js"; * // Use your own Google Maps API key * Cesium.GoogleMaps.defaultApiKey = "your-api-key"; * - * const viewer = new Cesium.Viewer("cesiumContainer"); + * const viewer = new Cesium.Viewer("cesiumContainer". { + * geocoder: Cesium.IonGeocodeProviderType.GOOGLE + * }); * * try { - * const tileset = await Cesium.createGooglePhotorealistic3DTileset(); + * const tileset = await Cesium.createGooglePhotorealistic3DTileset({ + * onlyUsingWithGoogleGeocoder: true, + * }); * viewer.scene.primitives.add(tileset)); * } catch (error) { * console.log(`Error creating tileset: ${error}`); * } */ -async function createGooglePhotorealistic3DTileset(key, options) { - options = defaultValue(options, {}); - options.cacheBytes = defaultValue(options.cacheBytes, 1536 * 1024 * 1024); - options.maximumCacheOverflowBytes = defaultValue( - options.maximumCacheOverflowBytes, +async function createGooglePhotorealistic3DTileset(apiOptions, tilesetOptions) { + tilesetOptions = defaultValue(tilesetOptions, {}); + tilesetOptions.cacheBytes = defaultValue( + tilesetOptions.cacheBytes, + 1536 * 1024 * 1024, + ); + tilesetOptions.maximumCacheOverflowBytes = defaultValue( + tilesetOptions.maximumCacheOverflowBytes, 1024 * 1024 * 1024, ); - options.enableCollision = defaultValue(options.enableCollision, true); + tilesetOptions.enableCollision = defaultValue( + tilesetOptions.enableCollision, + true, + ); - key = defaultValue(key, GoogleMaps.defaultApiKey); + apiOptions = defaultValue(apiOptions, defaultValue.EMPTY_OBJECT); + if (typeof apiOptions === "string") { + deprecationWarning( + "createGooglePhotorealistic3DTileset(key)", + "createGooglePhotorealistic3DTileset(key) has been deprecated. It is replaced by createGooglePhotorealistic3DTileset({key}). It will be removed in Cesium 1.126.", + ); + apiOptions = { key: apiOptions }; + } + if (!apiOptions.onlyUsingWithGoogleGeocoder) { + oneTimeWarning( + "google-tiles-with-google-geocoder", + "Only the Google geocoder can be used with Google Photorealistic 3D Tiles. Set the `geocode` property of Viewer constructor options. You can set additionalOptions.onlyUsingWithGoogleGeocoder to hide this warning once you have configured the geocoder.", + ); + } + + const key = defaultValue(apiOptions.key, GoogleMaps.defaultApiKey); if (!defined(key)) { - return requestCachedIonTileset(options); + return requestCachedIonTileset(tilesetOptions); } let credits; @@ -67,7 +106,7 @@ async function createGooglePhotorealistic3DTileset(key, options) { credits: credits, }); - return Cesium3DTileset.fromUrl(resource, options); + return Cesium3DTileset.fromUrl(resource, tilesetOptions); } const metadataCache = {}; diff --git a/packages/engine/Specs/Core/GoogleGeocoderServicesSpec.js b/packages/engine/Specs/Core/GoogleGeocoderServicesSpec.js new file mode 100644 index 000000000000..5260dc265742 --- /dev/null +++ b/packages/engine/Specs/Core/GoogleGeocoderServicesSpec.js @@ -0,0 +1,84 @@ +import { + createGuid, + GeocoderService, + GoogleGeocoderService, + Resource, + Rectangle, +} from "../../index.js"; + +describe("Core/GoogleGeocoderService", function () { + it("conforms to GeocoderService interface", function () { + expect(GoogleGeocoderService).toConformToInterface(GeocoderService); + }); + + it("constructor throws without key", function () { + expect(function () { + return new GoogleGeocoderService({}); + }).toThrowDeveloperError(); + }); + + it("constructor sets key on _resource", function () { + const key = createGuid(); + const service = new GoogleGeocoderService({ key }); + expect(service._resource.toString()).toEqual( + `https://maps.googleapis.com/maps/api/geocode/json?key=${key}`, + ); + }); + + it("geocode returns results for status=OK", async function () { + const key = createGuid(); + const query = createGuid(); + const service = new GoogleGeocoderService({ key }); + + spyOn(Resource.prototype, "fetchJson").and.resolveTo({ + results: [ + { + formatted_address: + "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA", + geometry: { + viewport: { + northeast: { + lat: 37.4237349802915, + lng: -122.083183169709, + }, + southwest: { + lat: 37.4210370197085, + lng: -122.085881130292, + }, + }, + }, + }, + ], + status: "OK", + }); + + const results = await service.geocode(query); + + expect(results).toEqual([ + { + displayName: "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA", + destination: Rectangle.fromDegrees( + -122.085881130292, + 37.4210370197085, + -122.083183169709, + 37.4237349802915, + ), + attribution: { + html: `Google`, + collapsible: false, + }, + }, + ]); + }); + + it("returns empty array for status=ZERO_RESULTS", async function () { + const service = new GoogleGeocoderService({ key: "key" }); + + spyOn(Resource.prototype, "fetchJson").and.resolveTo({ + status: "ZERO_RESULTS", + }); + + const results = await service.geocode("test"); + expect(results).toEqual([]); + }); +}); diff --git a/packages/engine/Specs/Core/IonGeocoderServiceSpec.js b/packages/engine/Specs/Core/IonGeocoderServiceSpec.js index 17951c615c02..719a762daffc 100644 --- a/packages/engine/Specs/Core/IonGeocoderServiceSpec.js +++ b/packages/engine/Specs/Core/IonGeocoderServiceSpec.js @@ -1,7 +1,9 @@ import { + DeveloperError, GeocoderService, GeocodeType, Ion, + IonGeocodeProviderType, IonGeocoderService, } from "../../index.js"; @@ -26,20 +28,24 @@ describe("Core/IonGeocoderService", function () { expect(service._accessToken).toEqual(Ion.defaultAccessToken); expect(service._server.url).toEqual(Ion.defaultServer.url); + expect(service.geocodeProviderType).toEqual(IonGeocodeProviderType.DEFAULT); }); it("creates with specified parameters", function () { const accessToken = "123456"; const server = "http://not.ion.invalid/"; + const geocodeProviderType = IonGeocodeProviderType.GOOGLE; const service = new IonGeocoderService({ accessToken: accessToken, server: server, scene: scene, + geocodeProviderType, }); expect(service._accessToken).toEqual(accessToken); expect(service._server.url).toEqual(server); + expect(service.geocodeProviderType).toEqual(geocodeProviderType); }); it("calls inner geocoder and returns result", async function () { @@ -64,4 +70,47 @@ describe("Core/IonGeocoderService", function () { expect(service.credit).toBeUndefined(); }); + + it("setting geocodeProviderType updates _pelias.url for GOOGLE", function () { + const service = new IonGeocoderService({ + scene, + geocoder: IonGeocodeProviderType.DEFAULT, + }); + + service.geocodeProviderType = IonGeocodeProviderType.GOOGLE; + expect(service._pelias.url.queryParameters["geocoder"]).toEqual("google"); + }); + + it("setting geocodeProviderType updates _pelias.url for BING", function () { + const service = new IonGeocoderService({ + scene, + geocoder: IonGeocodeProviderType.DEFAULT, + }); + + service.geocodeProviderType = IonGeocodeProviderType.BING; + expect(service._pelias.url.queryParameters["geocoder"]).toEqual("bing"); + }); + + it("setting geocodeProviderType updates _pelias.url for DEFAULT", function () { + const service = new IonGeocoderService({ + scene, + geocoder: IonGeocodeProviderType.GOOGLE, + }); + + service.geocodeProviderType = IonGeocodeProviderType.DEFAULT; + const queryParameters = service._pelias.url.queryParameters; + expect(queryParameters.geocoder).toBeUndefined(); + // Make sure that it isn't 'geocoder: undefined' + expect(queryParameters.hasOwnProperty("geocoder")).toBeFalse(); + }); + + it("throws if setting invalid geocodeProviderType", function () { + expect( + () => new IonGeocoderService({ scene, geocodeProviderType: "junk" }), + ).toThrowError(DeveloperError, /Invalid geocodeProviderType/); + expect(() => { + const service = new IonGeocoderService({ scene }); + service.geocodeProviderType = "junk"; + }).toThrowError(DeveloperError, /Invalid geocodeProviderType/); + }); }); diff --git a/packages/engine/Specs/DataSources/EntityViewSpec.js b/packages/engine/Specs/DataSources/EntityViewSpec.js index 2aa12913f9e5..61223cc1dd0b 100644 --- a/packages/engine/Specs/DataSources/EntityViewSpec.js +++ b/packages/engine/Specs/DataSources/EntityViewSpec.js @@ -83,6 +83,10 @@ describe( entity.trackingReferenceFrame = TrackingReferenceFrame.VELOCITY; view.update(JulianDate.now()); expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10); + + entity.trackingReferenceFrame = TrackingReferenceFrame.ENU; + view.update(JulianDate.now()); + expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10); }); it("uses entity bounding sphere", function () { @@ -115,6 +119,13 @@ describe( new BoundingSphere(new Cartesian3(3, 4, 5), 6), ); expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10); + + entity.trackingReferenceFrame = TrackingReferenceFrame.ENU; + view.update( + JulianDate.now(), + new BoundingSphere(new Cartesian3(3, 4, 5), 6), + ); + expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10); }); it("uses entity viewFrom if available and boundingsphere is supplied", function () { @@ -140,6 +151,10 @@ describe( entity.trackingReferenceFrame = TrackingReferenceFrame.VELOCITY; view.update(JulianDate.now()); expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10); + + entity.trackingReferenceFrame = TrackingReferenceFrame.ENU; + view.update(JulianDate.now()); + expect(view.scene.camera.position).toEqualEpsilon(sampleOffset, 1e-10); }); it("update throws without time parameter", function () { diff --git a/packages/widgets/Source/Viewer/Viewer.js b/packages/widgets/Source/Viewer/Viewer.js index 41f76c1bb495..b00646389215 100644 --- a/packages/widgets/Source/Viewer/Viewer.js +++ b/packages/widgets/Source/Viewer/Viewer.js @@ -18,6 +18,7 @@ import { Math as CesiumMath, Property, ScreenSpaceEventType, + IonGeocoderService, } from "@cesium/engine"; import Animation from "../Animation/Animation.js"; import AnimationViewModel from "../Animation/AnimationViewModel.js"; @@ -280,7 +281,7 @@ function enableVRUI(viewer, enabled) { * @property {boolean} [baseLayerPicker=true] If set to false, the BaseLayerPicker widget will not be created. * @property {boolean} [fullscreenButton=true] If set to false, the FullscreenButton widget will not be created. * @property {boolean} [vrButton=false] If set to true, the VRButton widget will be created. - * @property {boolean|GeocoderService[]} [geocoder=true] If set to false, the Geocoder widget will not be created. + * @property {boolean|IonGeocodeProviderType|GeocoderService[]} [geocoder=IonGeocodeProviderType.DEFAULT] The geocoding service or services to use when searching with the Geocoder widget. If set to false, the Geocoder widget will not be created. * @property {boolean} [homeButton=true] If set to false, the HomeButton widget will not be created. * @property {boolean} [infoBox=true] If set to false, the InfoBox widget will not be created. * @property {boolean} [sceneModePicker=true] If set to false, the SceneModePicker widget will not be created. @@ -567,7 +568,17 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to geocoderContainer.className = "cesium-viewer-geocoderContainer"; toolbar.appendChild(geocoderContainer); let geocoderService; - if (defined(options.geocoder) && typeof options.geocoder !== "boolean") { + if (typeof options.geocoder === "string") { + geocoderService = [ + new IonGeocoderService({ + scene, + geocodeProviderType: options.geocoder, + }), + ]; + } else if ( + defined(options.geocoder) && + typeof options.geocoder !== "boolean" + ) { geocoderService = Array.isArray(options.geocoder) ? options.geocoder : [options.geocoder]; diff --git a/packages/widgets/Specs/Viewer/ViewerSpec.js b/packages/widgets/Specs/Viewer/ViewerSpec.js index a967a34289e1..822df41c034b 100644 --- a/packages/widgets/Specs/Viewer/ViewerSpec.js +++ b/packages/widgets/Specs/Viewer/ViewerSpec.js @@ -18,6 +18,8 @@ import { ImageryLayerCollection, SceneMode, ShadowMode, + IonGeocodeProviderType, + IonGeocoderService, } from "@cesium/engine"; import { @@ -354,6 +356,19 @@ describe( expect(viewer.geocoder.viewModel._geocoderServices.length).toBe(1); }); + it("constructs geocoder with IonGeocodeProviderType", function () { + viewer = createViewer(container, { + geocoder: IonGeocodeProviderType.GOOGLE, + }); + expect(viewer.geocoder).toBeDefined(); + expect(viewer.geocoder.viewModel._geocoderServices.length).toBe(1); + const geocoderService = viewer.geocoder.viewModel._geocoderServices[0]; + expect(geocoderService).toBeInstanceOf(IonGeocoderService); + expect(geocoderService.geocodeProviderType).toEqual( + IonGeocodeProviderType.GOOGLE, + ); + }); + it("constructs geocoder with geocoder service option", function () { const service = new CartographicGeocoderService(); viewer = createViewer(container, {