diff --git a/Apps/Sandcastle/gallery/Cesium Widget.html b/Apps/Sandcastle/gallery/Cesium Widget.html
index ab81249db25b..79c0487dad3f 100644
--- a/Apps/Sandcastle/gallery/Cesium Widget.html
+++ b/Apps/Sandcastle/gallery/Cesium Widget.html
@@ -37,7 +37,27 @@
// it does not include the animation, imagery selection,
// and other widgets, nor does it depend on the third-party
// Knockout library.
- const widget = new Cesium.CesiumWidget("cesiumContainer");
+ const widget = new Cesium.CesiumWidget("cesiumContainer", {
+ shouldAnimate: true,
+ });
+
+ const position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 500);
+ const heading = Cesium.Math.toRadians(135);
+ const pitch = 0;
+ const roll = 0;
+ const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
+ const orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
+
+ const entity = widget.entities.add({
+ position: position,
+ orientation: orientation,
+ model: {
+ uri: "../../SampleData/models/CesiumAir/Cesium_Air.glb",
+ minimumPixelSize: 128,
+ maximumScale: 20000,
+ },
+ });
+ widget.trackedEntity = entity;
//Sandcastle_End
};
if (typeof Cesium !== "undefined") {
diff --git a/CHANGES.md b/CHANGES.md
index 09534ebabab2..30178ac0d929 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -25,6 +25,10 @@
- Updated default 3D Tiles and Model lighting when using PBR in order to create a more realistic appearance. Added `DynamicEnvironmentMapManager` to control lighting parameters. These can be accessed via `Cesium3DTileset.environmentMapManager` and `Model.environmentMapManager`. [#12129](https://github.com/CesiumGS/cesium/pull/12129)
- Added `ScreenSpaceCameraController.maximumTiltAngle` to limit how much the camera can tilt. [#12169](https://github.com/CesiumGS/cesium/pull/12169)
- Update Japan Buildings sandcastle to use Japan Regional Terrain [#12259](https://github.com/CesiumGS/cesium/pull/12259)
+- Moved `Viewer` functionality to `CesiumWidget` to increase usability, see the full list added to the `CesiumWidget` below. No functionality was removed from the `Viewer` but convenience helpers like the `entities` collection were added to the `CesiumWidget`. The `CesiumWidget` should be closer to a drop in replacement for the `Viewer` when not utilizing the extra Viewer widgets. [#11967](https://github.com/CesiumGS/cesium/issues/11967).
+ - New constructor options: `options.shouldAnimate`, `options.automaticallyTrackDataSourceClocks`, `options.dataSources`
+ - New properties: `dataSourceDisplay`, `entities`, `dataSources`, `allowDataSourcesToSuspendAnimation`, `trackedEntity`, `trackedEntityChanged`, `clockTrackedDataSource`
+ - New functions: `zoomTo()`, `flyTo()`
- Update Bing Maps attribution link [#12229] (https://github.com/CesiumGS/cesium/pull/12265)
##### Fixes :wrench:
diff --git a/packages/engine/Source/Widget/CesiumWidget.js b/packages/engine/Source/Widget/CesiumWidget.js
index 3d01ca4c3c4d..4e08a9beffc7 100644
--- a/packages/engine/Source/Widget/CesiumWidget.js
+++ b/packages/engine/Source/Widget/CesiumWidget.js
@@ -1,13 +1,26 @@
+import BoundingSphere from "../Core/BoundingSphere.js";
import Cartesian3 from "../Core/Cartesian3.js";
+import Cartographic from "../Core/Cartographic.js";
import Clock from "../Core/Clock.js";
import defaultValue from "../Core/defaultValue.js";
import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import Ellipsoid from "../Core/Ellipsoid.js";
+import Event from "../Core/Event.js";
+import EventHelper from "../Core/EventHelper.js";
import FeatureDetection from "../Core/FeatureDetection.js";
import formatError from "../Core/formatError.js";
+import HeadingPitchRange from "../Core/HeadingPitchRange.js";
+import Matrix4 from "../Core/Matrix4.js";
+import BoundingSphereState from "../DataSources/BoundingSphereState.js";
+import DataSourceCollection from "../DataSources/DataSourceCollection.js";
+import DataSourceDisplay from "../DataSources/DataSourceDisplay.js";
+import EntityView from "../DataSources/EntityView.js";
import getElement from "../DataSources/getElement.js";
+import Property from "../DataSources/Property.js";
+import Cesium3DTileset from "../Scene/Cesium3DTileset.js";
+import computeFlyToLocationForRectangle from "../Scene/computeFlyToLocationForRectangle.js";
import Globe from "../Scene/Globe.js";
import ImageryLayer from "../Scene/ImageryLayer.js";
import Moon from "../Scene/Moon.js";
@@ -18,6 +31,17 @@ import ShadowMode from "../Scene/ShadowMode.js";
import SkyAtmosphere from "../Scene/SkyAtmosphere.js";
import SkyBox from "../Scene/SkyBox.js";
import Sun from "../Scene/Sun.js";
+import TimeDynamicPointCloud from "../Scene/TimeDynamicPointCloud.js";
+import VoxelPrimitive from "../Scene/VoxelPrimitive.js";
+
+function trackDataSourceClock(clock, dataSource) {
+ if (defined(dataSource)) {
+ const dataSourceClock = dataSource.clock;
+ if (defined(dataSourceClock)) {
+ dataSourceClock.getValue(clock);
+ }
+ }
+}
function startRenderLoop(widget) {
widget._renderLoopRunning = true;
@@ -118,6 +142,7 @@ function configureCameraFrustum(widget) {
* @param {Element|string} container The DOM element or ID that will contain the widget.
* @param {object} [options] Object with the following properties:
* @param {Clock} [options.clock=new Clock()] The clock to use to control current time.
+ * @param {boolean} [options.shouldAnimate=false] true
if the clock should attempt to advance simulation time by default, false
otherwise.
* @param {Ellipsoid} [options.ellipsoid=Ellipsoid.default] The default ellipsoid.
* @param {ImageryLayer|false} [options.baseLayer=ImageryLayer.fromWorldImagery()] The bottommost imagery layer applied to the globe. If set to false
, no imagery provider will be added.
* @param {TerrainProvider} [options.terrainProvider=new EllipsoidTerrainProvider(options.ellipsoid)] The terrain provider.
@@ -133,14 +158,17 @@ function configureCameraFrustum(widget) {
* @param {boolean} [options.useBrowserRecommendedResolution=true] If true, render at the browser's recommended resolution and ignore window.devicePixelRatio
.
* @param {number} [options.targetFrameRate] The target frame rate when using the default render loop.
* @param {boolean} [options.showRenderLoopErrors=true] If true, this widget will automatically display an HTML panel to the user containing the error, if a render loop error occurs.
+ * @param {boolean} [options.automaticallyTrackDataSourceClocks=true] If true, this widget will automatically track the clock settings of newly added DataSources, updating if the DataSource's clock changes. Set this to false if you want to configure the clock independently.
* @param {ContextOptions} [options.contextOptions] Context and WebGL creation properties passed to {@link Scene}.
* @param {Element|string} [options.creditContainer] The DOM element or ID that will contain the {@link CreditDisplay}. If not specified, the credits are added
* to the bottom of the widget itself.
* @param {Element|string} [options.creditViewport] The DOM element or ID that will contain the credit pop up created by the {@link CreditDisplay}. If not specified, it will appear over the widget itself.
+ * @param {DataSourceCollection} [options.dataSources=new DataSourceCollection()] The collection of data sources visualized by the widget. If this parameter is provided,
+ * the instance is assumed to be owned by the caller and will not be destroyed when the widget is destroyed.
* @param {boolean} [options.shadows=false] Determines if shadows are cast by light sources.
* @param {ShadowMode} [options.terrainShadows=ShadowMode.RECEIVE_ONLY] Determines if the terrain casts or receives shadows from light sources.
* @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
- * @param {boolean} [options.blurActiveElementOnCanvasFocus=true] If true, the active element will blur when the viewer's canvas is clicked. Setting this to false is useful for cases when the canvas is clicked only for retrieving position or an entity data without actually meaning to set the canvas to be the active element.
+ * @param {boolean} [options.blurActiveElementOnCanvasFocus=true] If true, the active element will blur when the widget's canvas is clicked. Setting this to false is useful for cases when the canvas is clicked only for retrieving position or an entity data without actually meaning to set the canvas to be the active element.
* @param {boolean} [options.requestRenderMode=false] If true, rendering a frame will only occur when needed as determined by changes within the scene. Enabling improves performance of the application, but requires using {@link Scene#requestRender} to render a new frame explicitly in this mode. This will be necessary in many cases after making changes to the scene in other parts of the API. See {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}.
* @param {number} [options.maximumRenderTimeChange=0.0] If requestRenderMode is true, this value defines the maximum change in simulation time allowed before a render is requested. See {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}.
* @param {number} [options.msaaSamples=4] If provided, this value controls the rate of multisample antialiasing. Typical multisampling rates are 2, 4, and sometimes 8 samples per pixel. Higher sampling rates of MSAA may impact performance in exchange for improved visual quality. This value only applies to WebGL2 contexts that support multisample render targets. Set to 1 to disable MSAA.
@@ -266,8 +294,23 @@ function CesiumWidget(container, options) {
this._resolutionScale = 1.0;
this._useBrowserRecommendedResolution = useBrowserRecommendedResolution;
this._forceResize = false;
+ this._entityView = undefined;
+ this._clockTrackedDataSource = undefined;
+ this._trackedEntity = undefined;
+ this._needTrackedEntityUpdate = false;
+ this._zoomIsFlight = false;
+ this._zoomTarget = undefined;
+ this._zoomPromise = undefined;
+ this._zoomOptions = undefined;
+ this._trackedEntityChanged = new Event();
+ this._allowDataSourcesToSuspendAnimation = true;
+
this._clock = defined(options.clock) ? options.clock : new Clock();
+ if (defined(options.shouldAnimate)) {
+ this._clock.shouldAnimate = options.shouldAnimate;
+ }
+
configureCanvasSize(this);
try {
@@ -388,6 +431,71 @@ function CesiumWidget(container, options) {
}
};
scene.renderError.addEventListener(this._onRenderError);
+
+ let dataSourceCollection = options.dataSources;
+ let destroyDataSourceCollection = false;
+ if (!defined(dataSourceCollection)) {
+ dataSourceCollection = new DataSourceCollection();
+ destroyDataSourceCollection = true;
+ }
+
+ const dataSourceDisplay = new DataSourceDisplay({
+ scene: scene,
+ dataSourceCollection: dataSourceCollection,
+ });
+
+ const eventHelper = new EventHelper();
+ this._dataSourceChangedListeners = {};
+ this._automaticallyTrackDataSourceClocks =
+ options.automaticallyTrackDataSourceClocks ?? true;
+
+ this._dataSourceCollection = dataSourceCollection;
+ this._destroyDataSourceCollection = destroyDataSourceCollection;
+ this._dataSourceDisplay = dataSourceDisplay;
+ this._eventHelper = eventHelper;
+ this._canAnimateUpdateCallback = this._updateCanAnimate;
+
+ eventHelper.add(this._clock.onTick, CesiumWidget.prototype._onTick, this);
+ eventHelper.add(
+ scene.morphStart,
+ CesiumWidget.prototype._clearTrackedObject,
+ this,
+ );
+
+ //Listen to data source events in order to track clock changes.
+ eventHelper.add(
+ dataSourceCollection.dataSourceAdded,
+ CesiumWidget.prototype._onDataSourceAdded,
+ this,
+ );
+ eventHelper.add(
+ dataSourceCollection.dataSourceRemoved,
+ CesiumWidget.prototype._onDataSourceRemoved,
+ this,
+ );
+
+ eventHelper.add(scene.postRender, CesiumWidget.prototype._postRender, this);
+
+ // We need to subscribe to the data sources and collections so that we can clear the
+ // tracked object when it is removed from the scene.
+ // Subscribe to current data sources
+ const dataSourceLength = dataSourceCollection.length;
+ for (let i = 0; i < dataSourceLength; i++) {
+ this._dataSourceAdded(dataSourceCollection, dataSourceCollection.get(i));
+ }
+ this._dataSourceAdded(undefined, dataSourceDisplay.defaultDataSource);
+
+ // Hook up events so that we can subscribe to future sources.
+ eventHelper.add(
+ dataSourceCollection.dataSourceAdded,
+ CesiumWidget.prototype._dataSourceAdded,
+ this,
+ );
+ eventHelper.add(
+ dataSourceCollection.dataSourceRemoved,
+ CesiumWidget.prototype._dataSourceRemoved,
+ this,
+ );
} catch (error) {
if (showRenderLoopErrors) {
const title = "Error constructing CesiumWidget.";
@@ -505,6 +613,43 @@ Object.defineProperties(CesiumWidget.prototype, {
},
},
+ /**
+ * Gets the display used for {@link DataSource} visualization.
+ * @memberof CesiumWidget.prototype
+ * @type {DataSourceDisplay}
+ * @readonly
+ */
+ dataSourceDisplay: {
+ get: function () {
+ return this._dataSourceDisplay;
+ },
+ },
+
+ /**
+ * Gets the collection of entities not tied to a particular data source.
+ * This is a shortcut to [dataSourceDisplay.defaultDataSource.entities]{@link CesiumWidget#dataSourceDisplay}.
+ * @memberof CesiumWidget.prototype
+ * @type {EntityCollection}
+ * @readonly
+ */
+ entities: {
+ get: function () {
+ return this._dataSourceDisplay.defaultDataSource.entities;
+ },
+ },
+
+ /**
+ * Gets the set of {@link DataSource} instances to be visualized.
+ * @memberof CesiumWidget.prototype
+ * @type {DataSourceCollection}
+ * @readonly
+ */
+ dataSources: {
+ get: function () {
+ return this._dataSourceCollection;
+ },
+ },
+
/**
* Gets the camera.
* @memberof CesiumWidget.prototype
@@ -662,6 +807,104 @@ Object.defineProperties(CesiumWidget.prototype, {
}
},
},
+
+ /**
+ * Gets or sets whether or not data sources can temporarily pause
+ * animation in order to avoid showing an incomplete picture to the user.
+ * For example, if asynchronous primitives are being processed in the
+ * background, the clock will not advance until the geometry is ready.
+ *
+ * @memberof CesiumWidget.prototype
+ *
+ * @type {boolean}
+ */
+ allowDataSourcesToSuspendAnimation: {
+ get: function () {
+ return this._allowDataSourcesToSuspendAnimation;
+ },
+ set: function (value) {
+ this._allowDataSourcesToSuspendAnimation = value;
+ },
+ },
+
+ /**
+ * Gets or sets the Entity instance currently being tracked by the camera.
+ * @memberof CesiumWidget.prototype
+ * @type {Entity | undefined}
+ */
+ trackedEntity: {
+ get: function () {
+ return this._trackedEntity;
+ },
+ set: function (value) {
+ if (this._trackedEntity !== value) {
+ this._trackedEntity = value;
+
+ //Cancel any pending zoom
+ cancelZoom(this);
+
+ const scene = this.scene;
+ const sceneMode = scene.mode;
+
+ //Stop tracking
+ if (!defined(value) || !defined(value.position)) {
+ this._needTrackedEntityUpdate = false;
+ if (
+ sceneMode === SceneMode.COLUMBUS_VIEW ||
+ sceneMode === SceneMode.SCENE2D
+ ) {
+ scene.screenSpaceCameraController.enableTranslate = true;
+ }
+
+ if (
+ sceneMode === SceneMode.COLUMBUS_VIEW ||
+ sceneMode === SceneMode.SCENE3D
+ ) {
+ scene.screenSpaceCameraController.enableTilt = true;
+ }
+
+ this._entityView = undefined;
+ this.camera.lookAtTransform(Matrix4.IDENTITY);
+ } else {
+ //We can't start tracking immediately, so we set a flag and start tracking
+ //when the bounding sphere is ready (most likely next frame).
+ this._needTrackedEntityUpdate = true;
+ }
+
+ this._trackedEntityChanged.raiseEvent(value);
+ this.scene.requestRender();
+ }
+ },
+ },
+
+ /**
+ * Gets the event that is raised when the tracked entity changes.
+ * @memberof CesiumWidget.prototype
+ * @type {Event}
+ * @readonly
+ */
+ trackedEntityChanged: {
+ get: function () {
+ return this._trackedEntityChanged;
+ },
+ },
+
+ /**
+ * Gets or sets the data source to track with the widget's clock.
+ * @memberof CesiumWidget.prototype
+ * @type {DataSource}
+ */
+ clockTrackedDataSource: {
+ get: function () {
+ return this._clockTrackedDataSource;
+ },
+ set: function (value) {
+ if (this._clockTrackedDataSource !== value) {
+ this._clockTrackedDataSource = value;
+ trackDataSourceClock(this.clock, value);
+ }
+ },
+ },
});
/**
@@ -785,12 +1028,29 @@ CesiumWidget.prototype.isDestroyed = function () {
* removing the widget from layout.
*/
CesiumWidget.prototype.destroy = function () {
+ // Unsubscribe from data sources
+ const dataSources = this.dataSources;
+ const dataSourceLength = dataSources.length;
+ for (let i = 0; i < dataSourceLength; i++) {
+ this._dataSourceRemoved(dataSources, dataSources.get(i));
+ }
+ this._dataSourceRemoved(undefined, this._dataSourceDisplay.defaultDataSource);
+
+ this._dataSourceDisplay = this._dataSourceDisplay.destroy();
+
if (defined(this._scene)) {
this._scene.renderError.removeEventListener(this._onRenderError);
this._scene = this._scene.destroy();
}
this._container.removeChild(this._element);
this._creditContainer.removeChild(this._innerCreditContainer);
+
+ this._eventHelper.removeAll();
+
+ if (this._destroyDataSourceCollection) {
+ this._dataSourceCollection = this._dataSourceCollection.destroy();
+ }
+
destroyObject(this);
};
@@ -830,4 +1090,519 @@ CesiumWidget.prototype.render = function () {
this._clock.tick();
}
};
+
+/**
+ * @private
+ */
+CesiumWidget.prototype._dataSourceAdded = function (
+ dataSourceCollection,
+ dataSource,
+) {
+ const entityCollection = dataSource.entities;
+ entityCollection.collectionChanged.addEventListener(
+ CesiumWidget.prototype._onEntityCollectionChanged,
+ this,
+ );
+};
+
+/**
+ * @private
+ */
+CesiumWidget.prototype._dataSourceRemoved = function (
+ dataSourceCollection,
+ dataSource,
+) {
+ const entityCollection = dataSource.entities;
+ entityCollection.collectionChanged.removeEventListener(
+ CesiumWidget.prototype._onEntityCollectionChanged,
+ this,
+ );
+
+ if (defined(this.trackedEntity)) {
+ if (
+ entityCollection.getById(this.trackedEntity.id) === this.trackedEntity
+ ) {
+ this.trackedEntity = undefined;
+ }
+ }
+};
+
+/**
+ * @private
+ */
+CesiumWidget.prototype._updateCanAnimate = function (isUpdated) {
+ this._clock.canAnimate = isUpdated;
+};
+
+const boundingSphereScratch = new BoundingSphere();
+
+/**
+ * @private
+ */
+CesiumWidget.prototype._onTick = function (clock) {
+ const time = clock.currentTime;
+
+ const isUpdated = this._dataSourceDisplay.update(time);
+ if (this._allowDataSourcesToSuspendAnimation) {
+ this._canAnimateUpdateCallback(isUpdated);
+ }
+
+ const entityView = this._entityView;
+ if (defined(entityView)) {
+ const trackedEntity = this._trackedEntity;
+ const trackedState = this._dataSourceDisplay.getBoundingSphere(
+ trackedEntity,
+ true,
+ boundingSphereScratch,
+ );
+ if (trackedState === BoundingSphereState.DONE) {
+ entityView.update(time, boundingSphereScratch);
+ }
+ }
+};
+
+/**
+ * @private
+ */
+CesiumWidget.prototype._onEntityCollectionChanged = function (
+ collection,
+ added,
+ removed,
+) {
+ const length = removed.length;
+ for (let i = 0; i < length; i++) {
+ const removedObject = removed[i];
+ if (this.trackedEntity === removedObject) {
+ this.trackedEntity = undefined;
+ }
+ }
+};
+
+/**
+ * @private
+ */
+CesiumWidget.prototype._clearTrackedObject = function () {
+ this.trackedEntity = undefined;
+};
+
+/**
+ * @private
+ */
+CesiumWidget.prototype._onDataSourceChanged = function (dataSource) {
+ if (this.clockTrackedDataSource === dataSource) {
+ trackDataSourceClock(this.clock, dataSource);
+ }
+};
+
+/**
+ * @private
+ */
+CesiumWidget.prototype._onDataSourceAdded = function (
+ dataSourceCollection,
+ dataSource,
+) {
+ if (this._automaticallyTrackDataSourceClocks) {
+ this.clockTrackedDataSource = dataSource;
+ }
+ const id = dataSource.entities.id;
+ const removalFunc = this._eventHelper.add(
+ dataSource.changedEvent,
+ CesiumWidget.prototype._onDataSourceChanged,
+ this,
+ );
+ this._dataSourceChangedListeners[id] = removalFunc;
+};
+
+/**
+ * @private
+ */
+CesiumWidget.prototype._onDataSourceRemoved = function (
+ dataSourceCollection,
+ dataSource,
+) {
+ const resetClock = this.clockTrackedDataSource === dataSource;
+ const id = dataSource.entities.id;
+ this._dataSourceChangedListeners[id]();
+ this._dataSourceChangedListeners[id] = undefined;
+ if (resetClock) {
+ const numDataSources = dataSourceCollection.length;
+ if (this._automaticallyTrackDataSourceClocks && numDataSources > 0) {
+ this.clockTrackedDataSource = dataSourceCollection.get(
+ numDataSources - 1,
+ );
+ } else {
+ this.clockTrackedDataSource = undefined;
+ }
+ }
+};
+
+/**
+ * Asynchronously sets the camera to view the provided entity, entities, or data source.
+ * If the data source is still in the process of loading or the visualization is otherwise still loading,
+ * this method waits for the data to be ready before performing the zoom.
+ *
+ *
The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
+ * The heading and the pitch angles are defined in the local east-north-up reference frame.
+ * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
+ * angles are above the plane. Negative pitch angles are below the plane. The range is the distance from the center. If the range is
+ * zero, a range will be computed such that the whole bounding sphere is visible.
+ *
+ * In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
+ * target will be the range. The heading will be determined from the offset. If the heading cannot be
+ * determined from the offset, the heading will be north.
+ *
+ * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud|Promise} target The entity, array of entities, entity collection, data source, Cesium3DTileset, point cloud, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types.
+ * @param {HeadingPitchRange} [offset] The offset from the center of the entity in the local east-north-up reference frame.
+ * @returns {Promise} A Promise that resolves to true if the zoom was successful or false if the target is not currently visualized in the scene or the zoom was cancelled.
+ */
+CesiumWidget.prototype.zoomTo = function (target, offset) {
+ const options = {
+ offset: offset,
+ };
+ return zoomToOrFly(this, target, options, false);
+};
+
+/**
+ * Flies the camera to the provided entity, entities, or data source.
+ * If the data source is still in the process of loading or the visualization is otherwise still loading,
+ * this method waits for the data to be ready before performing the flight.
+ *
+ * The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
+ * The heading and the pitch angles are defined in the local east-north-up reference frame.
+ * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
+ * angles are above the plane. Negative pitch angles are below the plane. The range is the distance from the center. If the range is
+ * zero, a range will be computed such that the whole bounding sphere is visible.
+ *
+ * In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
+ * target will be the range. The heading will be determined from the offset. If the heading cannot be
+ * determined from the offset, the heading will be north.
+ *
+ * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud|Promise} target The entity, array of entities, entity collection, data source, Cesium3DTileset, point cloud, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types.
+ * @param {object} [options] Object with the following properties:
+ * @param {number} [options.duration=3.0] The duration of the flight in seconds.
+ * @param {number} [options.maximumHeight] The maximum height at the peak of the flight.
+ * @param {HeadingPitchRange} [options.offset] The offset from the target in the local east-north-up reference frame centered at the target.
+ * @returns {Promise} A Promise that resolves to true if the flight was successful or false if the target is not currently visualized in the scene or the flight was cancelled. //TODO: Cleanup entity mentions
+ */
+CesiumWidget.prototype.flyTo = function (target, options) {
+ return zoomToOrFly(this, target, options, true);
+};
+
+function zoomToOrFly(that, zoomTarget, options, isFlight) {
+ //>>includeStart('debug', pragmas.debug);
+ if (!defined(zoomTarget)) {
+ throw new DeveloperError("zoomTarget is required.");
+ }
+ //>>includeEnd('debug');
+
+ cancelZoom(that);
+
+ //We can't actually perform the zoom until all visualization is ready and
+ //bounding spheres have been computed. Therefore we create and return
+ //a deferred which will be resolved as part of the post-render step in the
+ //frame that actually performs the zoom.
+ const zoomPromise = new Promise((resolve) => {
+ that._completeZoom = function (value) {
+ resolve(value);
+ };
+ });
+ that._zoomPromise = zoomPromise;
+ that._zoomIsFlight = isFlight;
+ that._zoomOptions = options;
+
+ Promise.resolve(zoomTarget).then(function (zoomTarget) {
+ //Only perform the zoom if it wasn't cancelled before the promise resolved.
+ if (that._zoomPromise !== zoomPromise) {
+ return;
+ }
+
+ //If the zoom target is a rectangular imagery in an ImageLayer
+ if (zoomTarget instanceof ImageryLayer) {
+ let rectanglePromise;
+
+ if (defined(zoomTarget.imageryProvider)) {
+ rectanglePromise = Promise.resolve(zoomTarget.getImageryRectangle());
+ } else {
+ rectanglePromise = new Promise((resolve) => {
+ const removeListener = zoomTarget.readyEvent.addEventListener(() => {
+ removeListener();
+ resolve(zoomTarget.getImageryRectangle());
+ });
+ });
+ }
+ rectanglePromise
+ .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 = position;
+ }
+ });
+ return;
+ }
+
+ if (
+ zoomTarget instanceof Cesium3DTileset ||
+ zoomTarget instanceof TimeDynamicPointCloud ||
+ zoomTarget instanceof VoxelPrimitive
+ ) {
+ that._zoomTarget = zoomTarget;
+ return;
+ }
+
+ //If the zoom target is a data source, and it's in the middle of loading, wait for it to finish loading.
+ if (zoomTarget.isLoading && defined(zoomTarget.loadingEvent)) {
+ const removeEvent = zoomTarget.loadingEvent.addEventListener(function () {
+ removeEvent();
+
+ //Only perform the zoom if it wasn't cancelled before the data source finished.
+ if (that._zoomPromise === zoomPromise) {
+ that._zoomTarget = zoomTarget.entities.values.slice(0);
+ }
+ });
+ return;
+ }
+
+ //Zoom target is already an array, just copy it and return.
+ if (Array.isArray(zoomTarget)) {
+ that._zoomTarget = zoomTarget.slice(0);
+ return;
+ }
+
+ //If zoomTarget is an EntityCollection, this will retrieve the array
+ zoomTarget = zoomTarget.values ?? zoomTarget;
+
+ //If zoomTarget is a DataSource, this will retrieve the array.
+ if (defined(zoomTarget.entities)) {
+ zoomTarget = zoomTarget.entities.values;
+ }
+
+ //Zoom target is already an array, just copy it and return.
+ if (Array.isArray(zoomTarget)) {
+ that._zoomTarget = zoomTarget.slice(0);
+ } else {
+ //Single entity
+ that._zoomTarget = [zoomTarget];
+ }
+ });
+
+ that.scene.requestRender();
+ return zoomPromise;
+}
+
+function clearZoom(widget) {
+ widget._zoomPromise = undefined;
+ widget._zoomTarget = undefined;
+ widget._zoomOptions = undefined;
+}
+
+function cancelZoom(widget) {
+ const zoomPromise = widget._zoomPromise;
+ if (defined(zoomPromise)) {
+ clearZoom(widget);
+ widget._completeZoom(false);
+ }
+}
+
+/**
+ * @private
+ */
+CesiumWidget.prototype._postRender = function () {
+ updateZoomTarget(this);
+ updateTrackedEntity(this);
+};
+
+function updateZoomTarget(widget) {
+ const target = widget._zoomTarget;
+ if (!defined(target) || widget.scene.mode === SceneMode.MORPHING) {
+ return;
+ }
+
+ const scene = widget.scene;
+ const camera = scene.camera;
+ const zoomOptions = widget._zoomOptions ?? {};
+ let options;
+ function zoomToBoundingSphere(boundingSphere) {
+ // If offset was originally undefined then give it base value instead of empty object
+ if (!defined(zoomOptions.offset)) {
+ zoomOptions.offset = new HeadingPitchRange(
+ 0.0,
+ -0.5,
+ boundingSphere.radius,
+ );
+ }
+
+ options = {
+ offset: zoomOptions.offset,
+ duration: zoomOptions.duration,
+ maximumHeight: zoomOptions.maximumHeight,
+ complete: function () {
+ widget._completeZoom(true);
+ },
+ cancel: function () {
+ widget._completeZoom(false);
+ },
+ };
+
+ if (widget._zoomIsFlight) {
+ camera.flyToBoundingSphere(target.boundingSphere, options);
+ } else {
+ camera.viewBoundingSphere(boundingSphere, zoomOptions.offset);
+ camera.lookAtTransform(Matrix4.IDENTITY);
+
+ // Finish the promise
+ widget._completeZoom(true);
+ }
+
+ clearZoom(widget);
+ }
+
+ if (target instanceof TimeDynamicPointCloud) {
+ if (defined(target.boundingSphere)) {
+ zoomToBoundingSphere(target.boundingSphere);
+ return;
+ }
+
+ // Otherwise, the first "frame" needs to have been rendered
+ const removeEventListener = target.frameChanged.addEventListener(
+ function (timeDynamicPointCloud) {
+ zoomToBoundingSphere(timeDynamicPointCloud.boundingSphere);
+ removeEventListener();
+ },
+ );
+ return;
+ }
+
+ if (target instanceof Cesium3DTileset || target instanceof VoxelPrimitive) {
+ zoomToBoundingSphere(target.boundingSphere);
+ return;
+ }
+
+ // If zoomTarget was an ImageryLayer
+ if (target instanceof Cartographic) {
+ options = {
+ destination: scene.ellipsoid.cartographicToCartesian(target),
+ duration: zoomOptions.duration,
+ maximumHeight: zoomOptions.maximumHeight,
+ complete: function () {
+ widget._completeZoom(true);
+ },
+ cancel: function () {
+ widget._completeZoom(false);
+ },
+ };
+
+ if (widget._zoomIsFlight) {
+ camera.flyTo(options);
+ } else {
+ camera.setView(options);
+ widget._completeZoom(true);
+ }
+ clearZoom(widget);
+ return;
+ }
+
+ const entities = target;
+
+ const boundingSpheres = [];
+ for (let i = 0, len = entities.length; i < len; i++) {
+ const state = widget._dataSourceDisplay.getBoundingSphere(
+ entities[i],
+ false,
+ boundingSphereScratch,
+ );
+
+ if (state === BoundingSphereState.PENDING) {
+ return;
+ } else if (state !== BoundingSphereState.FAILED) {
+ boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
+ }
+ }
+
+ if (boundingSpheres.length === 0) {
+ cancelZoom(widget);
+ return;
+ }
+
+ // Stop tracking the current entity.
+ widget.trackedEntity = undefined;
+
+ const boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
+
+ if (!widget._zoomIsFlight) {
+ camera.viewBoundingSphere(boundingSphere, zoomOptions.offset);
+ camera.lookAtTransform(Matrix4.IDENTITY);
+ clearZoom(widget);
+ widget._completeZoom(true);
+ } else {
+ clearZoom(widget);
+ camera.flyToBoundingSphere(boundingSphere, {
+ duration: zoomOptions.duration,
+ maximumHeight: zoomOptions.maximumHeight,
+ complete: function () {
+ widget._completeZoom(true);
+ },
+ cancel: function () {
+ widget._completeZoom(false);
+ },
+ offset: zoomOptions.offset,
+ });
+ }
+}
+
+function updateTrackedEntity(widget) {
+ if (!widget._needTrackedEntityUpdate) {
+ return;
+ }
+
+ const trackedEntity = widget._trackedEntity;
+ const currentTime = widget.clock.currentTime;
+
+ //Verify we have a current position at this time. This is only triggered if a position
+ //has become undefined after trackedEntity is set but before the boundingSphere has been
+ //computed. In this case, we will track the entity once it comes back into existence.
+ const currentPosition = Property.getValueOrUndefined(
+ trackedEntity.position,
+ currentTime,
+ );
+
+ if (!defined(currentPosition)) {
+ return;
+ }
+
+ const scene = widget.scene;
+
+ const state = widget._dataSourceDisplay.getBoundingSphere(
+ trackedEntity,
+ false,
+ boundingSphereScratch,
+ );
+ if (state === BoundingSphereState.PENDING) {
+ return;
+ }
+
+ const sceneMode = scene.mode;
+ if (
+ sceneMode === SceneMode.COLUMBUS_VIEW ||
+ sceneMode === SceneMode.SCENE2D
+ ) {
+ scene.screenSpaceCameraController.enableTranslate = false;
+ }
+
+ if (
+ sceneMode === SceneMode.COLUMBUS_VIEW ||
+ sceneMode === SceneMode.SCENE3D
+ ) {
+ scene.screenSpaceCameraController.enableTilt = false;
+ }
+
+ const bs =
+ state !== BoundingSphereState.FAILED ? boundingSphereScratch : undefined;
+ widget._entityView = new EntityView(trackedEntity, scene, scene.ellipsoid);
+ widget._entityView.update(currentTime, bs);
+ widget._needTrackedEntityUpdate = false;
+}
+
export default CesiumWidget;
diff --git a/packages/engine/Specs/Widget/CesiumWidgetSpec.js b/packages/engine/Specs/Widget/CesiumWidgetSpec.js
index 9c7720b086a7..00fc6aef71fe 100644
--- a/packages/engine/Specs/Widget/CesiumWidgetSpec.js
+++ b/packages/engine/Specs/Widget/CesiumWidgetSpec.js
@@ -1,10 +1,30 @@
import {
+ BoundingSphere,
+ Cartesian3,
+ Cesium3DTileset,
+ Cesium3DTilesVoxelProvider,
CesiumWidget,
Clock,
+ ClockRange,
+ ClockStep,
CreditDisplay,
+ Color,
+ ConstantPositionProperty,
+ ConstantProperty,
+ DataSourceClock,
+ DataSourceCollection,
defaultValue,
+ defined,
EllipsoidTerrainProvider,
+ Entity,
+ HeadingPitchRange,
+ JulianDate,
+ Matrix4,
+ Rectangle,
ScreenSpaceEventHandler,
+ TimeDynamicPointCloud,
+ TimeIntervalCollection,
+ VoxelPrimitive,
WebMercatorProjection,
Camera,
ImageryLayer,
@@ -17,6 +37,7 @@ import {
import DomEventSimulator from "../../../../Specs/DomEventSimulator.js";
import getWebGLStub from "../../../../Specs/getWebGLStub.js";
+import MockDataSource from "../../../../Specs/MockDataSource.js";
import pollToPromise from "../../../../Specs/pollToPromise.js";
describe(
@@ -41,6 +62,15 @@ describe(
document.body.removeChild(container);
});
+ const testProvider = {
+ tilingScheme: {
+ tileXYToRectangle: function () {
+ return new Rectangle();
+ },
+ },
+ rectangle: Rectangle.MAX_VALUE,
+ };
+
function createCesiumWidget(container, options) {
options = defaultValue(options, {});
options.contextOptions = defaultValue(options.contextOptions, {});
@@ -89,6 +119,14 @@ describe(
expect(widget.clock).toBe(options.clock);
});
+ it("can set shouldAnimate", function () {
+ const options = {
+ shouldAnimate: true,
+ };
+ widget = createCesiumWidget(container, options);
+ expect(widget.clock.shouldAnimate).toBe(true);
+ });
+
it("can set scene mode 2D", function () {
widget = createCesiumWidget(container, {
sceneMode: SceneMode.SCENE2D,
@@ -205,6 +243,30 @@ describe(
expect(widget.scene.skyBox).toBe(options.skyBox);
});
+ it("can set dataSources at construction", function () {
+ const collection = new DataSourceCollection();
+ widget = createCesiumWidget(container, {
+ dataSources: collection,
+ });
+ expect(widget.dataSources).toBe(collection);
+ });
+
+ it("default DataSourceCollection is destroyed when widget is destroyed", function () {
+ widget = createCesiumWidget(container);
+ const dataSources = widget.dataSources;
+ widget.destroy();
+ expect(dataSources.isDestroyed()).toBe(true);
+ });
+
+ it("specified DataSourceCollection is not destroyed when widget is destroyed", function () {
+ const collection = new DataSourceCollection();
+ widget = createCesiumWidget(container, {
+ dataSources: collection,
+ });
+ widget.destroy();
+ expect(collection.isDestroyed()).toBe(false);
+ });
+
it("can set contextOptions", function () {
const webglOptions = {
alpha: true,
@@ -268,6 +330,126 @@ describe(
);
});
+ it("can get and set trackedEntity", function () {
+ widget = createCesiumWidget(container);
+
+ const entity = new Entity();
+ entity.position = new ConstantProperty(
+ new Cartesian3(123456, 123456, 123456),
+ );
+
+ widget.trackedEntity = entity;
+ expect(widget.trackedEntity).toBe(entity);
+
+ widget.trackedEntity = undefined;
+ expect(widget.trackedEntity).toBeUndefined();
+ });
+
+ it("raises an event when the tracked entity changes", function () {
+ const widget = createCesiumWidget(container);
+
+ const dataSource = new MockDataSource();
+ widget.dataSources.add(dataSource);
+
+ const entity = new Entity();
+ entity.position = new ConstantPositionProperty(
+ new Cartesian3(123456, 123456, 123456),
+ );
+
+ dataSource.entities.add(entity);
+
+ let myEntity;
+ widget.trackedEntityChanged.addEventListener(function (newValue) {
+ myEntity = newValue;
+ });
+ widget.trackedEntity = entity;
+ expect(myEntity).toBe(entity);
+
+ widget.trackedEntity = undefined;
+ expect(myEntity).toBeUndefined();
+
+ widget.destroy();
+ });
+
+ it("stops tracking when tracked object is removed", function () {
+ widget = createCesiumWidget(container);
+
+ const entity = new Entity();
+ entity.position = new ConstantProperty(
+ new Cartesian3(123456, 123456, 123456),
+ );
+
+ const dataSource = new MockDataSource();
+ dataSource.entities.add(entity);
+
+ widget.dataSources.add(dataSource);
+ widget.trackedEntity = entity;
+
+ expect(widget.trackedEntity).toBe(entity);
+
+ return pollToPromise(function () {
+ widget.render();
+ return Cartesian3.equals(
+ Matrix4.getTranslation(
+ widget.scene.camera.transform,
+ new Cartesian3(),
+ ),
+ entity.position.getValue(),
+ );
+ }).then(function () {
+ dataSource.entities.remove(entity);
+
+ expect(widget.trackedEntity).toBeUndefined();
+ expect(widget.scene.camera.transform).toEqual(Matrix4.IDENTITY);
+
+ dataSource.entities.add(entity);
+ widget.trackedEntity = entity;
+
+ expect(widget.trackedEntity).toBe(entity);
+
+ return pollToPromise(function () {
+ widget.render();
+ widget.render();
+ return Cartesian3.equals(
+ Matrix4.getTranslation(
+ widget.scene.camera.transform,
+ new Cartesian3(),
+ ),
+ entity.position.getValue(),
+ );
+ }).then(function () {
+ widget.dataSources.remove(dataSource);
+
+ expect(widget.trackedEntity).toBeUndefined();
+ expect(widget.scene.camera.transform).toEqual(Matrix4.IDENTITY);
+ });
+ });
+ });
+
+ it("does not crash when tracking an object with a position property whose value is undefined.", function () {
+ widget = createCesiumWidget(container);
+
+ const entity = new Entity();
+ entity.position = new ConstantProperty(undefined);
+ entity.polyline = {
+ positions: [
+ Cartesian3.fromDegrees(0, 0, 0),
+ Cartesian3.fromDegrees(0, 0, 1),
+ ],
+ };
+
+ widget.entities.add(entity);
+ widget.trackedEntity = entity;
+
+ spyOn(widget.scene.renderError, "raiseEvent");
+ return pollToPromise(function () {
+ widget.render();
+ return widget.dataSourceDisplay.update(widget.clock.currentTime);
+ }).then(function () {
+ expect(widget.scene.renderError.raiseEvent).not.toHaveBeenCalled();
+ });
+ });
+
it("throws if no container provided", function () {
expect(function () {
return createCesiumWidget(undefined);
@@ -281,6 +463,202 @@ describe(
}).toThrowDeveloperError();
});
+ it("suspends animation by dataSources if allowed", function () {
+ widget = createCesiumWidget(container);
+
+ let updateResult = true;
+ spyOn(widget.dataSourceDisplay, "update").and.callFake(function () {
+ widget.dataSourceDisplay._ready = updateResult;
+ return updateResult;
+ });
+
+ expect(widget.clock.canAnimate).toBe(true);
+
+ widget.clock.tick();
+ expect(widget.clock.canAnimate).toBe(true);
+
+ updateResult = false;
+ widget.clock.tick();
+ expect(widget.clock.canAnimate).toBe(false);
+
+ widget.clock.canAnimate = true;
+ widget.allowDataSourcesToSuspendAnimation = false;
+ widget.clock.tick();
+ expect(widget.clock.canAnimate).toBe(true);
+ });
+
+ it("sets the clock based on the first data source", function () {
+ const dataSource = new MockDataSource();
+ dataSource.clock = new DataSourceClock();
+ dataSource.clock.startTime = JulianDate.fromIso8601("2013-08-01T18:00Z");
+ dataSource.clock.stopTime = JulianDate.fromIso8601("2013-08-21T02:00Z");
+ dataSource.clock.currentTime =
+ JulianDate.fromIso8601("2013-08-02T00:00Z");
+ dataSource.clock.clockRange = ClockRange.CLAMPED;
+ dataSource.clock.clockStep = ClockStep.TICK_DEPENDENT;
+ dataSource.clock.multiplier = 20.0;
+
+ widget = createCesiumWidget(container);
+ return widget.dataSources.add(dataSource).then(function () {
+ expect(widget.clock.startTime).toEqual(dataSource.clock.startTime);
+ expect(widget.clock.stopTime).toEqual(dataSource.clock.stopTime);
+ expect(widget.clock.currentTime).toEqual(dataSource.clock.currentTime);
+ expect(widget.clock.clockRange).toEqual(dataSource.clock.clockRange);
+ expect(widget.clock.clockStep).toEqual(dataSource.clock.clockStep);
+ expect(widget.clock.multiplier).toEqual(dataSource.clock.multiplier);
+ });
+ });
+
+ it("sets the clock for multiple data sources", function () {
+ const dataSource1 = new MockDataSource();
+ dataSource1.clock = new DataSourceClock();
+ dataSource1.clock.startTime = JulianDate.fromIso8601("2013-08-01T18:00Z");
+ dataSource1.clock.stopTime = JulianDate.fromIso8601("2013-08-21T02:00Z");
+ dataSource1.clock.currentTime =
+ JulianDate.fromIso8601("2013-08-02T00:00Z");
+
+ let dataSource2, dataSource3;
+ widget = createCesiumWidget(container);
+ return widget.dataSources
+ .add(dataSource1)
+ .then(function () {
+ expect(widget.clockTrackedDataSource).toBe(dataSource1);
+ expect(widget.clock.startTime).toEqual(dataSource1.clock.startTime);
+
+ dataSource2 = new MockDataSource();
+ dataSource2.clock = new DataSourceClock();
+ dataSource2.clock.startTime =
+ JulianDate.fromIso8601("2014-08-01T18:00Z");
+ dataSource2.clock.stopTime =
+ JulianDate.fromIso8601("2014-08-21T02:00Z");
+ dataSource2.clock.currentTime =
+ JulianDate.fromIso8601("2014-08-02T00:00Z");
+
+ widget.dataSources.add(dataSource2);
+ })
+ .then(function () {
+ expect(widget.clockTrackedDataSource).toBe(dataSource2);
+ expect(widget.clock.startTime).toEqual(dataSource2.clock.startTime);
+
+ dataSource3 = new MockDataSource();
+ dataSource3.clock = new DataSourceClock();
+ dataSource3.clock.startTime =
+ JulianDate.fromIso8601("2015-08-01T18:00Z");
+ dataSource3.clock.stopTime =
+ JulianDate.fromIso8601("2015-08-21T02:00Z");
+ dataSource3.clock.currentTime =
+ JulianDate.fromIso8601("2015-08-02T00:00Z");
+
+ widget.dataSources.add(dataSource3);
+ })
+ .then(function () {
+ expect(widget.clockTrackedDataSource).toBe(dataSource3);
+ expect(widget.clock.startTime).toEqual(dataSource3.clock.startTime);
+
+ // Removing the last dataSource moves the clock to second-last.
+ widget.dataSources.remove(dataSource3);
+ expect(widget.clockTrackedDataSource).toBe(dataSource2);
+ expect(widget.clock.startTime).toEqual(dataSource2.clock.startTime);
+
+ // Removing the first data source has no effect, because it's not active.
+ widget.dataSources.remove(dataSource1);
+ expect(widget.clockTrackedDataSource).toBe(dataSource2);
+ expect(widget.clock.startTime).toEqual(dataSource2.clock.startTime);
+ });
+ });
+
+ it("updates the clock when the data source changes", function () {
+ const dataSource = new MockDataSource();
+ dataSource.clock = new DataSourceClock();
+ dataSource.clock.startTime = JulianDate.fromIso8601("2013-08-01T18:00Z");
+ dataSource.clock.stopTime = JulianDate.fromIso8601("2013-08-21T02:00Z");
+ dataSource.clock.currentTime =
+ JulianDate.fromIso8601("2013-08-02T00:00Z");
+ dataSource.clock.clockRange = ClockRange.CLAMPED;
+ dataSource.clock.clockStep = ClockStep.TICK_DEPENDENT;
+ dataSource.clock.multiplier = 20.0;
+
+ widget = createCesiumWidget(container);
+ return widget.dataSources.add(dataSource).then(function () {
+ dataSource.clock.startTime =
+ JulianDate.fromIso8601("2014-08-01T18:00Z");
+ dataSource.clock.stopTime = JulianDate.fromIso8601("2014-08-21T02:00Z");
+ dataSource.clock.currentTime =
+ JulianDate.fromIso8601("2014-08-02T00:00Z");
+ dataSource.clock.clockRange = ClockRange.UNBOUNDED;
+ dataSource.clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
+ dataSource.clock.multiplier = 10.0;
+
+ dataSource.changedEvent.raiseEvent(dataSource);
+
+ expect(widget.clock.startTime).toEqual(dataSource.clock.startTime);
+ expect(widget.clock.stopTime).toEqual(dataSource.clock.stopTime);
+ expect(widget.clock.currentTime).toEqual(dataSource.clock.currentTime);
+ expect(widget.clock.clockRange).toEqual(dataSource.clock.clockRange);
+ expect(widget.clock.clockStep).toEqual(dataSource.clock.clockStep);
+ expect(widget.clock.multiplier).toEqual(dataSource.clock.multiplier);
+
+ dataSource.clock.clockStep = ClockStep.SYSTEM_CLOCK;
+ dataSource.clock.multiplier = 1.0;
+
+ dataSource.changedEvent.raiseEvent(dataSource);
+
+ expect(widget.clock.clockStep).toEqual(dataSource.clock.clockStep);
+ });
+ });
+
+ it("can manually control the clock tracking", function () {
+ const dataSource1 = new MockDataSource();
+ dataSource1.clock = new DataSourceClock();
+ dataSource1.clock.startTime = JulianDate.fromIso8601("2013-08-01T18:00Z");
+ dataSource1.clock.stopTime = JulianDate.fromIso8601("2013-08-21T02:00Z");
+ dataSource1.clock.currentTime =
+ JulianDate.fromIso8601("2013-08-02T00:00Z");
+
+ widget = createCesiumWidget(container, {
+ automaticallyTrackDataSourceClocks: false,
+ });
+
+ let dataSource2;
+ return widget.dataSources
+ .add(dataSource1)
+ .then(function () {
+ // Because of the above widget option, data sources are not automatically
+ // selected for clock tracking.
+ expect(widget.clockTrackedDataSource).not.toBeDefined();
+ // The mock data source time is in the past, so will not be the default time.
+ expect(widget.clock.startTime).not.toEqual(
+ dataSource1.clock.startTime,
+ );
+
+ // Manually set the first data source as the tracked data source.
+ widget.clockTrackedDataSource = dataSource1;
+ expect(widget.clockTrackedDataSource).toBe(dataSource1);
+ expect(widget.clock.startTime).toEqual(dataSource1.clock.startTime);
+
+ dataSource2 = new MockDataSource();
+ dataSource2.clock = new DataSourceClock();
+ dataSource2.clock.startTime =
+ JulianDate.fromIso8601("2014-08-01T18:00Z");
+ dataSource2.clock.stopTime =
+ JulianDate.fromIso8601("2014-08-21T02:00Z");
+ dataSource2.clock.currentTime =
+ JulianDate.fromIso8601("2014-08-02T00:00Z");
+
+ // Adding a second data source in manual mode still leaves the first one tracked.
+ widget.dataSources.add(dataSource2);
+ })
+ .then(function () {
+ expect(widget.clockTrackedDataSource).toBe(dataSource1);
+ expect(widget.clock.startTime).toEqual(dataSource1.clock.startTime);
+
+ // Removing the tracked data source in manual mode turns off tracking, even
+ // if other data sources remain available for tracking.
+ widget.dataSources.remove(dataSource1);
+ expect(widget.clockTrackedDataSource).not.toBeDefined();
+ });
+ });
+
it("can set resolutionScale", function () {
widget = createCesiumWidget(container);
widget.resolutionScale = 0.5;
@@ -419,6 +797,889 @@ describe(
).toBeNull();
});
});
+
+ it("zoomTo throws if target is not defined", function () {
+ widget = createCesiumWidget(container);
+
+ expect(function () {
+ widget.zoomTo();
+ }).toThrowDeveloperError();
+ });
+
+ it("zoomTo zooms to Cesium3DTileset with default offset when offset not defined", async function () {
+ widget = createCesiumWidget(container);
+
+ const path =
+ "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json";
+ const tileset = await Cesium3DTileset.fromUrl(path);
+
+ const expectedBoundingSphere = tileset.boundingSphere;
+ const expectedOffset = new HeadingPitchRange(
+ 0.0,
+ -0.5,
+ expectedBoundingSphere.radius,
+ );
+
+ let wasCompleted = false;
+ spyOn(widget.camera, "viewBoundingSphere").and.callFake(
+ function (boundingSphere, offset) {
+ expect(boundingSphere).toEqual(expectedBoundingSphere);
+ expect(offset).toEqual(expectedOffset);
+ wasCompleted = true;
+ },
+ );
+ const promise = widget.zoomTo(tileset);
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+
+ it("zoomTo zooms to Cesium3DTileset with offset", async function () {
+ widget = createCesiumWidget(container);
+
+ const path =
+ "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json";
+ const tileset = await Cesium3DTileset.fromUrl(path);
+
+ const expectedBoundingSphere = tileset.boundingSphere;
+ const expectedOffset = new HeadingPitchRange(
+ 0.4,
+ 1.2,
+ 4.0 * expectedBoundingSphere.radius,
+ );
+
+ const promise = widget.zoomTo(tileset, expectedOffset);
+ let wasCompleted = false;
+ spyOn(widget.camera, "viewBoundingSphere").and.callFake(
+ function (boundingSphere, offset) {
+ expect(boundingSphere).toEqual(expectedBoundingSphere);
+ expect(offset).toEqual(expectedOffset);
+ wasCompleted = true;
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+
+ async function loadTimeDynamicPointCloud(widget) {
+ const scene = widget.scene;
+ const clock = widget.clock;
+
+ const uri =
+ "./Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/0.pnts";
+ const dates = ["2018-07-19T15:18:00Z", "2018-07-19T15:18:00.5Z"];
+
+ function dataCallback() {
+ return {
+ uri: uri,
+ };
+ }
+
+ const timeIntervalCollection =
+ TimeIntervalCollection.fromIso8601DateArray({
+ iso8601Dates: dates,
+ dataCallback: dataCallback,
+ });
+
+ const pointCloud = new TimeDynamicPointCloud({
+ intervals: timeIntervalCollection,
+ clock: clock,
+ });
+
+ const start = JulianDate.fromIso8601(dates[0]);
+
+ clock.startTime = start;
+ clock.currentTime = start;
+ clock.multiplier = 0.0;
+
+ scene.primitives.add(pointCloud);
+
+ await pollToPromise(function () {
+ scene.render();
+ return defined(pointCloud.boundingSphere);
+ });
+
+ return pointCloud;
+ }
+
+ it("zoomTo zooms to TimeDynamicPointCloud with default offset when offset not defined", function () {
+ widget = createCesiumWidget(container);
+ return loadTimeDynamicPointCloud(widget).then(function (pointCloud) {
+ const expectedBoundingSphere = pointCloud.boundingSphere;
+ const expectedOffset = new HeadingPitchRange(
+ 0.0,
+ -0.5,
+ expectedBoundingSphere.radius,
+ );
+
+ const promise = widget.zoomTo(pointCloud);
+ let wasCompleted = false;
+ spyOn(widget.camera, "viewBoundingSphere").and.callFake(
+ function (boundingSphere, offset) {
+ expect(boundingSphere).toEqual(expectedBoundingSphere);
+ expect(offset).toEqual(expectedOffset);
+ wasCompleted = true;
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ widget.scene.primitives.remove(pointCloud);
+ });
+ });
+ });
+
+ it("zoomTo zooms to TimeDynamicPointCloud with offset", function () {
+ widget = createCesiumWidget(container);
+ return loadTimeDynamicPointCloud(widget).then(function (pointCloud) {
+ const expectedBoundingSphere = pointCloud.boundingSphere;
+ const expectedOffset = new HeadingPitchRange(
+ 0.4,
+ 1.2,
+ 4.0 * expectedBoundingSphere.radius,
+ );
+
+ const promise = widget.zoomTo(pointCloud, expectedOffset);
+ let wasCompleted = false;
+ spyOn(widget.camera, "viewBoundingSphere").and.callFake(
+ function (boundingSphere, offset) {
+ expect(boundingSphere).toEqual(expectedBoundingSphere);
+ expect(offset).toEqual(expectedOffset);
+ wasCompleted = true;
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ widget.scene.primitives.remove(pointCloud);
+ });
+ });
+ });
+
+ async function loadVoxelPrimitive(widget) {
+ const voxelPrimitive = new VoxelPrimitive({
+ provider: await Cesium3DTilesVoxelProvider.fromUrl(
+ "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json",
+ ),
+ });
+ widget.scene.primitives.add(voxelPrimitive);
+ return voxelPrimitive;
+ }
+
+ it("zoomTo zooms to VoxelPrimitive with default offset when offset not defined", function () {
+ widget = createCesiumWidget(container);
+
+ return loadVoxelPrimitive(widget).then(function (voxelPrimitive) {
+ const expectedBoundingSphere = voxelPrimitive.boundingSphere;
+ const expectedOffset = new HeadingPitchRange(
+ 0.0,
+ -0.5,
+ expectedBoundingSphere.radius,
+ );
+
+ const promise = widget.zoomTo(voxelPrimitive);
+ let wasCompleted = false;
+ spyOn(widget.camera, "viewBoundingSphere").and.callFake(
+ function (boundingSphere, offset) {
+ expect(boundingSphere).toEqual(expectedBoundingSphere);
+ expect(offset).toEqual(expectedOffset);
+ wasCompleted = true;
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
+ it("zoomTo zooms to VoxelPrimitive with offset", function () {
+ widget = createCesiumWidget(container);
+
+ return loadVoxelPrimitive(widget).then(function (voxelPrimitive) {
+ const expectedBoundingSphere = voxelPrimitive.boundingSphere;
+ const expectedOffset = new HeadingPitchRange(
+ 0.4,
+ 1.2,
+ 4.0 * expectedBoundingSphere.radius,
+ );
+
+ const promise = widget.zoomTo(voxelPrimitive, expectedOffset);
+ let wasCompleted = false;
+ spyOn(widget.camera, "viewBoundingSphere").and.callFake(
+ function (boundingSphere, offset) {
+ expect(boundingSphere).toEqual(expectedBoundingSphere);
+ expect(offset).toEqual(expectedOffset);
+ wasCompleted = true;
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
+ it("zoomTo zooms to entity with undefined offset when offset not defined", function () {
+ widget = createCesiumWidget(container);
+ widget.entities.add({
+ name: "Blue box",
+ position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+ box: {
+ dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
+ material: Color.BLUE,
+ },
+ });
+
+ const entities = widget.entities;
+
+ const promise = widget.zoomTo(entities);
+ let wasCompleted = false;
+ spyOn(widget.dataSourceDisplay, "getBoundingSphere").and.callFake(
+ function () {
+ return new BoundingSphere();
+ },
+ );
+
+ spyOn(widget.camera, "viewBoundingSphere").and.callFake(
+ function (boundingSphere, offset) {
+ expect(boundingSphere).toBeDefined();
+ // expect offset to be undefined - doesn't use default bc of how zoomTo for entities is set up
+ expect(offset).toBeUndefined();
+ wasCompleted = true;
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+
+ it("zoomTo zooms to entity with offset", function () {
+ widget = createCesiumWidget(container);
+ widget.entities.add({
+ name: "Blue box",
+ position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+ box: {
+ dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
+ material: Color.BLUE,
+ },
+ });
+
+ const entities = widget.entities;
+ // fake temp offset
+ const expectedOffset = new HeadingPitchRange(3.0, 0.2, 2.3);
+
+ const promise = widget.zoomTo(entities, expectedOffset);
+ let wasCompleted = false;
+ spyOn(widget.dataSourceDisplay, "getBoundingSphere").and.callFake(
+ function () {
+ return new BoundingSphere();
+ },
+ );
+ spyOn(widget.camera, "viewBoundingSphere").and.callFake(
+ function (boundingSphere, offset) {
+ expect(expectedOffset).toEqual(offset);
+ wasCompleted = true;
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+
+ it("zoomTo zooms to entity when globe is disabled", async function () {
+ // Create widget with globe disabled
+ const widget = createCesiumWidget(container, {
+ globe: false,
+ infoBox: false,
+ selectionIndicator: false,
+ shadows: true,
+ shouldAnimate: true,
+ });
+
+ // Create position variable
+ const position = Cartesian3.fromDegrees(-123.0744619, 44.0503706, 1000.0);
+
+ // Add entity to widget
+ const entity = widget.entities.add({
+ position: position,
+ model: {
+ uri: "../SampleData/models/CesiumAir/Cesium_Air.glb",
+ },
+ });
+
+ await widget.zoomTo(entity);
+
+ // Verify that no errors occurred
+ expect(widget.scene).toBeDefined();
+ expect(widget.scene.errorEvent).toBeUndefined();
+ });
+
+ it("flyTo throws if target is not defined", function () {
+ widget = createCesiumWidget(container);
+
+ expect(function () {
+ widget.flyTo();
+ }).toThrowDeveloperError();
+ });
+
+ it("flyTo flies to Cesium3DTileset with default offset when options not defined", async function () {
+ widget = createCesiumWidget(container);
+
+ const path =
+ "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json";
+ const tileset = await Cesium3DTileset.fromUrl(path);
+
+ const promise = widget.flyTo(tileset);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.offset).toBeDefined();
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+
+ it("flyTo flies to Cesium3DTileset with default offset when offset not defined", async function () {
+ widget = createCesiumWidget(container);
+
+ const path =
+ "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json";
+ const tileset = await Cesium3DTileset.fromUrl(path);
+
+ const options = {};
+
+ const promise = widget.flyTo(tileset, options);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.offset).toBeDefined();
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+
+ it("flyTo flies to Cesium3DTileset when options are defined", async function () {
+ widget = createCesiumWidget(container);
+
+ const path =
+ "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json";
+ const tileset = await Cesium3DTileset.fromUrl(path);
+
+ const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
+ const options = {
+ offset: offsetVal,
+ duration: 3.0,
+ maximumHeight: 5.0,
+ };
+
+ const promise = widget.flyTo(tileset, options);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.duration).toBeDefined();
+ expect(options.maximumHeight).toBeDefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+
+ it("flyTo flies to TimeDynamicPointCloud with default offset when options not defined", function () {
+ widget = createCesiumWidget(container);
+ return loadTimeDynamicPointCloud(widget).then(function (pointCloud) {
+ const promise = widget.flyTo(pointCloud);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.offset).toBeDefined();
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ widget.scene.primitives.remove(pointCloud);
+ });
+ });
+ });
+
+ it("flyTo flies to TimeDynamicPointCloud with default offset when offset not defined", function () {
+ widget = createCesiumWidget(container);
+ return loadTimeDynamicPointCloud(widget).then(function (pointCloud) {
+ const options = {};
+ const promise = widget.flyTo(pointCloud, options);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.offset).toBeDefined();
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ widget.scene.primitives.remove(pointCloud);
+ });
+ });
+ });
+
+ it("flyTo flies to TimeDynamicPointCloud when options are defined", function () {
+ widget = createCesiumWidget(container);
+ return loadTimeDynamicPointCloud(widget).then(function (pointCloud) {
+ const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
+ const options = {
+ offset: offsetVal,
+ duration: 3.0,
+ maximumHeight: 5.0,
+ };
+ const promise = widget.flyTo(pointCloud, options);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.duration).toBeDefined();
+ expect(options.maximumHeight).toBeDefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ widget.scene.primitives.remove(pointCloud);
+ });
+ });
+ });
+
+ it("flyTo flies to VoxelPrimitive with default offset when options not defined", function () {
+ widget = createCesiumWidget(container);
+
+ return loadVoxelPrimitive(widget).then(function (voxelPrimitive) {
+ const promise = widget.flyTo(voxelPrimitive);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.offset).toBeDefined();
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
+ it("flyTo flies to VoxelPrimitive with default offset when offset not defined", function () {
+ widget = createCesiumWidget(container);
+ const options = {};
+
+ return loadVoxelPrimitive(widget).then(function (voxelPrimitive) {
+ const promise = widget.flyTo(voxelPrimitive, options);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.offset).toBeDefined();
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
+ it("flyTo flies to VoxelPrimitive when options are defined", function () {
+ widget = createCesiumWidget(container);
+
+ // load tileset to test
+ return loadVoxelPrimitive(widget).then(function (voxelPrimitive) {
+ const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
+ const options = {
+ offset: offsetVal,
+ duration: 3.0,
+ maximumHeight: 5.0,
+ };
+
+ const promise = widget.flyTo(voxelPrimitive, options);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.duration).toBeDefined();
+ expect(options.maximumHeight).toBeDefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
+ it("flyTo flies to entity with default offset when options not defined", function () {
+ widget = createCesiumWidget(container);
+
+ widget.entities.add({
+ name: "Blue box",
+ position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+ box: {
+ dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
+ material: Color.BLUE,
+ },
+ });
+
+ const entities = widget.entities;
+ const promise = widget.flyTo(entities);
+ let wasCompleted = false;
+ spyOn(widget.dataSourceDisplay, "getBoundingSphere").and.callFake(
+ function () {
+ return new BoundingSphere();
+ },
+ );
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+
+ it("flyTo flies to imagery layer with default offset when options are not defined", async function () {
+ widget = createCesiumWidget(container);
+
+ const imageryLayer = new ImageryLayer(testProvider);
+
+ const promise = widget.flyTo(imageryLayer, {
+ duration: 0,
+ });
+
+ widget._postRender();
+
+ await expectAsync(promise).toBeResolved();
+ });
+
+ it("flyTo flies to VoxelPrimitive with default offset when options not defined", function () {
+ widget = createCesiumWidget(container);
+
+ return loadVoxelPrimitive(widget).then(function (voxelPrimitive) {
+ const promise = widget.flyTo(voxelPrimitive);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.offset).toBeDefined();
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
+ it("flyTo flies to VoxelPrimitive with default offset when offset not defined", function () {
+ widget = createCesiumWidget(container);
+ const options = {};
+
+ return loadVoxelPrimitive(widget).then(function (voxelPrimitive) {
+ const promise = widget.flyTo(voxelPrimitive, options);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.offset).toBeDefined();
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
+ it("flyTo flies to VoxelPrimitive when options are defined", function () {
+ widget = createCesiumWidget(container);
+
+ // load tileset to test
+ return loadVoxelPrimitive(widget).then(function (voxelPrimitive) {
+ const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
+ const options = {
+ offset: offsetVal,
+ duration: 3.0,
+ maximumHeight: 5.0,
+ };
+
+ const promise = widget.flyTo(voxelPrimitive, options);
+ let wasCompleted = false;
+
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.duration).toBeDefined();
+ expect(options.maximumHeight).toBeDefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
+ it("flyTo flys to entity with default offset when offset not defined", function () {
+ widget = createCesiumWidget(container);
+
+ widget.entities.add({
+ name: "Blue box",
+ position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+ box: {
+ dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
+ material: Color.BLUE,
+ },
+ });
+
+ const entities = widget.entities;
+ const options = {};
+
+ const promise = widget.flyTo(entities, options);
+ let wasCompleted = false;
+ spyOn(widget.dataSourceDisplay, "getBoundingSphere").and.callFake(
+ function () {
+ return new BoundingSphere();
+ },
+ );
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+
+ it("flyTo flies to entity when options are defined", function () {
+ widget = createCesiumWidget(container);
+
+ widget.entities.add({
+ name: "Blue box",
+ position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+ box: {
+ dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
+ material: Color.BLUE,
+ },
+ });
+
+ const entities = widget.entities;
+ const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
+ const options = {
+ offset: offsetVal,
+ duration: 3.0,
+ maximumHeight: 5.0,
+ };
+
+ const promise = widget.flyTo(entities, options);
+ let wasCompleted = false;
+ spyOn(widget.dataSourceDisplay, "getBoundingSphere").and.callFake(
+ function () {
+ return new BoundingSphere();
+ },
+ );
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.duration).toBeDefined();
+ expect(options.maximumHeight).toBeDefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+
+ it("flyTo flies to entity when offset is defined but other options for flyTo are not", function () {
+ widget = createCesiumWidget(container);
+
+ widget.entities.add({
+ name: "Blue box",
+ position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
+ box: {
+ dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
+ material: Color.BLUE,
+ },
+ });
+
+ const entities = widget.entities;
+ const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
+ const options = {
+ offset: offsetVal,
+ };
+
+ const promise = widget.flyTo(entities, options);
+ let wasCompleted = false;
+ spyOn(widget.dataSourceDisplay, "getBoundingSphere").and.callFake(
+ function () {
+ return new BoundingSphere();
+ },
+ );
+ spyOn(widget.camera, "flyToBoundingSphere").and.callFake(
+ function (target, options) {
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ },
+ );
+
+ widget._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+
+ it("removes data source listeners when destroyed", function () {
+ widget = createCesiumWidget(container);
+
+ //one data source that is added before mixing in
+ const preMixinDataSource = new MockDataSource();
+ //one data source that is added after mixing in
+ const postMixinDataSource = new MockDataSource();
+ return widget.dataSources
+ .add(preMixinDataSource)
+ .then(function () {
+ widget.dataSources.add(postMixinDataSource);
+ })
+ .then(function () {
+ const preMixinListenerCount =
+ preMixinDataSource.entities.collectionChanged._listeners.length;
+ const postMixinListenerCount =
+ postMixinDataSource.entities.collectionChanged._listeners.length;
+
+ widget = widget.destroy();
+
+ expect(
+ preMixinDataSource.entities.collectionChanged._listeners.length,
+ ).not.toEqual(preMixinListenerCount);
+ expect(
+ postMixinDataSource.entities.collectionChanged._listeners.length,
+ ).not.toEqual(postMixinListenerCount);
+ });
+ });
},
"WebGL",
);
diff --git a/packages/widgets/Source/Viewer/Viewer.js b/packages/widgets/Source/Viewer/Viewer.js
index 3479e543ad58..3b6ec54ba847 100644
--- a/packages/widgets/Source/Viewer/Viewer.js
+++ b/packages/widgets/Source/Viewer/Viewer.js
@@ -2,36 +2,23 @@ import {
BoundingSphere,
BoundingSphereState,
Cartesian3,
- Cartographic,
CesiumWidget,
Cesium3DTileFeature,
- Cesium3DTileset,
Clock,
- computeFlyToLocationForRectangle,
ConstantPositionProperty,
- DataSourceCollection,
- DataSourceDisplay,
defaultValue,
defined,
destroyObject,
DeveloperError,
Entity,
- EntityView,
Event,
EventHelper,
getElement,
- HeadingPitchRange,
- ImageryLayer,
JulianDate,
Math as CesiumMath,
- Matrix4,
Property,
- SceneMode,
ScreenSpaceEventType,
- TimeDynamicPointCloud,
- VoxelPrimitive,
} from "@cesium/engine";
-import knockout from "../ThirdParty/knockout.js";
import Animation from "../Animation/Animation.js";
import AnimationViewModel from "../Animation/AnimationViewModel.js";
import BaseLayerPicker from "../BaseLayerPicker/BaseLayerPicker.js";
@@ -138,26 +125,23 @@ function pickEntity(viewer, e) {
const scratchStopTime = new JulianDate();
-function trackDataSourceClock(timeline, clock, dataSource) {
+function linkTimelineToDataSourceClock(timeline, dataSource) {
if (defined(dataSource)) {
const dataSourceClock = dataSource.clock;
- if (defined(dataSourceClock)) {
- dataSourceClock.getValue(clock);
- if (defined(timeline)) {
- const startTime = dataSourceClock.startTime;
- let stopTime = dataSourceClock.stopTime;
- // When the start and stop times are equal, set the timeline to the shortest interval
- // starting at the start time. This prevents an invalid timeline configuration.
- if (JulianDate.equals(startTime, stopTime)) {
- stopTime = JulianDate.addSeconds(
- startTime,
- CesiumMath.EPSILON2,
- scratchStopTime,
- );
- }
- timeline.updateFromClock();
- timeline.zoomTo(startTime, stopTime);
+ if (defined(dataSourceClock) && defined(timeline)) {
+ const startTime = dataSourceClock.startTime;
+ let stopTime = dataSourceClock.stopTime;
+ // When the start and stop times are equal, set the timeline to the shortest interval
+ // starting at the start time. This prevents an invalid timeline configuration.
+ if (JulianDate.equals(startTime, stopTime)) {
+ stopTime = JulianDate.addSeconds(
+ startTime,
+ CesiumMath.EPSILON2,
+ scratchStopTime,
+ );
}
+ timeline.updateFromClock();
+ timeline.zoomTo(startTime, stopTime);
}
}
}
@@ -477,10 +461,6 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
destroyClockViewModel = true;
}
- if (defined(options.shouldAnimate)) {
- clock.shouldAnimate = options.shouldAnimate;
- }
-
// Cesium widget
const cesiumWidget = new CesiumWidget(cesiumWidgetContainer, {
baseLayer:
@@ -491,6 +471,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
? false
: undefined,
clock: clock,
+ shouldAnimate: options.shouldAnimate,
skyBox: options.skyBox,
skyAtmosphere: options.skyAtmosphere,
sceneMode: options.sceneMode,
@@ -498,6 +479,8 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
mapProjection: options.mapProjection,
globe: options.globe,
orderIndependentTranslucency: options.orderIndependentTranslucency,
+ automaticallyTrackDataSourceClocks:
+ options.automaticallyTrackDataSourceClocks,
contextOptions: options.contextOptions,
useDefaultRenderLoop: options.useDefaultRenderLoop,
targetFrameRate: options.targetFrameRate,
@@ -507,6 +490,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
? options.creditContainer
: bottomContainer,
creditViewport: options.creditViewport,
+ dataSources: options.dataSources,
scene3DOnly: scene3DOnly,
shadows: options.shadows,
terrainShadows: options.terrainShadows,
@@ -518,24 +502,11 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
msaaSamples: options.msaaSamples,
});
- let dataSourceCollection = options.dataSources;
- let destroyDataSourceCollection = false;
- if (!defined(dataSourceCollection)) {
- dataSourceCollection = new DataSourceCollection();
- destroyDataSourceCollection = true;
- }
-
const scene = cesiumWidget.scene;
- const dataSourceDisplay = new DataSourceDisplay({
- scene: scene,
- dataSourceCollection: dataSourceCollection,
- });
-
const eventHelper = new EventHelper();
eventHelper.add(clock.onTick, Viewer.prototype._onTick, this);
- eventHelper.add(scene.morphStart, Viewer.prototype._clearTrackedObject, this);
// Selection Indicator
let selectionIndicator;
@@ -841,19 +812,12 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
this._vrSubscription = vrSubscription;
this._vrModeSubscription = vrModeSubscription;
this._dataSourceChangedListeners = {};
- this._automaticallyTrackDataSourceClocks = defaultValue(
- options.automaticallyTrackDataSourceClocks,
- true,
- );
this._container = container;
this._bottomContainer = bottomContainer;
this._element = viewerContainer;
this._cesiumWidget = cesiumWidget;
this._selectionIndicator = selectionIndicator;
this._infoBox = infoBox;
- this._dataSourceCollection = dataSourceCollection;
- this._destroyDataSourceCollection = destroyDataSourceCollection;
- this._dataSourceDisplay = dataSourceDisplay;
this._clockViewModel = clockViewModel;
this._destroyClockViewModel = destroyClockViewModel;
this._toolbar = toolbar;
@@ -870,25 +834,12 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
this._eventHelper = eventHelper;
this._lastWidth = 0;
this._lastHeight = 0;
- this._allowDataSourcesToSuspendAnimation = true;
- this._entityView = undefined;
this._enableInfoOrSelection = defined(infoBox) || defined(selectionIndicator);
- this._clockTrackedDataSource = undefined;
- this._trackedEntity = undefined;
- this._needTrackedEntityUpdate = false;
this._selectedEntity = undefined;
- this._zoomIsFlight = false;
- this._zoomTarget = undefined;
- this._zoomPromise = undefined;
- this._zoomOptions = undefined;
this._selectedEntityChanged = new Event();
- this._trackedEntityChanged = new Event();
- knockout.track(this, [
- "_trackedEntity",
- "_selectedEntity",
- "_clockTrackedDataSource",
- ]);
+ const dataSourceCollection = this._cesiumWidget.dataSources;
+ const dataSourceDisplay = this._cesiumWidget.dataSourceDisplay;
//Listen to data source events in order to track clock changes.
eventHelper.add(
@@ -904,7 +855,6 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
// Prior to each render, check if anything needs to be resized.
eventHelper.add(scene.postUpdate, Viewer.prototype.resize, this);
- eventHelper.add(scene.postRender, Viewer.prototype._postRender, this);
// We need to subscribe to the data sources and collections so that we can clear the
// tracked object when it is removed from the scene.
@@ -956,6 +906,10 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to
pickAndTrackObject,
ScreenSpaceEventType.LEFT_DOUBLE_CLICK,
);
+
+ // This allows to update the Viewer's _clockViewModel instead of the CesiumWidget's _clock
+ // when CesiumWidget is created from the Viewer.
+ cesiumWidget._canAnimateUpdateCallback = this._updateCanAnimate(this);
}
Object.defineProperties(Viewer.prototype, {
@@ -1160,7 +1114,7 @@ Object.defineProperties(Viewer.prototype, {
*/
dataSourceDisplay: {
get: function () {
- return this._dataSourceDisplay;
+ return this._cesiumWidget.dataSourceDisplay;
},
},
@@ -1173,7 +1127,7 @@ Object.defineProperties(Viewer.prototype, {
*/
entities: {
get: function () {
- return this._dataSourceDisplay.defaultDataSource.entities;
+ return this._cesiumWidget.entities;
},
},
@@ -1185,7 +1139,7 @@ Object.defineProperties(Viewer.prototype, {
*/
dataSources: {
get: function () {
- return this._dataSourceCollection;
+ return this._cesiumWidget.dataSources;
},
},
@@ -1452,10 +1406,10 @@ Object.defineProperties(Viewer.prototype, {
*/
allowDataSourcesToSuspendAnimation: {
get: function () {
- return this._allowDataSourcesToSuspendAnimation;
+ return this._cesiumWidget.allowDataSourcesToSuspendAnimation;
},
set: function (value) {
- this._allowDataSourcesToSuspendAnimation = value;
+ this._cesiumWidget.allowDataSourcesToSuspendAnimation = value;
},
},
@@ -1466,46 +1420,10 @@ Object.defineProperties(Viewer.prototype, {
*/
trackedEntity: {
get: function () {
- return this._trackedEntity;
+ return this._cesiumWidget.trackedEntity;
},
set: function (value) {
- if (this._trackedEntity !== value) {
- this._trackedEntity = value;
-
- //Cancel any pending zoom
- cancelZoom(this);
-
- const scene = this.scene;
- const sceneMode = scene.mode;
-
- //Stop tracking
- if (!defined(value) || !defined(value.position)) {
- this._needTrackedEntityUpdate = false;
- if (
- sceneMode === SceneMode.COLUMBUS_VIEW ||
- sceneMode === SceneMode.SCENE2D
- ) {
- scene.screenSpaceCameraController.enableTranslate = true;
- }
-
- if (
- sceneMode === SceneMode.COLUMBUS_VIEW ||
- sceneMode === SceneMode.SCENE3D
- ) {
- scene.screenSpaceCameraController.enableTilt = true;
- }
-
- this._entityView = undefined;
- this.camera.lookAtTransform(Matrix4.IDENTITY);
- } else {
- //We can't start tracking immediately, so we set a flag and start tracking
- //when the bounding sphere is ready (most likely next frame).
- this._needTrackedEntityUpdate = true;
- }
-
- this._trackedEntityChanged.raiseEvent(value);
- this.scene.requestRender();
- }
+ this._cesiumWidget.trackedEntity = value;
},
},
/**
@@ -1558,7 +1476,7 @@ Object.defineProperties(Viewer.prototype, {
*/
trackedEntityChanged: {
get: function () {
- return this._trackedEntityChanged;
+ return this._cesiumWidget.trackedEntityChanged;
},
},
/**
@@ -1568,12 +1486,12 @@ Object.defineProperties(Viewer.prototype, {
*/
clockTrackedDataSource: {
get: function () {
- return this._clockTrackedDataSource;
+ return this._cesiumWidget.clockTrackedDataSource;
},
set: function (value) {
- if (this._clockTrackedDataSource !== value) {
- this._clockTrackedDataSource = value;
- trackDataSourceClock(this._timeline, this.clock, value);
+ if (this._cesiumWidget.clockTrackedDataSource !== value) {
+ this._cesiumWidget.clockTrackedDataSource = value;
+ linkTimelineToDataSourceClock(this._timeline, value);
}
},
},
@@ -1738,7 +1656,6 @@ Viewer.prototype.isDestroyed = function () {
* removing the widget from layout.
*/
Viewer.prototype.destroy = function () {
- let i;
if (
defined(this.screenSpaceEventHandler) &&
!this.screenSpaceEventHandler.isDestroyed()
@@ -1751,14 +1668,6 @@ Viewer.prototype.destroy = function () {
);
}
- // Unsubscribe from data sources
- const dataSources = this.dataSources;
- const dataSourceLength = dataSources.length;
- for (i = 0; i < dataSourceLength; i++) {
- this._dataSourceRemoved(dataSources, dataSources.get(i));
- }
- this._dataSourceRemoved(undefined, this._dataSourceDisplay.defaultDataSource);
-
this._container.removeChild(this._element);
this._element.removeChild(this._toolbar);
@@ -1825,13 +1734,8 @@ Viewer.prototype.destroy = function () {
if (this._destroyClockViewModel) {
this._clockViewModel = this._clockViewModel.destroy();
}
- this._dataSourceDisplay = this._dataSourceDisplay.destroy();
this._cesiumWidget = this._cesiumWidget.destroy();
- if (this._destroyDataSourceCollection) {
- this._dataSourceCollection = this._dataSourceCollection.destroy();
- }
-
return destroyObject(this);
};
@@ -1862,14 +1766,6 @@ Viewer.prototype._dataSourceRemoved = function (
this,
);
- if (defined(this.trackedEntity)) {
- if (
- entityCollection.getById(this.trackedEntity.id) === this.trackedEntity
- ) {
- this.trackedEntity = undefined;
- }
- }
-
if (defined(this.selectedEntity)) {
if (
entityCollection.getById(this.selectedEntity.id) === this.selectedEntity
@@ -1879,30 +1775,21 @@ Viewer.prototype._dataSourceRemoved = function (
}
};
+/**
+ * @private
+ */
+Viewer.prototype._updateCanAnimate = function (that) {
+ return function (isUpdated) {
+ that._clockViewModel.canAnimate = isUpdated;
+ };
+};
+
/**
* @private
*/
Viewer.prototype._onTick = function (clock) {
const time = clock.currentTime;
- const isUpdated = this._dataSourceDisplay.update(time);
- if (this._allowDataSourcesToSuspendAnimation) {
- this._clockViewModel.canAnimate = isUpdated;
- }
-
- const entityView = this._entityView;
- if (defined(entityView)) {
- const trackedEntity = this._trackedEntity;
- const trackedState = this._dataSourceDisplay.getBoundingSphere(
- trackedEntity,
- true,
- boundingSphereScratch,
- );
- if (trackedState === BoundingSphereState.DONE) {
- entityView.update(time, boundingSphereScratch);
- }
- }
-
let position;
let enableCamera = false;
const selectedEntity = this.selectedEntity;
@@ -1913,7 +1800,7 @@ Viewer.prototype._onTick = function (clock) {
selectedEntity.isShowing &&
selectedEntity.isAvailable(time)
) {
- const state = this._dataSourceDisplay.getBoundingSphere(
+ const state = this._cesiumWidget.dataSourceDisplay.getBoundingSphere(
selectedEntity,
true,
boundingSphereScratch,
@@ -1975,9 +1862,6 @@ Viewer.prototype._onEntityCollectionChanged = function (
const length = removed.length;
for (let i = 0; i < length; i++) {
const removedObject = removed[i];
- if (this.trackedEntity === removedObject) {
- this.trackedEntity = undefined;
- }
if (this.selectedEntity === removedObject) {
this.selectedEntity = undefined;
}
@@ -2031,7 +1915,7 @@ Viewer.prototype._clearObjects = function () {
*/
Viewer.prototype._onDataSourceChanged = function (dataSource) {
if (this.clockTrackedDataSource === dataSource) {
- trackDataSourceClock(this.timeline, this.clock, dataSource);
+ linkTimelineToDataSourceClock(this.timeline, dataSource);
}
};
@@ -2042,9 +1926,6 @@ Viewer.prototype._onDataSourceAdded = function (
dataSourceCollection,
dataSource,
) {
- if (this._automaticallyTrackDataSourceClocks) {
- this.clockTrackedDataSource = dataSource;
- }
const id = dataSource.entities.id;
const removalFunc = this._eventHelper.add(
dataSource.changedEvent,
@@ -2061,20 +1942,9 @@ Viewer.prototype._onDataSourceRemoved = function (
dataSourceCollection,
dataSource,
) {
- const resetClock = this.clockTrackedDataSource === dataSource;
const id = dataSource.entities.id;
this._dataSourceChangedListeners[id]();
this._dataSourceChangedListeners[id] = undefined;
- if (resetClock) {
- const numDataSources = dataSourceCollection.length;
- if (this._automaticallyTrackDataSourceClocks && numDataSources > 0) {
- this.clockTrackedDataSource = dataSourceCollection.get(
- numDataSources - 1,
- );
- } else {
- this.clockTrackedDataSource = undefined;
- }
- }
};
/**
@@ -2097,10 +1967,7 @@ Viewer.prototype._onDataSourceRemoved = function (
* @returns {Promise} A Promise that resolves to true if the zoom was successful or false if the target is not currently visualized in the scene or the zoom was cancelled.
*/
Viewer.prototype.zoomTo = function (target, offset) {
- const options = {
- offset: offset,
- };
- return zoomToOrFly(this, target, options, false);
+ return this._cesiumWidget.zoomTo(target, offset);
};
/**
@@ -2126,326 +1993,9 @@ Viewer.prototype.zoomTo = function (target, offset) {
* @returns {Promise} A Promise that resolves to true if the flight was successful or false if the target is not currently visualized in the scene or the flight was cancelled. //TODO: Cleanup entity mentions
*/
Viewer.prototype.flyTo = function (target, options) {
- return zoomToOrFly(this, target, options, true);
-};
-
-function zoomToOrFly(that, zoomTarget, options, isFlight) {
- //>>includeStart('debug', pragmas.debug);
- if (!defined(zoomTarget)) {
- throw new DeveloperError("zoomTarget is required.");
- }
- //>>includeEnd('debug');
-
- cancelZoom(that);
-
- //We can't actually perform the zoom until all visualization is ready and
- //bounding spheres have been computed. Therefore we create and return
- //a deferred which will be resolved as part of the post-render step in the
- //frame that actually performs the zoom.
- const zoomPromise = new Promise((resolve) => {
- that._completeZoom = function (value) {
- resolve(value);
- };
- });
- that._zoomPromise = zoomPromise;
- that._zoomIsFlight = isFlight;
- that._zoomOptions = options;
-
- Promise.resolve(zoomTarget).then(function (zoomTarget) {
- //Only perform the zoom if it wasn't cancelled before the promise resolved.
- if (that._zoomPromise !== zoomPromise) {
- return;
- }
-
- //If the zoom target is a rectangular imagery in an ImageLayer
- if (zoomTarget instanceof ImageryLayer) {
- let rectanglePromise;
-
- if (defined(zoomTarget.imageryProvider)) {
- rectanglePromise = Promise.resolve(zoomTarget.getImageryRectangle());
- } else {
- rectanglePromise = new Promise((resolve) => {
- const removeListener = zoomTarget.readyEvent.addEventListener(() => {
- removeListener();
- resolve(zoomTarget.getImageryRectangle());
- });
- });
- }
- rectanglePromise
- .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 = position;
- }
- });
- return;
- }
-
- if (
- zoomTarget instanceof Cesium3DTileset ||
- zoomTarget instanceof TimeDynamicPointCloud ||
- zoomTarget instanceof VoxelPrimitive
- ) {
- that._zoomTarget = zoomTarget;
- return;
- }
-
- //If the zoom target is a data source, and it's in the middle of loading, wait for it to finish loading.
- if (zoomTarget.isLoading && defined(zoomTarget.loadingEvent)) {
- const removeEvent = zoomTarget.loadingEvent.addEventListener(function () {
- removeEvent();
-
- //Only perform the zoom if it wasn't cancelled before the data source finished.
- if (that._zoomPromise === zoomPromise) {
- that._zoomTarget = zoomTarget.entities.values.slice(0);
- }
- });
- return;
- }
-
- //Zoom target is already an array, just copy it and return.
- if (Array.isArray(zoomTarget)) {
- that._zoomTarget = zoomTarget.slice(0);
- return;
- }
-
- //If zoomTarget is an EntityCollection, this will retrieve the array
- zoomTarget = defaultValue(zoomTarget.values, zoomTarget);
-
- //If zoomTarget is a DataSource, this will retrieve the array.
- if (defined(zoomTarget.entities)) {
- zoomTarget = zoomTarget.entities.values;
- }
-
- //Zoom target is already an array, just copy it and return.
- if (Array.isArray(zoomTarget)) {
- that._zoomTarget = zoomTarget.slice(0);
- } else {
- //Single entity
- that._zoomTarget = [zoomTarget];
- }
- });
-
- that.scene.requestRender();
- return zoomPromise;
-}
-
-function clearZoom(viewer) {
- viewer._zoomPromise = undefined;
- viewer._zoomTarget = undefined;
- viewer._zoomOptions = undefined;
-}
-
-function cancelZoom(viewer) {
- const zoomPromise = viewer._zoomPromise;
- if (defined(zoomPromise)) {
- clearZoom(viewer);
- viewer._completeZoom(false);
- }
-}
-
-/**
- * @private
- */
-Viewer.prototype._postRender = function () {
- updateZoomTarget(this);
- updateTrackedEntity(this);
+ return this._cesiumWidget.flyTo(target, options);
};
-function updateZoomTarget(viewer) {
- const target = viewer._zoomTarget;
- if (!defined(target) || viewer.scene.mode === SceneMode.MORPHING) {
- return;
- }
-
- const scene = viewer.scene;
- const camera = scene.camera;
- const zoomOptions = defaultValue(viewer._zoomOptions, {});
- let options;
- function zoomToBoundingSphere(boundingSphere) {
- // If offset was originally undefined then give it base value instead of empty object
- if (!defined(zoomOptions.offset)) {
- zoomOptions.offset = new HeadingPitchRange(
- 0.0,
- -0.5,
- boundingSphere.radius,
- );
- }
-
- options = {
- offset: zoomOptions.offset,
- duration: zoomOptions.duration,
- maximumHeight: zoomOptions.maximumHeight,
- complete: function () {
- viewer._completeZoom(true);
- },
- cancel: function () {
- viewer._completeZoom(false);
- },
- };
-
- if (viewer._zoomIsFlight) {
- camera.flyToBoundingSphere(target.boundingSphere, options);
- } else {
- camera.viewBoundingSphere(boundingSphere, zoomOptions.offset);
- camera.lookAtTransform(Matrix4.IDENTITY);
-
- // Finish the promise
- viewer._completeZoom(true);
- }
-
- clearZoom(viewer);
- }
-
- if (target instanceof TimeDynamicPointCloud) {
- if (defined(target.boundingSphere)) {
- zoomToBoundingSphere(target.boundingSphere);
- return;
- }
-
- // Otherwise, the first "frame" needs to have been rendered
- const removeEventListener = target.frameChanged.addEventListener(
- function (timeDynamicPointCloud) {
- zoomToBoundingSphere(timeDynamicPointCloud.boundingSphere);
- removeEventListener();
- },
- );
- return;
- }
-
- if (target instanceof Cesium3DTileset || target instanceof VoxelPrimitive) {
- zoomToBoundingSphere(target.boundingSphere);
- return;
- }
-
- // If zoomTarget was an ImageryLayer
- if (target instanceof Cartographic) {
- options = {
- destination: scene.ellipsoid.cartographicToCartesian(target),
- duration: zoomOptions.duration,
- maximumHeight: zoomOptions.maximumHeight,
- complete: function () {
- viewer._completeZoom(true);
- },
- cancel: function () {
- viewer._completeZoom(false);
- },
- };
-
- if (viewer._zoomIsFlight) {
- camera.flyTo(options);
- } else {
- camera.setView(options);
- viewer._completeZoom(true);
- }
- clearZoom(viewer);
- return;
- }
-
- const entities = target;
-
- const boundingSpheres = [];
- for (let i = 0, len = entities.length; i < len; i++) {
- const state = viewer._dataSourceDisplay.getBoundingSphere(
- entities[i],
- false,
- boundingSphereScratch,
- );
-
- if (state === BoundingSphereState.PENDING) {
- return;
- } else if (state !== BoundingSphereState.FAILED) {
- boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
- }
- }
-
- if (boundingSpheres.length === 0) {
- cancelZoom(viewer);
- return;
- }
-
- // Stop tracking the current entity.
- viewer.trackedEntity = undefined;
-
- const boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
-
- if (!viewer._zoomIsFlight) {
- camera.viewBoundingSphere(boundingSphere, zoomOptions.offset);
- camera.lookAtTransform(Matrix4.IDENTITY);
- clearZoom(viewer);
- viewer._completeZoom(true);
- } else {
- clearZoom(viewer);
- camera.flyToBoundingSphere(boundingSphere, {
- duration: zoomOptions.duration,
- maximumHeight: zoomOptions.maximumHeight,
- complete: function () {
- viewer._completeZoom(true);
- },
- cancel: function () {
- viewer._completeZoom(false);
- },
- offset: zoomOptions.offset,
- });
- }
-}
-
-function updateTrackedEntity(viewer) {
- if (!viewer._needTrackedEntityUpdate) {
- return;
- }
-
- const trackedEntity = viewer._trackedEntity;
- const currentTime = viewer.clock.currentTime;
-
- //Verify we have a current position at this time. This is only triggered if a position
- //has become undefined after trackedEntity is set but before the boundingSphere has been
- //computed. In this case, we will track the entity once it comes back into existence.
- const currentPosition = Property.getValueOrUndefined(
- trackedEntity.position,
- currentTime,
- );
-
- if (!defined(currentPosition)) {
- return;
- }
-
- const scene = viewer.scene;
-
- const state = viewer._dataSourceDisplay.getBoundingSphere(
- trackedEntity,
- false,
- boundingSphereScratch,
- );
- if (state === BoundingSphereState.PENDING) {
- return;
- }
-
- const sceneMode = scene.mode;
- if (
- sceneMode === SceneMode.COLUMBUS_VIEW ||
- sceneMode === SceneMode.SCENE2D
- ) {
- scene.screenSpaceCameraController.enableTranslate = false;
- }
-
- if (
- sceneMode === SceneMode.COLUMBUS_VIEW ||
- sceneMode === SceneMode.SCENE3D
- ) {
- scene.screenSpaceCameraController.enableTilt = false;
- }
-
- const bs =
- state !== BoundingSphereState.FAILED ? boundingSphereScratch : undefined;
- viewer._entityView = new EntityView(trackedEntity, scene, scene.ellipsoid);
- viewer._entityView.update(currentTime, bs);
- viewer._needTrackedEntityUpdate = false;
-}
-
/**
* A function that augments a Viewer instance with additional functionality.
* @callback Viewer.ViewerMixin
diff --git a/packages/widgets/Specs/Viewer/ViewerSpec.js b/packages/widgets/Specs/Viewer/ViewerSpec.js
index a8428eae1a3e..49df43f46eb2 100644
--- a/packages/widgets/Specs/Viewer/ViewerSpec.js
+++ b/packages/widgets/Specs/Viewer/ViewerSpec.js
@@ -1,37 +1,23 @@
import {
- BoundingSphere,
Cartesian3,
CartographicGeocoderService,
CesiumWidget,
Clock,
- ClockRange,
- ClockStep,
- Color,
CreditDisplay,
- defined,
EllipsoidTerrainProvider,
- HeadingPitchRange,
- JulianDate,
- Matrix4,
Rectangle,
- TimeIntervalCollection,
WebMercatorProjection,
ConstantPositionProperty,
ConstantProperty,
- DataSourceClock,
DataSourceCollection,
DataSourceDisplay,
Entity,
Camera,
CameraFlightPath,
- Cesium3DTileset,
ImageryLayer,
ImageryLayerCollection,
- Cesium3DTilesVoxelProvider,
SceneMode,
ShadowMode,
- TimeDynamicPointCloud,
- VoxelPrimitive,
} from "@cesium/engine";
import {
@@ -140,13 +126,6 @@ describe(
clockViewModel.destroy();
});
- it("can set shouldAnimate", function () {
- viewer = createViewer(container, {
- shouldAnimate: true,
- });
- expect(viewer.clock.shouldAnimate).toBe(true);
- });
-
it("setting shouldAnimate in options overrides clock shouldAnimate", function () {
const clockViewModel = new ClockViewModel(
new Clock({
@@ -606,30 +585,6 @@ describe(
expect(viewer.scene.skyAtmosphere).not.toBeDefined();
});
- it("can set dataSources at construction", function () {
- const collection = new DataSourceCollection();
- viewer = createViewer(container, {
- dataSources: collection,
- });
- expect(viewer.dataSources).toBe(collection);
- });
-
- it("default DataSourceCollection is destroyed when Viewer is destroyed", function () {
- viewer = createViewer(container);
- const dataSources = viewer.dataSources;
- viewer.destroy();
- expect(dataSources.isDestroyed()).toBe(true);
- });
-
- it("specified DataSourceCollection is not destroyed when Viewer is destroyed", function () {
- const collection = new DataSourceCollection();
- viewer = createViewer(container, {
- dataSources: collection,
- });
- viewer.destroy();
- expect(collection.isDestroyed()).toBe(false);
- });
-
it("throws if targetFrameRate less than 0", function () {
viewer = createViewer(container);
expect(function () {
@@ -721,178 +676,6 @@ describe(
}, "render loop to be disabled.");
});
- it("sets the clock and timeline based on the first data source", function () {
- const dataSource = new MockDataSource();
- dataSource.clock = new DataSourceClock();
- dataSource.clock.startTime = JulianDate.fromIso8601("2013-08-01T18:00Z");
- dataSource.clock.stopTime = JulianDate.fromIso8601("2013-08-21T02:00Z");
- dataSource.clock.currentTime =
- JulianDate.fromIso8601("2013-08-02T00:00Z");
- dataSource.clock.clockRange = ClockRange.CLAMPED;
- dataSource.clock.clockStep = ClockStep.TICK_DEPENDENT;
- dataSource.clock.multiplier = 20.0;
-
- viewer = createViewer(container);
- return viewer.dataSources.add(dataSource).then(function () {
- expect(viewer.clock.startTime).toEqual(dataSource.clock.startTime);
- expect(viewer.clock.stopTime).toEqual(dataSource.clock.stopTime);
- expect(viewer.clock.currentTime).toEqual(dataSource.clock.currentTime);
- expect(viewer.clock.clockRange).toEqual(dataSource.clock.clockRange);
- expect(viewer.clock.clockStep).toEqual(dataSource.clock.clockStep);
- expect(viewer.clock.multiplier).toEqual(dataSource.clock.multiplier);
- });
- });
-
- it("sets the clock for multiple data sources", function () {
- const dataSource1 = new MockDataSource();
- dataSource1.clock = new DataSourceClock();
- dataSource1.clock.startTime = JulianDate.fromIso8601("2013-08-01T18:00Z");
- dataSource1.clock.stopTime = JulianDate.fromIso8601("2013-08-21T02:00Z");
- dataSource1.clock.currentTime =
- JulianDate.fromIso8601("2013-08-02T00:00Z");
-
- let dataSource2, dataSource3;
- viewer = createViewer(container);
- return viewer.dataSources
- .add(dataSource1)
- .then(function () {
- expect(viewer.clockTrackedDataSource).toBe(dataSource1);
- expect(viewer.clock.startTime).toEqual(dataSource1.clock.startTime);
-
- dataSource2 = new MockDataSource();
- dataSource2.clock = new DataSourceClock();
- dataSource2.clock.startTime =
- JulianDate.fromIso8601("2014-08-01T18:00Z");
- dataSource2.clock.stopTime =
- JulianDate.fromIso8601("2014-08-21T02:00Z");
- dataSource2.clock.currentTime =
- JulianDate.fromIso8601("2014-08-02T00:00Z");
-
- viewer.dataSources.add(dataSource2);
- })
- .then(function () {
- expect(viewer.clockTrackedDataSource).toBe(dataSource2);
- expect(viewer.clock.startTime).toEqual(dataSource2.clock.startTime);
-
- dataSource3 = new MockDataSource();
- dataSource3.clock = new DataSourceClock();
- dataSource3.clock.startTime =
- JulianDate.fromIso8601("2015-08-01T18:00Z");
- dataSource3.clock.stopTime =
- JulianDate.fromIso8601("2015-08-21T02:00Z");
- dataSource3.clock.currentTime =
- JulianDate.fromIso8601("2015-08-02T00:00Z");
-
- viewer.dataSources.add(dataSource3);
- })
- .then(function () {
- expect(viewer.clockTrackedDataSource).toBe(dataSource3);
- expect(viewer.clock.startTime).toEqual(dataSource3.clock.startTime);
-
- // Removing the last dataSource moves the clock to second-last.
- viewer.dataSources.remove(dataSource3);
- expect(viewer.clockTrackedDataSource).toBe(dataSource2);
- expect(viewer.clock.startTime).toEqual(dataSource2.clock.startTime);
-
- // Removing the first data source has no effect, because it's not active.
- viewer.dataSources.remove(dataSource1);
- expect(viewer.clockTrackedDataSource).toBe(dataSource2);
- expect(viewer.clock.startTime).toEqual(dataSource2.clock.startTime);
- });
- });
-
- it("updates the clock when the data source changes", function () {
- const dataSource = new MockDataSource();
- dataSource.clock = new DataSourceClock();
- dataSource.clock.startTime = JulianDate.fromIso8601("2013-08-01T18:00Z");
- dataSource.clock.stopTime = JulianDate.fromIso8601("2013-08-21T02:00Z");
- dataSource.clock.currentTime =
- JulianDate.fromIso8601("2013-08-02T00:00Z");
- dataSource.clock.clockRange = ClockRange.CLAMPED;
- dataSource.clock.clockStep = ClockStep.TICK_DEPENDENT;
- dataSource.clock.multiplier = 20.0;
-
- viewer = createViewer(container);
- return viewer.dataSources.add(dataSource).then(function () {
- dataSource.clock.startTime =
- JulianDate.fromIso8601("2014-08-01T18:00Z");
- dataSource.clock.stopTime = JulianDate.fromIso8601("2014-08-21T02:00Z");
- dataSource.clock.currentTime =
- JulianDate.fromIso8601("2014-08-02T00:00Z");
- dataSource.clock.clockRange = ClockRange.UNBOUNDED;
- dataSource.clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
- dataSource.clock.multiplier = 10.0;
-
- dataSource.changedEvent.raiseEvent(dataSource);
-
- expect(viewer.clock.startTime).toEqual(dataSource.clock.startTime);
- expect(viewer.clock.stopTime).toEqual(dataSource.clock.stopTime);
- expect(viewer.clock.currentTime).toEqual(dataSource.clock.currentTime);
- expect(viewer.clock.clockRange).toEqual(dataSource.clock.clockRange);
- expect(viewer.clock.clockStep).toEqual(dataSource.clock.clockStep);
- expect(viewer.clock.multiplier).toEqual(dataSource.clock.multiplier);
-
- dataSource.clock.clockStep = ClockStep.SYSTEM_CLOCK;
- dataSource.clock.multiplier = 1.0;
-
- dataSource.changedEvent.raiseEvent(dataSource);
-
- expect(viewer.clock.clockStep).toEqual(dataSource.clock.clockStep);
- });
- });
-
- it("can manually control the clock tracking", function () {
- const dataSource1 = new MockDataSource();
- dataSource1.clock = new DataSourceClock();
- dataSource1.clock.startTime = JulianDate.fromIso8601("2013-08-01T18:00Z");
- dataSource1.clock.stopTime = JulianDate.fromIso8601("2013-08-21T02:00Z");
- dataSource1.clock.currentTime =
- JulianDate.fromIso8601("2013-08-02T00:00Z");
-
- viewer = createViewer(container, {
- automaticallyTrackDataSourceClocks: false,
- });
-
- let dataSource2;
- return viewer.dataSources
- .add(dataSource1)
- .then(function () {
- // Because of the above Viewer option, data sources are not automatically
- // selected for clock tracking.
- expect(viewer.clockTrackedDataSource).not.toBeDefined();
- // The mock data source time is in the past, so will not be the default time.
- expect(viewer.clock.startTime).not.toEqual(
- dataSource1.clock.startTime,
- );
-
- // Manually set the first data source as the tracked data source.
- viewer.clockTrackedDataSource = dataSource1;
- expect(viewer.clockTrackedDataSource).toBe(dataSource1);
- expect(viewer.clock.startTime).toEqual(dataSource1.clock.startTime);
-
- dataSource2 = new MockDataSource();
- dataSource2.clock = new DataSourceClock();
- dataSource2.clock.startTime =
- JulianDate.fromIso8601("2014-08-01T18:00Z");
- dataSource2.clock.stopTime =
- JulianDate.fromIso8601("2014-08-21T02:00Z");
- dataSource2.clock.currentTime =
- JulianDate.fromIso8601("2014-08-02T00:00Z");
-
- // Adding a second data source in manual mode still leaves the first one tracked.
- viewer.dataSources.add(dataSource2);
- })
- .then(function () {
- expect(viewer.clockTrackedDataSource).toBe(dataSource1);
- expect(viewer.clock.startTime).toEqual(dataSource1.clock.startTime);
-
- // Removing the tracked data source in manual mode turns off tracking, even
- // if other data sources remain available for tracking.
- viewer.dataSources.remove(dataSource1);
- expect(viewer.clockTrackedDataSource).not.toBeDefined();
- });
- });
-
it("shows the error panel when render throws", function () {
viewer = createViewer(container);
@@ -979,21 +762,6 @@ describe(
);
});
- it("can get and set trackedEntity", function () {
- viewer = createViewer(container);
-
- const entity = new Entity();
- entity.position = new ConstantProperty(
- new Cartesian3(123456, 123456, 123456),
- );
-
- viewer.trackedEntity = entity;
- expect(viewer.trackedEntity).toBe(entity);
-
- viewer.trackedEntity = undefined;
- expect(viewer.trackedEntity).toBeUndefined();
- });
-
it("can get and set selectedEntity", function () {
const viewer = createViewer(container);
@@ -1042,32 +810,6 @@ describe(
viewer.destroy();
});
- it("raises an event when the tracked entity changes", function () {
- const viewer = createViewer(container);
-
- const dataSource = new MockDataSource();
- viewer.dataSources.add(dataSource);
-
- const entity = new Entity();
- entity.position = new ConstantPositionProperty(
- new Cartesian3(123456, 123456, 123456),
- );
-
- dataSource.entities.add(entity);
-
- let myEntity;
- viewer.trackedEntityChanged.addEventListener(function (newValue) {
- myEntity = newValue;
- });
- viewer.trackedEntity = entity;
- expect(myEntity).toBe(entity);
-
- viewer.trackedEntity = undefined;
- expect(myEntity).toBeUndefined();
-
- viewer.destroy();
- });
-
it("selectedEntity sets InfoBox properties", function () {
const viewer = createViewer(container);
@@ -1123,966 +865,28 @@ describe(
expect(viewer.trackedEntity).toBeUndefined();
});
- it("stops tracking when tracked object is removed", function () {
- viewer = createViewer(container);
-
- const entity = new Entity();
- entity.position = new ConstantProperty(
- new Cartesian3(123456, 123456, 123456),
- );
-
- const dataSource = new MockDataSource();
- dataSource.entities.add(entity);
-
- viewer.dataSources.add(dataSource);
- viewer.trackedEntity = entity;
-
- expect(viewer.trackedEntity).toBe(entity);
-
- return pollToPromise(function () {
- viewer.render();
- return Cartesian3.equals(
- Matrix4.getTranslation(
- viewer.scene.camera.transform,
- new Cartesian3(),
- ),
- entity.position.getValue(),
- );
- }).then(function () {
- dataSource.entities.remove(entity);
-
- expect(viewer.trackedEntity).toBeUndefined();
- expect(viewer.scene.camera.transform).toEqual(Matrix4.IDENTITY);
-
- dataSource.entities.add(entity);
- viewer.trackedEntity = entity;
-
- expect(viewer.trackedEntity).toBe(entity);
-
- return pollToPromise(function () {
- viewer.render();
- viewer.render();
- return Cartesian3.equals(
- Matrix4.getTranslation(
- viewer.scene.camera.transform,
- new Cartesian3(),
- ),
- entity.position.getValue(),
- );
- }).then(function () {
- viewer.dataSources.remove(dataSource);
-
- expect(viewer.trackedEntity).toBeUndefined();
- expect(viewer.scene.camera.transform).toEqual(Matrix4.IDENTITY);
- });
- });
- });
-
- it("does not crash when tracking an object with a position property whose value is undefined.", function () {
- viewer = createViewer(container);
-
- const entity = new Entity();
- entity.position = new ConstantProperty(undefined);
- entity.polyline = {
- positions: [
- Cartesian3.fromDegrees(0, 0, 0),
- Cartesian3.fromDegrees(0, 0, 1),
- ],
- };
-
- viewer.entities.add(entity);
- viewer.trackedEntity = entity;
-
- spyOn(viewer.scene.renderError, "raiseEvent");
- return pollToPromise(function () {
- viewer.render();
- return viewer.dataSourceDisplay.update(viewer.clock.currentTime);
- }).then(function () {
- expect(viewer.scene.renderError.raiseEvent).not.toHaveBeenCalled();
- });
- });
-
- it("zoomTo throws if target is not defined", function () {
- viewer = createViewer(container);
-
- expect(function () {
- viewer.zoomTo();
- }).toThrowDeveloperError();
- });
-
- it("zoomTo zooms to Cesium3DTileset with default offset when offset not defined", async function () {
- viewer = createViewer(container);
-
- const path =
- "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json";
- const tileset = await Cesium3DTileset.fromUrl(path);
-
- const expectedBoundingSphere = tileset.boundingSphere;
- const expectedOffset = new HeadingPitchRange(
- 0.0,
- -0.5,
- expectedBoundingSphere.radius,
- );
-
- let wasCompleted = false;
- spyOn(viewer.camera, "viewBoundingSphere").and.callFake(
- function (boundingSphere, offset) {
- expect(boundingSphere).toEqual(expectedBoundingSphere);
- expect(offset).toEqual(expectedOffset);
- wasCompleted = true;
- },
- );
- const promise = viewer.zoomTo(tileset);
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
-
- it("zoomTo zooms to Cesium3DTileset with offset", async function () {
- viewer = createViewer(container);
-
- const path =
- "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json";
- const tileset = await Cesium3DTileset.fromUrl(path);
-
- const expectedBoundingSphere = tileset.boundingSphere;
- const expectedOffset = new HeadingPitchRange(
- 0.4,
- 1.2,
- 4.0 * expectedBoundingSphere.radius,
- );
-
- const promise = viewer.zoomTo(tileset, expectedOffset);
- let wasCompleted = false;
- spyOn(viewer.camera, "viewBoundingSphere").and.callFake(
- function (boundingSphere, offset) {
- expect(boundingSphere).toEqual(expectedBoundingSphere);
- expect(offset).toEqual(expectedOffset);
- wasCompleted = true;
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
-
- async function loadTimeDynamicPointCloud(viewer) {
- const scene = viewer.scene;
- const clock = viewer.clock;
-
- const uri =
- "./Data/Cesium3DTiles/PointCloud/PointCloudTimeDynamic/0.pnts";
- const dates = ["2018-07-19T15:18:00Z", "2018-07-19T15:18:00.5Z"];
-
- function dataCallback() {
- return {
- uri: uri,
- };
- }
-
- const timeIntervalCollection =
- TimeIntervalCollection.fromIso8601DateArray({
- iso8601Dates: dates,
- dataCallback: dataCallback,
- });
-
- const pointCloud = new TimeDynamicPointCloud({
- intervals: timeIntervalCollection,
- clock: clock,
- });
-
- const start = JulianDate.fromIso8601(dates[0]);
-
- clock.startTime = start;
- clock.currentTime = start;
- clock.multiplier = 0.0;
-
- scene.primitives.add(pointCloud);
-
- await pollToPromise(function () {
- scene.render();
- return defined(pointCloud.boundingSphere);
- });
-
- return pointCloud;
- }
-
- it("zoomTo zooms to TimeDynamicPointCloud with default offset when offset not defined", function () {
+ it("suspends animation by dataSources if allowed", function () {
viewer = createViewer(container);
- return loadTimeDynamicPointCloud(viewer).then(function (pointCloud) {
- const expectedBoundingSphere = pointCloud.boundingSphere;
- const expectedOffset = new HeadingPitchRange(
- 0.0,
- -0.5,
- expectedBoundingSphere.radius,
- );
-
- const promise = viewer.zoomTo(pointCloud);
- let wasCompleted = false;
- spyOn(viewer.camera, "viewBoundingSphere").and.callFake(
- function (boundingSphere, offset) {
- expect(boundingSphere).toEqual(expectedBoundingSphere);
- expect(offset).toEqual(expectedOffset);
- wasCompleted = true;
- },
- );
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- viewer.scene.primitives.remove(pointCloud);
- });
- });
- });
-
- it("zoomTo zooms to TimeDynamicPointCloud with offset", function () {
- viewer = createViewer(container);
- return loadTimeDynamicPointCloud(viewer).then(function (pointCloud) {
- const expectedBoundingSphere = pointCloud.boundingSphere;
- const expectedOffset = new HeadingPitchRange(
- 0.4,
- 1.2,
- 4.0 * expectedBoundingSphere.radius,
- );
-
- const promise = viewer.zoomTo(pointCloud, expectedOffset);
- let wasCompleted = false;
- spyOn(viewer.camera, "viewBoundingSphere").and.callFake(
- function (boundingSphere, offset) {
- expect(boundingSphere).toEqual(expectedBoundingSphere);
- expect(offset).toEqual(expectedOffset);
- wasCompleted = true;
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- viewer.scene.primitives.remove(pointCloud);
- });
- });
- });
-
- async function loadVoxelPrimitive(viewer) {
- const voxelPrimitive = new VoxelPrimitive({
- provider: await Cesium3DTilesVoxelProvider.fromUrl(
- "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json",
- ),
- });
- viewer.scene.primitives.add(voxelPrimitive);
- return voxelPrimitive;
- }
-
- it("zoomTo zooms to VoxelPrimitive with default offset when offset not defined", function () {
- viewer = createViewer(container);
-
- return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
- const expectedBoundingSphere = voxelPrimitive.boundingSphere;
- const expectedOffset = new HeadingPitchRange(
- 0.0,
- -0.5,
- expectedBoundingSphere.radius,
- );
-
- const promise = viewer.zoomTo(voxelPrimitive);
- let wasCompleted = false;
- spyOn(viewer.camera, "viewBoundingSphere").and.callFake(
- function (boundingSphere, offset) {
- expect(boundingSphere).toEqual(expectedBoundingSphere);
- expect(offset).toEqual(expectedOffset);
- wasCompleted = true;
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
- });
-
- it("zoomTo zooms to VoxelPrimitive with offset", function () {
- viewer = createViewer(container);
-
- return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
- const expectedBoundingSphere = voxelPrimitive.boundingSphere;
- const expectedOffset = new HeadingPitchRange(
- 0.4,
- 1.2,
- 4.0 * expectedBoundingSphere.radius,
- );
-
- const promise = viewer.zoomTo(voxelPrimitive, expectedOffset);
- let wasCompleted = false;
- spyOn(viewer.camera, "viewBoundingSphere").and.callFake(
- function (boundingSphere, offset) {
- expect(boundingSphere).toEqual(expectedBoundingSphere);
- expect(offset).toEqual(expectedOffset);
- wasCompleted = true;
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
- });
-
- it("zoomTo zooms to entity with undefined offset when offset not defined", function () {
- viewer = createViewer(container);
- viewer.entities.add({
- name: "Blue box",
- position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
- box: {
- dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
- material: Color.BLUE,
- },
- });
-
- const entities = viewer.entities;
-
- const promise = viewer.zoomTo(entities);
- let wasCompleted = false;
- spyOn(viewer._dataSourceDisplay, "getBoundingSphere").and.callFake(
- function () {
- return new BoundingSphere();
- },
- );
-
- spyOn(viewer.camera, "viewBoundingSphere").and.callFake(
- function (boundingSphere, offset) {
- expect(boundingSphere).toBeDefined();
- // expect offset to be undefined - doesn't use default bc of how zoomTo for entities is set up
- expect(offset).toBeUndefined();
- wasCompleted = true;
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
-
- it("zoomTo zooms to entity with offset", function () {
- viewer = createViewer(container);
- viewer.entities.add({
- name: "Blue box",
- position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
- box: {
- dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
- material: Color.BLUE,
- },
+ let updateResult = true;
+ spyOn(viewer.dataSourceDisplay, "update").and.callFake(function () {
+ viewer.dataSourceDisplay._ready = updateResult;
+ return updateResult;
});
- const entities = viewer.entities;
- // fake temp offset
- const expectedOffset = new HeadingPitchRange(3.0, 0.2, 2.3);
+ expect(viewer.clockViewModel.canAnimate).toBe(true);
- const promise = viewer.zoomTo(entities, expectedOffset);
- let wasCompleted = false;
- spyOn(viewer._dataSourceDisplay, "getBoundingSphere").and.callFake(
- function () {
- return new BoundingSphere();
- },
- );
- spyOn(viewer.camera, "viewBoundingSphere").and.callFake(
- function (boundingSphere, offset) {
- expect(expectedOffset).toEqual(offset);
- wasCompleted = true;
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
-
- it("zoomTo zooms to entity when globe is disabled", async function () {
- // Create viewer with globe disabled
- const viewer = createViewer(container, {
- globe: false,
- infoBox: false,
- selectionIndicator: false,
- shadows: true,
- shouldAnimate: true,
- });
-
- // Create position variable
- const position = Cartesian3.fromDegrees(-123.0744619, 44.0503706, 1000.0);
-
- // Add entity to viewer
- const entity = viewer.entities.add({
- position: position,
- model: {
- uri: "../SampleData/models/CesiumAir/Cesium_Air.glb",
- },
- });
-
- await viewer.zoomTo(entity);
-
- // Verify that no errors occurred
- expect(viewer.scene).toBeDefined();
- expect(viewer.scene.errorEvent).toBeUndefined();
- });
-
- it("flyTo throws if target is not defined", function () {
- viewer = createViewer(container);
-
- expect(function () {
- viewer.flyTo();
- }).toThrowDeveloperError();
- });
-
- it("flyTo flies to Cesium3DTileset with default offset when options not defined", async function () {
- viewer = createViewer(container);
-
- const path =
- "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json";
- const tileset = await Cesium3DTileset.fromUrl(path);
-
- const promise = viewer.flyTo(tileset);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.offset).toBeDefined();
- expect(options.duration).toBeUndefined();
- expect(options.maximumHeight).toBeUndefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
-
- it("flyTo flies to Cesium3DTileset with default offset when offset not defined", async function () {
- viewer = createViewer(container);
-
- const path =
- "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json";
- const tileset = await Cesium3DTileset.fromUrl(path);
-
- const options = {};
-
- const promise = viewer.flyTo(tileset, options);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.offset).toBeDefined();
- expect(options.duration).toBeUndefined();
- expect(options.maximumHeight).toBeUndefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
-
- it("flyTo flies to Cesium3DTileset when options are defined", async function () {
- viewer = createViewer(container);
-
- const path =
- "./Data/Cesium3DTiles/Tilesets/TilesetOfTilesets/tileset.json";
- const tileset = await Cesium3DTileset.fromUrl(path);
-
- const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
- const options = {
- offset: offsetVal,
- duration: 3.0,
- maximumHeight: 5.0,
- };
-
- const promise = viewer.flyTo(tileset, options);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.duration).toBeDefined();
- expect(options.maximumHeight).toBeDefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
-
- it("flyTo flies to TimeDynamicPointCloud with default offset when options not defined", function () {
- viewer = createViewer(container);
- return loadTimeDynamicPointCloud(viewer).then(function (pointCloud) {
- const promise = viewer.flyTo(pointCloud);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.offset).toBeDefined();
- expect(options.duration).toBeUndefined();
- expect(options.maximumHeight).toBeUndefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- viewer.scene.primitives.remove(pointCloud);
- });
- });
- });
-
- it("flyTo flies to TimeDynamicPointCloud with default offset when offset not defined", function () {
- viewer = createViewer(container);
- return loadTimeDynamicPointCloud(viewer).then(function (pointCloud) {
- const options = {};
- const promise = viewer.flyTo(pointCloud, options);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.offset).toBeDefined();
- expect(options.duration).toBeUndefined();
- expect(options.maximumHeight).toBeUndefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- viewer.scene.primitives.remove(pointCloud);
- });
- });
- });
-
- it("flyTo flies to TimeDynamicPointCloud when options are defined", function () {
- viewer = createViewer(container);
- return loadTimeDynamicPointCloud(viewer).then(function (pointCloud) {
- const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
- const options = {
- offset: offsetVal,
- duration: 3.0,
- maximumHeight: 5.0,
- };
- const promise = viewer.flyTo(pointCloud, options);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.duration).toBeDefined();
- expect(options.maximumHeight).toBeDefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- viewer.scene.primitives.remove(pointCloud);
- });
- });
- });
-
- it("flyTo flies to VoxelPrimitive with default offset when options not defined", function () {
- viewer = createViewer(container);
-
- return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
- const promise = viewer.flyTo(voxelPrimitive);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.offset).toBeDefined();
- expect(options.duration).toBeUndefined();
- expect(options.maximumHeight).toBeUndefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
- });
-
- it("flyTo flies to VoxelPrimitive with default offset when offset not defined", function () {
- viewer = createViewer(container);
- const options = {};
-
- return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
- const promise = viewer.flyTo(voxelPrimitive, options);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.offset).toBeDefined();
- expect(options.duration).toBeUndefined();
- expect(options.maximumHeight).toBeUndefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
- });
-
- it("flyTo flies to VoxelPrimitive when options are defined", function () {
- viewer = createViewer(container);
-
- // load tileset to test
- return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
- const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
- const options = {
- offset: offsetVal,
- duration: 3.0,
- maximumHeight: 5.0,
- };
-
- const promise = viewer.flyTo(voxelPrimitive, options);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.duration).toBeDefined();
- expect(options.maximumHeight).toBeDefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
- });
-
- it("flyTo flies to entity with default offset when options not defined", function () {
- viewer = createViewer(container);
-
- viewer.entities.add({
- name: "Blue box",
- position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
- box: {
- dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
- material: Color.BLUE,
- },
- });
-
- const entities = viewer.entities;
- const promise = viewer.flyTo(entities);
- let wasCompleted = false;
- spyOn(viewer._dataSourceDisplay, "getBoundingSphere").and.callFake(
- function () {
- return new BoundingSphere();
- },
- );
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.duration).toBeUndefined();
- expect(options.maximumHeight).toBeUndefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
-
- it("flyTo flies to imagery layer with default offset when options are not defined", async function () {
- viewer = createViewer(container);
-
- const imageryLayer = new ImageryLayer(testProvider);
-
- const promise = viewer.flyTo(imageryLayer, {
- duration: 0,
- });
-
- viewer._postRender();
-
- await expectAsync(promise).toBeResolved();
- });
-
- it("flyTo flies to VoxelPrimitive with default offset when options not defined", function () {
- viewer = createViewer(container);
-
- return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
- const promise = viewer.flyTo(voxelPrimitive);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.offset).toBeDefined();
- expect(options.duration).toBeUndefined();
- expect(options.maximumHeight).toBeUndefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
- });
-
- it("flyTo flies to VoxelPrimitive with default offset when offset not defined", function () {
- viewer = createViewer(container);
- const options = {};
-
- return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
- const promise = viewer.flyTo(voxelPrimitive, options);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.offset).toBeDefined();
- expect(options.duration).toBeUndefined();
- expect(options.maximumHeight).toBeUndefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
- });
-
- it("flyTo flies to VoxelPrimitive when options are defined", function () {
- viewer = createViewer(container);
-
- // load tileset to test
- return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
- const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
- const options = {
- offset: offsetVal,
- duration: 3.0,
- maximumHeight: 5.0,
- };
-
- const promise = viewer.flyTo(voxelPrimitive, options);
- let wasCompleted = false;
-
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.duration).toBeDefined();
- expect(options.maximumHeight).toBeDefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
- });
-
- it("flyTo flys to entity with default offset when offset not defined", function () {
- viewer = createViewer(container);
-
- viewer.entities.add({
- name: "Blue box",
- position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
- box: {
- dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
- material: Color.BLUE,
- },
- });
-
- const entities = viewer.entities;
- const options = {};
-
- const promise = viewer.flyTo(entities, options);
- let wasCompleted = false;
- spyOn(viewer._dataSourceDisplay, "getBoundingSphere").and.callFake(
- function () {
- return new BoundingSphere();
- },
- );
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.duration).toBeUndefined();
- expect(options.maximumHeight).toBeUndefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
-
- it("flyTo flies to entity when options are defined", function () {
- viewer = createViewer(container);
-
- viewer.entities.add({
- name: "Blue box",
- position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
- box: {
- dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
- material: Color.BLUE,
- },
- });
-
- const entities = viewer.entities;
- const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
- const options = {
- offset: offsetVal,
- duration: 3.0,
- maximumHeight: 5.0,
- };
-
- const promise = viewer.flyTo(entities, options);
- let wasCompleted = false;
- spyOn(viewer._dataSourceDisplay, "getBoundingSphere").and.callFake(
- function () {
- return new BoundingSphere();
- },
- );
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.duration).toBeDefined();
- expect(options.maximumHeight).toBeDefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
-
- it("flyTo flies to entity when offset is defined but other options for flyTo are not", function () {
- viewer = createViewer(container);
-
- viewer.entities.add({
- name: "Blue box",
- position: Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
- box: {
- dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
- material: Color.BLUE,
- },
- });
-
- const entities = viewer.entities;
- const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
- const options = {
- offset: offsetVal,
- };
-
- const promise = viewer.flyTo(entities, options);
- let wasCompleted = false;
- spyOn(viewer._dataSourceDisplay, "getBoundingSphere").and.callFake(
- function () {
- return new BoundingSphere();
- },
- );
- spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(
- function (target, options) {
- expect(options.duration).toBeUndefined();
- expect(options.maximumHeight).toBeUndefined();
- wasCompleted = true;
- options.complete();
- },
- );
-
- viewer._postRender();
-
- return promise.then(function () {
- expect(wasCompleted).toEqual(true);
- });
- });
+ viewer.clock.tick();
+ expect(viewer.clockViewModel.canAnimate).toBe(true);
- it("removes data source listeners when destroyed", function () {
- viewer = createViewer(container);
+ updateResult = false;
+ viewer.clock.tick();
+ expect(viewer.clockViewModel.canAnimate).toBe(false);
- //one data source that is added before mixing in
- const preMixinDataSource = new MockDataSource();
- //one data source that is added after mixing in
- const postMixinDataSource = new MockDataSource();
- return viewer.dataSources
- .add(preMixinDataSource)
- .then(function () {
- viewer.dataSources.add(postMixinDataSource);
- })
- .then(function () {
- const preMixinListenerCount =
- preMixinDataSource.entities.collectionChanged._listeners.length;
- const postMixinListenerCount =
- postMixinDataSource.entities.collectionChanged._listeners.length;
-
- viewer = viewer.destroy();
-
- expect(
- preMixinDataSource.entities.collectionChanged._listeners.length,
- ).not.toEqual(preMixinListenerCount);
- expect(
- postMixinDataSource.entities.collectionChanged._listeners.length,
- ).not.toEqual(postMixinListenerCount);
- });
+ viewer.clockViewModel.canAnimate = true;
+ viewer.allowDataSourcesToSuspendAnimation = false;
+ viewer.clock.tick();
+ expect(viewer.clockViewModel.canAnimate).toBe(true);
});
},
"WebGL",