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 defer from "../../Core/defer.js"; import defined from "../../Core/defined.js"; import destroyObject from "../../Core/destroyObject.js"; import DeveloperError from "../../Core/DeveloperError.js"; import Event from "../../Core/Event.js"; import EventHelper from "../../Core/EventHelper.js"; import HeadingPitchRange from "../../Core/HeadingPitchRange.js"; import Matrix4 from "../../Core/Matrix4.js"; import ScreenSpaceEventType from "../../Core/ScreenSpaceEventType.js"; import BoundingSphereState from "../../DataSources/BoundingSphereState.js"; import ConstantPositionProperty from "../../DataSources/ConstantPositionProperty.js"; import DataSourceCollection from "../../DataSources/DataSourceCollection.js"; import DataSourceDisplay from "../../DataSources/DataSourceDisplay.js"; import Entity from "../../DataSources/Entity.js"; import EntityView from "../../DataSources/EntityView.js"; import Property from "../../DataSources/Property.js"; import Cesium3DTileset from "../../Scene/Cesium3DTileset.js"; import computeFlyToLocationForRectangle from "../../Scene/computeFlyToLocationForRectangle.js"; import ImageryLayer from "../../Scene/ImageryLayer.js"; import SceneMode from "../../Scene/SceneMode.js"; import TimeDynamicPointCloud from "../../Scene/TimeDynamicPointCloud.js"; import knockout from "../../ThirdParty/knockout.js"; import Animation from "../Animation/Animation.js"; import AnimationViewModel from "../Animation/AnimationViewModel.js"; import BaseLayerPicker from "../BaseLayerPicker/BaseLayerPicker.js"; import createDefaultImageryProviderViewModels from "../BaseLayerPicker/createDefaultImageryProviderViewModels.js"; import createDefaultTerrainProviderViewModels from "../BaseLayerPicker/createDefaultTerrainProviderViewModels.js"; import CesiumWidget from "../CesiumWidget/CesiumWidget.js"; import ClockViewModel from "../ClockViewModel.js"; import FullscreenButton from "../FullscreenButton/FullscreenButton.js"; import Geocoder from "../Geocoder/Geocoder.js"; import getElement from "../getElement.js"; import HomeButton from "../HomeButton/HomeButton.js"; import InfoBox from "../InfoBox/InfoBox.js"; import NavigationHelpButton from "../NavigationHelpButton/NavigationHelpButton.js"; import ProjectionPicker from "../ProjectionPicker/ProjectionPicker.js"; import SceneModePicker from "../SceneModePicker/SceneModePicker.js"; import SelectionIndicator from "../SelectionIndicator/SelectionIndicator.js"; import subscribeAndEvaluate from "../subscribeAndEvaluate.js"; import Timeline from "../Timeline/Timeline.js"; import VRButton from "../VRButton/VRButton.js"; import Cesium3DTileFeature from "../../Scene/Cesium3DTileFeature.js"; import JulianDate from "../../Core/JulianDate.js"; import CesiumMath from "../../Core/Math.js"; const boundingSphereScratch = new BoundingSphere(); function onTimelineScrubfunction(e) { const clock = e.clock; clock.currentTime = e.timeJulian; clock.shouldAnimate = false; } function getCesium3DTileFeatureDescription(feature) { const propertyNames = feature.getPropertyNames(); let html = ""; propertyNames.forEach(function (propertyName) { const value = feature.getProperty(propertyName); if (defined(value)) { html += `
true
, each geometry instance will only be rendered in 3D to save GPU memory.
* @property {Boolean} [shouldAnimate=false] true
if the clock should attempt to advance simulation time by default, false
otherwise. This option takes precedence over setting {@link Viewer#clockViewModel}.
* @property {ClockViewModel} [clockViewModel=new ClockViewModel(clock)] The clock view model to use to control current time.
* @property {ProviderViewModel} [selectedImageryProviderViewModel] The view model for the current base imagery layer, if not supplied the first available base layer is used. This value is only valid if `baseLayerPicker` is set to true.
* @property {ProviderViewModel[]} [imageryProviderViewModels=createDefaultImageryProviderViewModels()] The array of ProviderViewModels to be selectable from the BaseLayerPicker. This value is only valid if `baseLayerPicker` is set to true.
* @property {ProviderViewModel} [selectedTerrainProviderViewModel] The view model for the current base terrain layer, if not supplied the first available base layer is used. This value is only valid if `baseLayerPicker` is set to true.
* @property {ProviderViewModel[]} [terrainProviderViewModels=createDefaultTerrainProviderViewModels()] The array of ProviderViewModels to be selectable from the BaseLayerPicker. This value is only valid if `baseLayerPicker` is set to true.
* @property {ImageryProvider} [imageryProvider=createWorldImagery()] The imagery provider to use. This value is only valid if `baseLayerPicker` is set to false.
* @property {TerrainProvider} [terrainProvider=new EllipsoidTerrainProvider()] The terrain provider to use
* @property {SkyBox|false} [skyBox] The skybox used to render the stars. When undefined
, the default stars are used. If set to false
, no skyBox, Sun, or Moon will be added.
* @property {SkyAtmosphere|false} [skyAtmosphere] Blue sky, and the glow around the Earth's limb. Set to false
to turn it off.
* @property {Element|String} [fullscreenElement=document.body] The element or id to be placed into fullscreen mode when the full screen button is pressed.
* @property {Boolean} [useDefaultRenderLoop=true] True if this widget should control the render loop, false otherwise.
* @property {Number} [targetFrameRate] The target frame rate when using the default render loop.
* @property {Boolean} [showRenderLoopErrors=true] If true, this widget will automatically display an HTML panel to the user containing the error, if a render loop error occurs.
* @property {Boolean} [useBrowserRecommendedResolution=true] If true, render at the browser's recommended resolution and ignore window.devicePixelRatio
.
* @property {Boolean} [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.
* @property {Object} [contextOptions] Context and WebGL creation properties corresponding to options
passed to {@link Scene}.
* @property {SceneMode} [sceneMode=SceneMode.SCENE3D] The initial scene mode.
* @property {MapProjection} [mapProjection=new GeographicProjection()] The map projection to use in 2D and Columbus View modes.
* @property {Globe|false} [globe=new Globe(mapProjection.ellipsoid)] The globe to use in the scene. If set to false
, no globe will be added.
* @property {Boolean} [orderIndependentTranslucency=true] If true and the configuration supports it, use order independent translucency.
* @property {Element|String} [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.
* @property {Element|String} [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.
* @property {DataSourceCollection} [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 viewer is destroyed.
* @property {Boolean} [shadows=false] Determines if shadows are cast by light sources.
* @property {ShadowMode} [terrainShadows=ShadowMode.RECEIVE_ONLY] Determines if the terrain casts or receives shadows from light sources.
* @property {MapMode2D} [mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
* @property {Boolean} [projectionPicker=false] If set to true, the ProjectionPicker widget will be created.
* @property {Boolean} [requestRenderMode=false] If true, rendering a frame will only occur when needed as determined by changes within the scene. Enabling reduces the CPU/GPU usage of your application and uses less battery on mobile, 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}.
* @property {Number} [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}.
* @property {Number} [depthPlaneEllipsoidOffset=0.0] Adjust the DepthPlane to address rendering artefacts below ellipsoid zero elevation.
* @property {Number} [msaaSamples=1] 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.
*/
/**
* A base widget for building applications. It composites all of the standard Cesium widgets into one reusable package.
* The widget can always be extended by using mixins, which add functionality useful for a variety of applications.
*
* @alias Viewer
* @constructor
*
* @param {Element|String} container The DOM element or ID that will contain the widget.
* @param {Viewer.ConstructorOptions} [options] Object describing initialization options
*
* @exception {DeveloperError} Element with id "container" does not exist in the document.
* @exception {DeveloperError} options.selectedImageryProviderViewModel is not available when not using the BaseLayerPicker widget, specify options.imageryProvider instead.
* @exception {DeveloperError} options.selectedTerrainProviderViewModel is not available when not using the BaseLayerPicker widget, specify options.terrainProvider instead.
*
* @see Animation
* @see BaseLayerPicker
* @see CesiumWidget
* @see FullscreenButton
* @see HomeButton
* @see SceneModePicker
* @see Timeline
* @see viewerDragDropMixin
*
* @demo {@link https://sandcastle.cesium.com/index.html?src=Hello%20World.html|Cesium Sandcastle Hello World Demo}
*
* @example
* //Initialize the viewer widget with several custom options and mixins.
* const viewer = new Cesium.Viewer('cesiumContainer', {
* //Start in Columbus Viewer
* sceneMode : Cesium.SceneMode.COLUMBUS_VIEW,
* //Use Cesium World Terrain
* terrainProvider : Cesium.createWorldTerrain(),
* //Hide the base layer picker
* baseLayerPicker : false,
* //Use OpenStreetMaps
* imageryProvider : new Cesium.OpenStreetMapImageryProvider({
* url : 'https://a.tile.openstreetmap.org/'
* }),
* skyBox : new Cesium.SkyBox({
* sources : {
* positiveX : 'stars/TychoSkymapII.t3_08192x04096_80_px.jpg',
* negativeX : 'stars/TychoSkymapII.t3_08192x04096_80_mx.jpg',
* positiveY : 'stars/TychoSkymapII.t3_08192x04096_80_py.jpg',
* negativeY : 'stars/TychoSkymapII.t3_08192x04096_80_my.jpg',
* positiveZ : 'stars/TychoSkymapII.t3_08192x04096_80_pz.jpg',
* negativeZ : 'stars/TychoSkymapII.t3_08192x04096_80_mz.jpg'
* }
* }),
* // Show Columbus View map with Web Mercator projection
* mapProjection : new Cesium.WebMercatorProjection()
* });
*
* //Add basic drag and drop functionality
* viewer.extend(Cesium.viewerDragDropMixin);
*
* //Show a pop-up alert if we encounter an error when processing a dropped file
* viewer.dropError.addEventListener(function(dropHandler, name, error) {
* console.log(error);
* window.alert(error);
* });
*/
function Viewer(container, options) {
//>>includeStart('debug', pragmas.debug);
if (!defined(container)) {
throw new DeveloperError("container is required.");
}
//>>includeEnd('debug');
container = getElement(container);
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
const createBaseLayerPicker =
(!defined(options.globe) || options.globe !== false) &&
(!defined(options.baseLayerPicker) || options.baseLayerPicker !== false);
//>>includeStart('debug', pragmas.debug);
// If not using BaseLayerPicker, selectedImageryProviderViewModel is an invalid option
if (
!createBaseLayerPicker &&
defined(options.selectedImageryProviderViewModel)
) {
throw new DeveloperError(
"options.selectedImageryProviderViewModel is not available when not using the BaseLayerPicker widget. \
Either specify options.imageryProvider instead or set options.baseLayerPicker to true."
);
}
// If not using BaseLayerPicker, selectedTerrainProviderViewModel is an invalid option
if (
!createBaseLayerPicker &&
defined(options.selectedTerrainProviderViewModel)
) {
throw new DeveloperError(
"options.selectedTerrainProviderViewModel is not available when not using the BaseLayerPicker widget. \
Either specify options.terrainProvider instead or set options.baseLayerPicker to true."
);
}
//>>includeEnd('debug')
const that = this;
const viewerContainer = document.createElement("div");
viewerContainer.className = "cesium-viewer";
container.appendChild(viewerContainer);
// Cesium widget container
const cesiumWidgetContainer = document.createElement("div");
cesiumWidgetContainer.className = "cesium-viewer-cesiumWidgetContainer";
viewerContainer.appendChild(cesiumWidgetContainer);
// Bottom container
const bottomContainer = document.createElement("div");
bottomContainer.className = "cesium-viewer-bottom";
viewerContainer.appendChild(bottomContainer);
const scene3DOnly = defaultValue(options.scene3DOnly, false);
let clock;
let clockViewModel;
let destroyClockViewModel = false;
if (defined(options.clockViewModel)) {
clockViewModel = options.clockViewModel;
clock = clockViewModel.clock;
} else {
clock = new Clock();
clockViewModel = new ClockViewModel(clock);
destroyClockViewModel = true;
}
if (defined(options.shouldAnimate)) {
clock.shouldAnimate = options.shouldAnimate;
}
// Cesium widget
const cesiumWidget = new CesiumWidget(cesiumWidgetContainer, {
imageryProvider:
createBaseLayerPicker || defined(options.imageryProvider)
? false
: undefined,
clock: clock,
skyBox: options.skyBox,
skyAtmosphere: options.skyAtmosphere,
sceneMode: options.sceneMode,
mapProjection: options.mapProjection,
globe: options.globe,
orderIndependentTranslucency: options.orderIndependentTranslucency,
contextOptions: options.contextOptions,
useDefaultRenderLoop: options.useDefaultRenderLoop,
targetFrameRate: options.targetFrameRate,
showRenderLoopErrors: options.showRenderLoopErrors,
useBrowserRecommendedResolution: options.useBrowserRecommendedResolution,
creditContainer: defined(options.creditContainer)
? options.creditContainer
: bottomContainer,
creditViewport: options.creditViewport,
scene3DOnly: scene3DOnly,
shadows: options.shadows,
terrainShadows: options.terrainShadows,
mapMode2D: options.mapMode2D,
requestRenderMode: options.requestRenderMode,
maximumRenderTimeChange: options.maximumRenderTimeChange,
depthPlaneEllipsoidOffset: options.depthPlaneEllipsoidOffset,
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;
if (
!defined(options.selectionIndicator) ||
options.selectionIndicator !== false
) {
const selectionIndicatorContainer = document.createElement("div");
selectionIndicatorContainer.className =
"cesium-viewer-selectionIndicatorContainer";
viewerContainer.appendChild(selectionIndicatorContainer);
selectionIndicator = new SelectionIndicator(
selectionIndicatorContainer,
scene
);
}
// Info Box
let infoBox;
if (!defined(options.infoBox) || options.infoBox !== false) {
const infoBoxContainer = document.createElement("div");
infoBoxContainer.className = "cesium-viewer-infoBoxContainer";
viewerContainer.appendChild(infoBoxContainer);
infoBox = new InfoBox(infoBoxContainer);
const infoBoxViewModel = infoBox.viewModel;
eventHelper.add(
infoBoxViewModel.cameraClicked,
Viewer.prototype._onInfoBoxCameraClicked,
this
);
eventHelper.add(
infoBoxViewModel.closeClicked,
Viewer.prototype._onInfoBoxClockClicked,
this
);
}
// Main Toolbar
const toolbar = document.createElement("div");
toolbar.className = "cesium-viewer-toolbar";
viewerContainer.appendChild(toolbar);
// Geocoder
let geocoder;
if (!defined(options.geocoder) || options.geocoder !== false) {
const geocoderContainer = document.createElement("div");
geocoderContainer.className = "cesium-viewer-geocoderContainer";
toolbar.appendChild(geocoderContainer);
let geocoderService;
if (defined(options.geocoder) && typeof options.geocoder !== "boolean") {
geocoderService = Array.isArray(options.geocoder)
? options.geocoder
: [options.geocoder];
}
geocoder = new Geocoder({
container: geocoderContainer,
geocoderServices: geocoderService,
scene: scene,
});
// Subscribe to search so that we can clear the trackedEntity when it is clicked.
eventHelper.add(
geocoder.viewModel.search.beforeExecute,
Viewer.prototype._clearObjects,
this
);
}
// HomeButton
let homeButton;
if (!defined(options.homeButton) || options.homeButton !== false) {
homeButton = new HomeButton(toolbar, scene);
if (defined(geocoder)) {
eventHelper.add(homeButton.viewModel.command.afterExecute, function () {
const viewModel = geocoder.viewModel;
viewModel.searchText = "";
if (viewModel.isSearchInProgress) {
viewModel.search();
}
});
}
// Subscribe to the home button beforeExecute event so that we can clear the trackedEntity.
eventHelper.add(
homeButton.viewModel.command.beforeExecute,
Viewer.prototype._clearTrackedObject,
this
);
}
// SceneModePicker
// By default, we silently disable the scene mode picker if scene3DOnly is true,
// but if sceneModePicker is explicitly set to true, throw an error.
//>>includeStart('debug', pragmas.debug);
if (options.sceneModePicker === true && scene3DOnly) {
throw new DeveloperError(
"options.sceneModePicker is not available when options.scene3DOnly is set to true."
);
}
//>>includeEnd('debug');
let sceneModePicker;
if (
!scene3DOnly &&
(!defined(options.sceneModePicker) || options.sceneModePicker !== false)
) {
sceneModePicker = new SceneModePicker(toolbar, scene);
}
let projectionPicker;
if (options.projectionPicker) {
projectionPicker = new ProjectionPicker(toolbar, scene);
}
// BaseLayerPicker
let baseLayerPicker;
let baseLayerPickerDropDown;
if (createBaseLayerPicker) {
const imageryProviderViewModels = defaultValue(
options.imageryProviderViewModels,
createDefaultImageryProviderViewModels()
);
const terrainProviderViewModels = defaultValue(
options.terrainProviderViewModels,
createDefaultTerrainProviderViewModels()
);
baseLayerPicker = new BaseLayerPicker(toolbar, {
globe: scene.globe,
imageryProviderViewModels: imageryProviderViewModels,
selectedImageryProviderViewModel:
options.selectedImageryProviderViewModel,
terrainProviderViewModels: terrainProviderViewModels,
selectedTerrainProviderViewModel:
options.selectedTerrainProviderViewModel,
});
//Grab the dropdown for resize code.
const elements = toolbar.getElementsByClassName(
"cesium-baseLayerPicker-dropDown"
);
baseLayerPickerDropDown = elements[0];
}
// These need to be set after the BaseLayerPicker is created in order to take effect
if (defined(options.imageryProvider) && options.imageryProvider !== false) {
if (createBaseLayerPicker) {
baseLayerPicker.viewModel.selectedImagery = undefined;
}
scene.imageryLayers.removeAll();
scene.imageryLayers.addImageryProvider(options.imageryProvider);
}
if (defined(options.terrainProvider)) {
if (createBaseLayerPicker) {
baseLayerPicker.viewModel.selectedTerrain = undefined;
}
scene.terrainProvider = options.terrainProvider;
}
// Navigation Help Button
let navigationHelpButton;
if (
!defined(options.navigationHelpButton) ||
options.navigationHelpButton !== false
) {
let showNavHelp = true;
try {
//window.localStorage is null if disabled in Firefox or undefined in browsers with implementation
if (defined(window.localStorage)) {
const hasSeenNavHelp = window.localStorage.getItem(
"cesium-hasSeenNavHelp"
);
if (defined(hasSeenNavHelp) && Boolean(hasSeenNavHelp)) {
showNavHelp = false;
} else {
window.localStorage.setItem("cesium-hasSeenNavHelp", "true");
}
}
} catch (e) {
//Accessing window.localStorage throws if disabled in Chrome
//window.localStorage.setItem throws if in Safari private browsing mode or in any browser if we are over quota.
}
navigationHelpButton = new NavigationHelpButton({
container: toolbar,
instructionsInitiallyVisible: defaultValue(
options.navigationInstructionsInitiallyVisible,
showNavHelp
),
});
}
// Animation
let animation;
if (!defined(options.animation) || options.animation !== false) {
const animationContainer = document.createElement("div");
animationContainer.className = "cesium-viewer-animationContainer";
viewerContainer.appendChild(animationContainer);
animation = new Animation(
animationContainer,
new AnimationViewModel(clockViewModel)
);
}
// Timeline
let timeline;
if (!defined(options.timeline) || options.timeline !== false) {
const timelineContainer = document.createElement("div");
timelineContainer.className = "cesium-viewer-timelineContainer";
viewerContainer.appendChild(timelineContainer);
timeline = new Timeline(timelineContainer, clock);
timeline.addEventListener("settime", onTimelineScrubfunction, false);
timeline.zoomTo(clock.startTime, clock.stopTime);
}
// Fullscreen
let fullscreenButton;
let fullscreenSubscription;
let fullscreenContainer;
if (
!defined(options.fullscreenButton) ||
options.fullscreenButton !== false
) {
fullscreenContainer = document.createElement("div");
fullscreenContainer.className = "cesium-viewer-fullscreenContainer";
viewerContainer.appendChild(fullscreenContainer);
fullscreenButton = new FullscreenButton(
fullscreenContainer,
options.fullscreenElement
);
//Subscribe to fullscreenButton.viewModel.isFullscreenEnabled so
//that we can hide/show the button as well as size the timeline.
fullscreenSubscription = subscribeAndEvaluate(
fullscreenButton.viewModel,
"isFullscreenEnabled",
function (isFullscreenEnabled) {
fullscreenContainer.style.display = isFullscreenEnabled
? "block"
: "none";
if (defined(timeline)) {
timeline.container.style.right = `${fullscreenContainer.clientWidth}px`;
timeline.resize();
}
}
);
}
// VR
let vrButton;
let vrSubscription;
let vrModeSubscription;
if (options.vrButton) {
const vrContainer = document.createElement("div");
vrContainer.className = "cesium-viewer-vrContainer";
viewerContainer.appendChild(vrContainer);
vrButton = new VRButton(vrContainer, scene, options.fullScreenElement);
vrSubscription = subscribeAndEvaluate(
vrButton.viewModel,
"isVREnabled",
function (isVREnabled) {
vrContainer.style.display = isVREnabled ? "block" : "none";
if (defined(fullscreenButton)) {
vrContainer.style.right = `${fullscreenContainer.clientWidth}px`;
}
if (defined(timeline)) {
timeline.container.style.right = `${vrContainer.clientWidth}px`;
timeline.resize();
}
}
);
vrModeSubscription = subscribeAndEvaluate(
vrButton.viewModel,
"isVRMode",
function (isVRMode) {
enableVRUI(that, isVRMode);
}
);
}
//Assign all properties to this instance. No "this" assignments should
//take place above this line.
this._baseLayerPickerDropDown = baseLayerPickerDropDown;
this._fullscreenSubscription = fullscreenSubscription;
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;
this._homeButton = homeButton;
this._sceneModePicker = sceneModePicker;
this._projectionPicker = projectionPicker;
this._baseLayerPicker = baseLayerPicker;
this._navigationHelpButton = navigationHelpButton;
this._animation = animation;
this._timeline = timeline;
this._fullscreenButton = fullscreenButton;
this._vrButton = vrButton;
this._geocoder = geocoder;
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",
]);
//Listen to data source events in order to track clock changes.
eventHelper.add(
dataSourceCollection.dataSourceAdded,
Viewer.prototype._onDataSourceAdded,
this
);
eventHelper.add(
dataSourceCollection.dataSourceRemoved,
Viewer.prototype._onDataSourceRemoved,
this
);
// 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.
// 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,
Viewer.prototype._dataSourceAdded,
this
);
eventHelper.add(
dataSourceCollection.dataSourceRemoved,
Viewer.prototype._dataSourceRemoved,
this
);
// Subscribe to left clicks and zoom to the picked object.
function pickAndTrackObject(e) {
const entity = pickEntity(that, e);
if (defined(entity)) {
//Only track the entity if it has a valid position at the current time.
if (
Property.getValueOrUndefined(entity.position, that.clock.currentTime)
) {
that.trackedEntity = entity;
} else {
that.zoomTo(entity);
}
} else if (defined(that.trackedEntity)) {
that.trackedEntity = undefined;
}
}
function pickAndSelectObject(e) {
that.selectedEntity = pickEntity(that, e);
}
cesiumWidget.screenSpaceEventHandler.setInputAction(
pickAndSelectObject,
ScreenSpaceEventType.LEFT_CLICK
);
cesiumWidget.screenSpaceEventHandler.setInputAction(
pickAndTrackObject,
ScreenSpaceEventType.LEFT_DOUBLE_CLICK
);
}
Object.defineProperties(Viewer.prototype, {
/**
* Gets the parent container.
* @memberof Viewer.prototype
* @type {Element}
* @readonly
*/
container: {
get: function () {
return this._container;
},
},
/**
* Gets the DOM element for the area at the bottom of the window containing the
* {@link CreditDisplay} and potentially other things.
* @memberof Viewer.prototype
* @type {Element}
* @readonly
*/
bottomContainer: {
get: function () {
return this._bottomContainer;
},
},
/**
* Gets the CesiumWidget.
* @memberof Viewer.prototype
* @type {CesiumWidget}
* @readonly
*/
cesiumWidget: {
get: function () {
return this._cesiumWidget;
},
},
/**
* Gets the selection indicator.
* @memberof Viewer.prototype
* @type {SelectionIndicator}
* @readonly
*/
selectionIndicator: {
get: function () {
return this._selectionIndicator;
},
},
/**
* Gets the info box.
* @memberof Viewer.prototype
* @type {InfoBox}
* @readonly
*/
infoBox: {
get: function () {
return this._infoBox;
},
},
/**
* Gets the Geocoder.
* @memberof Viewer.prototype
* @type {Geocoder}
* @readonly
*/
geocoder: {
get: function () {
return this._geocoder;
},
},
/**
* Gets the HomeButton.
* @memberof Viewer.prototype
* @type {HomeButton}
* @readonly
*/
homeButton: {
get: function () {
return this._homeButton;
},
},
/**
* Gets the SceneModePicker.
* @memberof Viewer.prototype
* @type {SceneModePicker}
* @readonly
*/
sceneModePicker: {
get: function () {
return this._sceneModePicker;
},
},
/**
* Gets the ProjectionPicker.
* @memberof Viewer.prototype
* @type {ProjectionPicker}
* @readonly
*/
projectionPicker: {
get: function () {
return this._projectionPicker;
},
},
/**
* Gets the BaseLayerPicker.
* @memberof Viewer.prototype
* @type {BaseLayerPicker}
* @readonly
*/
baseLayerPicker: {
get: function () {
return this._baseLayerPicker;
},
},
/**
* Gets the NavigationHelpButton.
* @memberof Viewer.prototype
* @type {NavigationHelpButton}
* @readonly
*/
navigationHelpButton: {
get: function () {
return this._navigationHelpButton;
},
},
/**
* Gets the Animation widget.
* @memberof Viewer.prototype
* @type {Animation}
* @readonly
*/
animation: {
get: function () {
return this._animation;
},
},
/**
* Gets the Timeline widget.
* @memberof Viewer.prototype
* @type {Timeline}
* @readonly
*/
timeline: {
get: function () {
return this._timeline;
},
},
/**
* Gets the FullscreenButton.
* @memberof Viewer.prototype
* @type {FullscreenButton}
* @readonly
*/
fullscreenButton: {
get: function () {
return this._fullscreenButton;
},
},
/**
* Gets the VRButton.
* @memberof Viewer.prototype
* @type {VRButton}
* @readonly
*/
vrButton: {
get: function () {
return this._vrButton;
},
},
/**
* Gets the display used for {@link DataSource} visualization.
* @memberof Viewer.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 Viewer#dataSourceDisplay}.
* @memberof Viewer.prototype
* @type {EntityCollection}
* @readonly
*/
entities: {
get: function () {
return this._dataSourceDisplay.defaultDataSource.entities;
},
},
/**
* Gets the set of {@link DataSource} instances to be visualized.
* @memberof Viewer.prototype
* @type {DataSourceCollection}
* @readonly
*/
dataSources: {
get: function () {
return this._dataSourceCollection;
},
},
/**
* Gets the canvas.
* @memberof Viewer.prototype
* @type {HTMLCanvasElement}
* @readonly
*/
canvas: {
get: function () {
return this._cesiumWidget.canvas;
},
},
/**
* Gets the scene.
* @memberof Viewer.prototype
* @type {Scene}
* @readonly
*/
scene: {
get: function () {
return this._cesiumWidget.scene;
},
},
/**
* Determines if shadows are cast by light sources.
* @memberof Viewer.prototype
* @type {Boolean}
*/
shadows: {
get: function () {
return this.scene.shadowMap.enabled;
},
set: function (value) {
this.scene.shadowMap.enabled = value;
},
},
/**
* Determines if the terrain casts or shadows from light sources.
* @memberof Viewer.prototype
* @type {ShadowMode}
*/
terrainShadows: {
get: function () {
return this.scene.globe.shadows;
},
set: function (value) {
this.scene.globe.shadows = value;
},
},
/**
* Get the scene's shadow map
* @memberof Viewer.prototype
* @type {ShadowMap}
* @readonly
*/
shadowMap: {
get: function () {
return this.scene.shadowMap;
},
},
/**
* Gets the collection of image layers that will be rendered on the globe.
* @memberof Viewer.prototype
*
* @type {ImageryLayerCollection}
* @readonly
*/
imageryLayers: {
get: function () {
return this.scene.imageryLayers;
},
},
/**
* The terrain provider providing surface geometry for the globe.
* @memberof Viewer.prototype
*
* @type {TerrainProvider}
*/
terrainProvider: {
get: function () {
return this.scene.terrainProvider;
},
set: function (terrainProvider) {
this.scene.terrainProvider = terrainProvider;
},
},
/**
* Gets the camera.
* @memberof Viewer.prototype
*
* @type {Camera}
* @readonly
*/
camera: {
get: function () {
return this.scene.camera;
},
},
/**
* Gets the post-process stages.
* @memberof Viewer.prototype
*
* @type {PostProcessStageCollection}
* @readonly
*/
postProcessStages: {
get: function () {
return this.scene.postProcessStages;
},
},
/**
* Gets the clock.
* @memberof Viewer.prototype
* @type {Clock}
* @readonly
*/
clock: {
get: function () {
return this._clockViewModel.clock;
},
},
/**
* Gets the clock view model.
* @memberof Viewer.prototype
* @type {ClockViewModel}
* @readonly
*/
clockViewModel: {
get: function () {
return this._clockViewModel;
},
},
/**
* Gets the screen space event handler.
* @memberof Viewer.prototype
* @type {ScreenSpaceEventHandler}
* @readonly
*/
screenSpaceEventHandler: {
get: function () {
return this._cesiumWidget.screenSpaceEventHandler;
},
},
/**
* Gets or sets the target frame rate of the widget when useDefaultRenderLoop
* is true. If undefined, the browser's {@link requestAnimationFrame} implementation
* determines the frame rate. If defined, this value must be greater than 0. A value higher
* than the underlying requestAnimationFrame implementation will have no effect.
* @memberof Viewer.prototype
*
* @type {Number}
*/
targetFrameRate: {
get: function () {
return this._cesiumWidget.targetFrameRate;
},
set: function (value) {
this._cesiumWidget.targetFrameRate = value;
},
},
/**
* Gets or sets whether or not this widget should control the render loop.
* If set to true the widget will use {@link requestAnimationFrame} to
* perform rendering and resizing of the widget, as well as drive the
* simulation clock. If set to false, you must manually call the
* resize
, render
methods
* as part of a custom render loop. If an error occurs during rendering, {@link Scene}'s
* renderError
event will be raised and this property
* will be set to false. It must be set back to true to continue rendering
* after the error.
* @memberof Viewer.prototype
*
* @type {Boolean}
*/
useDefaultRenderLoop: {
get: function () {
return this._cesiumWidget.useDefaultRenderLoop;
},
set: function (value) {
this._cesiumWidget.useDefaultRenderLoop = value;
},
},
/**
* Gets or sets a scaling factor for rendering resolution. Values less than 1.0 can improve
* performance on less powerful devices while values greater than 1.0 will render at a higher
* resolution and then scale down, resulting in improved visual fidelity.
* For example, if the widget is laid out at a size of 640x480, setting this value to 0.5
* will cause the scene to be rendered at 320x240 and then scaled up while setting
* it to 2.0 will cause the scene to be rendered at 1280x960 and then scaled down.
* @memberof Viewer.prototype
*
* @type {Number}
* @default 1.0
*/
resolutionScale: {
get: function () {
return this._cesiumWidget.resolutionScale;
},
set: function (value) {
this._cesiumWidget.resolutionScale = value;
},
},
/**
* Boolean flag indicating if the browser's recommended resolution is used.
* If true, the browser's device pixel ratio is ignored and 1.0 is used instead,
* effectively rendering based on CSS pixels instead of device pixels. This can improve
* performance on less powerful devices that have high pixel density. When false, rendering
* will be in device pixels. {@link Viewer#resolutionScale} will still take effect whether
* this flag is true or false.
* @memberof Viewer.prototype
*
* @type {Boolean}
* @default true
*/
useBrowserRecommendedResolution: {
get: function () {
return this._cesiumWidget.useBrowserRecommendedResolution;
},
set: function (value) {
this._cesiumWidget.useBrowserRecommendedResolution = value;
},
},
/**
* 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 Viewer.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 Viewer.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 or sets the object instance for which to display a selection indicator.
*
* If a user interactively picks a Cesium3DTilesFeature instance, then this property
* will contain a transient Entity instance with a property named "feature" that is
* the instance that was picked.
* @memberof Viewer.prototype
* @type {Entity | undefined}
*/
selectedEntity: {
get: function () {
return this._selectedEntity;
},
set: function (value) {
if (this._selectedEntity !== value) {
this._selectedEntity = value;
const selectionIndicatorViewModel = defined(this._selectionIndicator)
? this._selectionIndicator.viewModel
: undefined;
if (defined(value)) {
if (defined(selectionIndicatorViewModel)) {
selectionIndicatorViewModel.animateAppear();
}
} else if (defined(selectionIndicatorViewModel)) {
// Leave the info text in place here, it is needed during the exit animation.
selectionIndicatorViewModel.animateDepart();
}
this._selectedEntityChanged.raiseEvent(value);
}
},
},
/**
* Gets the event that is raised when the selected entity changes.
* @memberof Viewer.prototype
* @type {Event}
* @readonly
*/
selectedEntityChanged: {
get: function () {
return this._selectedEntityChanged;
},
},
/**
* Gets the event that is raised when the tracked entity changes.
* @memberof Viewer.prototype
* @type {Event}
* @readonly
*/
trackedEntityChanged: {
get: function () {
return this._trackedEntityChanged;
},
},
/**
* Gets or sets the data source to track with the viewer's clock.
* @memberof Viewer.prototype
* @type {DataSource}
*/
clockTrackedDataSource: {
get: function () {
return this._clockTrackedDataSource;
},
set: function (value) {
if (this._clockTrackedDataSource !== value) {
this._clockTrackedDataSource = value;
trackDataSourceClock(this._timeline, this.clock, value);
}
},
},
});
/**
* Extends the base viewer functionality with the provided mixin.
* A mixin may add additional properties, functions, or other behavior
* to the provided viewer instance.
*
* @param {Viewer.ViewerMixin} mixin The Viewer mixin to add to this instance.
* @param {Object} [options] The options object to be passed to the mixin function.
*
* @see viewerDragDropMixin
*/
Viewer.prototype.extend = function (mixin, options) {
//>>includeStart('debug', pragmas.debug);
if (!defined(mixin)) {
throw new DeveloperError("mixin is required.");
}
//>>includeEnd('debug')
mixin(this, options);
};
/**
* Resizes the widget to match the container size.
* This function is called automatically as needed unless
* useDefaultRenderLoop
is set to false.
*/
Viewer.prototype.resize = function () {
const cesiumWidget = this._cesiumWidget;
const container = this._container;
const width = container.clientWidth;
const height = container.clientHeight;
const animationExists = defined(this._animation);
const timelineExists = defined(this._timeline);
cesiumWidget.resize();
if (width === this._lastWidth && height === this._lastHeight) {
return;
}
const panelMaxHeight = height - 125;
const baseLayerPickerDropDown = this._baseLayerPickerDropDown;
if (defined(baseLayerPickerDropDown)) {
baseLayerPickerDropDown.style.maxHeight = `${panelMaxHeight}px`;
}
if (defined(this._geocoder)) {
const geocoderSuggestions = this._geocoder.searchSuggestionsContainer;
geocoderSuggestions.style.maxHeight = `${panelMaxHeight}px`;
}
if (defined(this._infoBox)) {
this._infoBox.viewModel.maxHeight = panelMaxHeight;
}
const timeline = this._timeline;
let animationContainer;
let animationWidth = 0;
let creditLeft = 0;
let creditBottom = 0;
if (
animationExists &&
window.getComputedStyle(this._animation.container).visibility !== "hidden"
) {
const lastWidth = this._lastWidth;
animationContainer = this._animation.container;
if (width > 900) {
animationWidth = 169;
if (lastWidth <= 900) {
animationContainer.style.width = "169px";
animationContainer.style.height = "112px";
this._animation.resize();
}
} else if (width >= 600) {
animationWidth = 136;
if (lastWidth < 600 || lastWidth > 900) {
animationContainer.style.width = "136px";
animationContainer.style.height = "90px";
this._animation.resize();
}
} else {
animationWidth = 106;
if (lastWidth > 600 || lastWidth === 0) {
animationContainer.style.width = "106px";
animationContainer.style.height = "70px";
this._animation.resize();
}
}
creditLeft = animationWidth + 5;
}
if (
timelineExists &&
window.getComputedStyle(this._timeline.container).visibility !== "hidden"
) {
const fullscreenButton = this._fullscreenButton;
const vrButton = this._vrButton;
const timelineContainer = timeline.container;
const timelineStyle = timelineContainer.style;
creditBottom = timelineContainer.clientHeight + 3;
timelineStyle.left = `${animationWidth}px`;
let pixels = 0;
if (defined(fullscreenButton)) {
pixels += fullscreenButton.container.clientWidth;
}
if (defined(vrButton)) {
pixels += vrButton.container.clientWidth;
}
timelineStyle.right = `${pixels}px`;
timeline.resize();
}
this._bottomContainer.style.left = `${creditLeft}px`;
this._bottomContainer.style.bottom = `${creditBottom}px`;
this._lastWidth = width;
this._lastHeight = height;
};
/**
* This forces the widget to re-think its layout, including
* widget sizes and credit placement.
*/
Viewer.prototype.forceResize = function () {
this._lastWidth = 0;
this.resize();
};
/**
* Renders the scene. This function is called automatically
* unless useDefaultRenderLoop
is set to false;
*/
Viewer.prototype.render = function () {
this._cesiumWidget.render();
};
/**
* @returns {Boolean} true if the object has been destroyed, false otherwise.
*/
Viewer.prototype.isDestroyed = function () {
return false;
};
/**
* Destroys the widget. Should be called if permanently
* removing the widget from layout.
*/
Viewer.prototype.destroy = function () {
let i;
this.screenSpaceEventHandler.removeInputAction(
ScreenSpaceEventType.LEFT_CLICK
);
this.screenSpaceEventHandler.removeInputAction(
ScreenSpaceEventType.LEFT_DOUBLE_CLICK
);
// 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);
this._eventHelper.removeAll();
if (defined(this._geocoder)) {
this._geocoder = this._geocoder.destroy();
}
if (defined(this._homeButton)) {
this._homeButton = this._homeButton.destroy();
}
if (defined(this._sceneModePicker)) {
this._sceneModePicker = this._sceneModePicker.destroy();
}
if (defined(this._projectionPicker)) {
this._projectionPicker = this._projectionPicker.destroy();
}
if (defined(this._baseLayerPicker)) {
this._baseLayerPicker = this._baseLayerPicker.destroy();
}
if (defined(this._animation)) {
this._element.removeChild(this._animation.container);
this._animation = this._animation.destroy();
}
if (defined(this._timeline)) {
this._timeline.removeEventListener(
"settime",
onTimelineScrubfunction,
false
);
this._element.removeChild(this._timeline.container);
this._timeline = this._timeline.destroy();
}
if (defined(this._fullscreenButton)) {
this._fullscreenSubscription.dispose();
this._element.removeChild(this._fullscreenButton.container);
this._fullscreenButton = this._fullscreenButton.destroy();
}
if (defined(this._vrButton)) {
this._vrSubscription.dispose();
this._vrModeSubscription.dispose();
this._element.removeChild(this._vrButton.container);
this._vrButton = this._vrButton.destroy();
}
if (defined(this._infoBox)) {
this._element.removeChild(this._infoBox.container);
this._infoBox = this._infoBox.destroy();
}
if (defined(this._selectionIndicator)) {
this._element.removeChild(this._selectionIndicator.container);
this._selectionIndicator = this._selectionIndicator.destroy();
}
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);
};
/**
* @private
*/
Viewer.prototype._dataSourceAdded = function (
dataSourceCollection,
dataSource
) {
const entityCollection = dataSource.entities;
entityCollection.collectionChanged.addEventListener(
Viewer.prototype._onEntityCollectionChanged,
this
);
};
/**
* @private
*/
Viewer.prototype._dataSourceRemoved = function (
dataSourceCollection,
dataSource
) {
const entityCollection = dataSource.entities;
entityCollection.collectionChanged.removeEventListener(
Viewer.prototype._onEntityCollectionChanged,
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
) {
this.selectedEntity = undefined;
}
}
};
/**
* @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,
false,
boundingSphereScratch
);
if (trackedState === BoundingSphereState.DONE) {
entityView.update(time, boundingSphereScratch);
}
}
let position;
let enableCamera = false;
const selectedEntity = this.selectedEntity;
const showSelection = defined(selectedEntity) && this._enableInfoOrSelection;
if (
showSelection &&
selectedEntity.isShowing &&
selectedEntity.isAvailable(time)
) {
const state = this._dataSourceDisplay.getBoundingSphere(
selectedEntity,
true,
boundingSphereScratch
);
if (state !== BoundingSphereState.FAILED) {
position = boundingSphereScratch.center;
} else if (defined(selectedEntity.position)) {
position = selectedEntity.position.getValue(time, position);
}
enableCamera = defined(position);
}
const selectionIndicatorViewModel = defined(this._selectionIndicator)
? this._selectionIndicator.viewModel
: undefined;
if (defined(selectionIndicatorViewModel)) {
selectionIndicatorViewModel.position = Cartesian3.clone(
position,
selectionIndicatorViewModel.position
);
selectionIndicatorViewModel.showSelection = showSelection && enableCamera;
selectionIndicatorViewModel.update();
}
const infoBoxViewModel = defined(this._infoBox)
? this._infoBox.viewModel
: undefined;
if (defined(infoBoxViewModel)) {
infoBoxViewModel.showInfo = showSelection;
infoBoxViewModel.enableCamera = enableCamera;
infoBoxViewModel.isCameraTracking =
this.trackedEntity === this.selectedEntity;
if (showSelection) {
infoBoxViewModel.titleText = defaultValue(
selectedEntity.name,
selectedEntity.id
);
infoBoxViewModel.description = Property.getValueOrDefault(
selectedEntity.description,
time,
""
);
} else {
infoBoxViewModel.titleText = "";
infoBoxViewModel.description = "";
}
}
};
/**
* @private
*/
Viewer.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;
}
if (this.selectedEntity === removedObject) {
this.selectedEntity = undefined;
}
}
};
/**
* @private
*/
Viewer.prototype._onInfoBoxCameraClicked = function (infoBoxViewModel) {
if (
infoBoxViewModel.isCameraTracking &&
this.trackedEntity === this.selectedEntity
) {
this.trackedEntity = undefined;
} else {
const selectedEntity = this.selectedEntity;
const position = selectedEntity.position;
if (defined(position)) {
this.trackedEntity = this.selectedEntity;
} else {
this.zoomTo(this.selectedEntity);
}
}
};
/**
* @private
*/
Viewer.prototype._clearTrackedObject = function () {
this.trackedEntity = undefined;
};
/**
* @private
*/
Viewer.prototype._onInfoBoxClockClicked = function (infoBoxViewModel) {
this.selectedEntity = undefined;
};
/**
* @private
*/
Viewer.prototype._clearObjects = function () {
this.trackedEntity = undefined;
this.selectedEntity = undefined;
};
/**
* @private
*/
Viewer.prototype._onDataSourceChanged = function (dataSource) {
if (this.clockTrackedDataSource === dataSource) {
trackDataSourceClock(this.timeline, this.clock, dataSource);
}
};
/**
* @private
*/
Viewer.prototype._onDataSourceAdded = function (
dataSourceCollection,
dataSource
) {
if (this._automaticallyTrackDataSourceClocks) {
this.clockTrackedDataSource = dataSource;
}
const id = dataSource.entities.id;
const removalFunc = this._eventHelper.add(
dataSource.changedEvent,
Viewer.prototype._onDataSourceChanged,
this
);
this._dataSourceChangedListeners[id] = removalFunc;
};
/**
* @private
*/
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;
}
}
};
/**
* 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.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.