cesium
Version:
Cesium is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,172 lines (1,066 loc) • 78.9 kB
JavaScript
/*global define*/
define([
'../../Core/BoundingSphere',
'../../Core/Cartesian3',
'../../Core/defaultValue',
'../../Core/defined',
'../../Core/defineProperties',
'../../Core/destroyObject',
'../../Core/DeveloperError',
'../../Core/EventHelper',
'../../Core/Fullscreen',
'../../Core/isArray',
'../../Core/Matrix4',
'../../Core/Rectangle',
'../../Core/ScreenSpaceEventType',
'../../DataSources/BoundingSphereState',
'../../DataSources/ConstantPositionProperty',
'../../DataSources/DataSourceCollection',
'../../DataSources/DataSourceDisplay',
'../../DataSources/Entity',
'../../DataSources/EntityView',
'../../DataSources/Property',
'../../Scene/ImageryLayer',
'../../Scene/SceneMode',
'../../ThirdParty/knockout',
'../../ThirdParty/when',
'../Animation/Animation',
'../Animation/AnimationViewModel',
'../BaseLayerPicker/BaseLayerPicker',
'../BaseLayerPicker/createDefaultImageryProviderViewModels',
'../BaseLayerPicker/createDefaultTerrainProviderViewModels',
'../CesiumWidget/CesiumWidget',
'../ClockViewModel',
'../FullscreenButton/FullscreenButton',
'../Geocoder/Geocoder',
'../getElement',
'../HomeButton/HomeButton',
'../InfoBox/InfoBox',
'../NavigationHelpButton/NavigationHelpButton',
'../SceneModePicker/SceneModePicker',
'../SelectionIndicator/SelectionIndicator',
'../subscribeAndEvaluate',
'../Timeline/Timeline',
'../VRButton/VRButton'
], function(
BoundingSphere,
Cartesian3,
defaultValue,
defined,
defineProperties,
destroyObject,
DeveloperError,
EventHelper,
Fullscreen,
isArray,
Matrix4,
Rectangle,
ScreenSpaceEventType,
BoundingSphereState,
ConstantPositionProperty,
DataSourceCollection,
DataSourceDisplay,
Entity,
EntityView,
Property,
ImageryLayer,
SceneMode,
knockout,
when,
Animation,
AnimationViewModel,
BaseLayerPicker,
createDefaultImageryProviderViewModels,
createDefaultTerrainProviderViewModels,
CesiumWidget,
ClockViewModel,
FullscreenButton,
Geocoder,
getElement,
HomeButton,
InfoBox,
NavigationHelpButton,
SceneModePicker,
SelectionIndicator,
subscribeAndEvaluate,
Timeline,
VRButton) {
'use strict';
var boundingSphereScratch = new BoundingSphere();
function onTimelineScrubfunction(e) {
var clock = e.clock;
clock.currentTime = e.timeJulian;
clock.shouldAnimate = false;
}
function pickEntity(viewer, e) {
var picked = viewer.scene.pick(e.position);
if (defined(picked)) {
var id = defaultValue(picked.id, picked.primitive.id);
if (id instanceof Entity) {
return id;
}
}
// No regular entity picked. Try picking features from imagery layers.
if (defined(viewer.scene.globe)) {
return pickImageryLayerFeature(viewer, e.position);
}
}
function trackDataSourceClock(timeline, clock, dataSource) {
if (defined(dataSource)) {
var dataSourceClock = dataSource.clock;
if (defined(dataSourceClock)) {
dataSourceClock.getValue(clock);
if (defined(timeline)) {
timeline.updateFromClock();
timeline.zoomTo(dataSourceClock.startTime, dataSourceClock.stopTime);
}
}
}
}
var cartesian3Scratch = new Cartesian3();
function pickImageryLayerFeature(viewer, windowPosition) {
var scene = viewer.scene;
var pickRay = scene.camera.getPickRay(windowPosition);
var imageryLayerFeaturePromise = scene.imageryLayers.pickImageryLayerFeatures(pickRay, scene);
if (!defined(imageryLayerFeaturePromise)) {
return;
}
// Imagery layer feature picking is asynchronous, so put up a message while loading.
var loadingMessage = new Entity({
id : 'Loading...',
description : 'Loading feature information...'
});
when(imageryLayerFeaturePromise, function(features) {
// Has this async pick been superseded by a later one?
if (viewer.selectedEntity !== loadingMessage) {
return;
}
if (!defined(features) || features.length === 0) {
viewer.selectedEntity = createNoFeaturesEntity();
return;
}
// Select the first feature.
var feature = features[0];
var entity = new Entity({
id : feature.name,
description : feature.description
});
if (defined(feature.position)) {
var ecfPosition = viewer.scene.globe.ellipsoid.cartographicToCartesian(feature.position, cartesian3Scratch);
entity.position = new ConstantPositionProperty(ecfPosition);
}
viewer.selectedEntity = entity;
}, function() {
// Has this async pick been superseded by a later one?
if (viewer.selectedEntity !== loadingMessage) {
return;
}
viewer.selectedEntity = createNoFeaturesEntity();
});
return loadingMessage;
}
function createNoFeaturesEntity() {
return new Entity({
id : 'None',
description : 'No features found.'
});
}
function enableVRUI(viewer, enabled) {
var geocoder = viewer._geocoder;
var homeButton = viewer._homeButton;
var sceneModePicker = viewer._sceneModePicker;
var baseLayerPicker = viewer._baseLayerPicker;
var animation = viewer._animation;
var timeline = viewer._timeline;
var fullscreenButton = viewer._fullscreenButton;
var infoBox = viewer._infoBox;
var selectionIndicator = viewer._selectionIndicator;
var visibility = enabled ? 'hidden' : 'visible';
if (defined(geocoder)) {
geocoder.container.style.visibility = visibility;
}
if (defined(homeButton)) {
homeButton.container.style.visibility = visibility;
}
if(defined(sceneModePicker)) {
sceneModePicker.container.style.visibility = visibility;
}
if(defined(baseLayerPicker)) {
baseLayerPicker.container.style.visibility = visibility;
}
if (defined(animation)) {
animation.container.style.visibility = visibility;
}
if (defined(timeline)) {
timeline.container.style.visibility = visibility;
}
if (defined(fullscreenButton) && fullscreenButton.viewModel.isFullscreenEnabled) {
fullscreenButton.container.style.visibility = visibility;
}
if (defined(infoBox)) {
infoBox.container.style.visibility = visibility;
}
if (defined(selectionIndicator)) {
selectionIndicator.container.style.visibility = visibility;
}
if (viewer._container) {
var right = enabled || !defined(fullscreenButton) ? 0 : fullscreenButton.container.clientWidth;
viewer._vrButton.container.style.right = right + 'px';
viewer.forceResize();
}
}
/**
* 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 {Object} [options] Object with the following properties:
* @param {Boolean} [options.animation=true] If set to false, the Animation widget will not be created.
* @param {Boolean} [options.baseLayerPicker=true] If set to false, the BaseLayerPicker widget will not be created.
* @param {Boolean} [options.fullscreenButton=true] If set to false, the FullscreenButton widget will not be created.
* @param {Boolean} [options.vrButton=false] If set to true, the VRButton widget will be created.
* @param {Boolean} [options.geocoder=true] If set to false, the Geocoder widget will not be created.
* @param {Boolean} [options.homeButton=true] If set to false, the HomeButton widget will not be created.
* @param {Boolean} [options.infoBox=true] If set to false, the InfoBox widget will not be created.
* @param {Boolean} [options.sceneModePicker=true] If set to false, the SceneModePicker widget will not be created.
* @param {Boolean} [options.selectionIndicator=true] If set to false, the SelectionIndicator widget will not be created.
* @param {Boolean} [options.timeline=true] If set to false, the Timeline widget will not be created.
* @param {Boolean} [options.navigationHelpButton=true] If set to false, the navigation help button will not be created.
* @param {Boolean} [options.navigationInstructionsInitiallyVisible=true] True if the navigation instructions should initially be visible, or false if the should not be shown until the user explicitly clicks the button.
* @param {Boolean} [options.scene3DOnly=false] When <code>true</code>, each geometry instance will only be rendered in 3D to save GPU memory.
* @param {Clock} [options.clock=new Clock()] The clock to use to control current time.
* @param {ProviderViewModel} [options.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 options.baseLayerPicker is set to true.
* @param {ProviderViewModel[]} [options.imageryProviderViewModels=createDefaultImageryProviderViewModels()] The array of ProviderViewModels to be selectable from the BaseLayerPicker. This value is only valid if options.baseLayerPicker is set to true.
* @param {ProviderViewModel} [options.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 options.baseLayerPicker is set to true.
* @param {ProviderViewModel[]} [options.terrainProviderViewModels=createDefaultTerrainProviderViewModels()] The array of ProviderViewModels to be selectable from the BaseLayerPicker. This value is only valid if options.baseLayerPicker is set to true.
* @param {ImageryProvider} [options.imageryProvider=new BingMapsImageryProvider()] The imagery provider to use. This value is only valid if options.baseLayerPicker is set to false.
* @param {TerrainProvider} [options.terrainProvider=new EllipsoidTerrainProvider()] The terrain provider to use
* @param {SkyBox} [options.skyBox] The skybox used to render the stars. When <code>undefined</code>, the default stars are used.
* @param {SkyAtmosphere} [options.skyAtmosphere] Blue sky, and the glow around the Earth's limb. Set to <code>false</code> to turn it off.
* @param {Element|String} [options.fullscreenElement=document.body] The element or id to be placed into fullscreen mode when the full screen button is pressed.
* @param {Boolean} [options.useDefaultRenderLoop=true] True if this widget should control the render loop, false otherwise.
* @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 {Object} [options.contextOptions] Context and WebGL creation properties corresponding to <code>options</code> passed to {@link Scene}.
* @param {SceneMode} [options.sceneMode=SceneMode.SCENE3D] The initial scene mode.
* @param {MapProjection} [options.mapProjection=new GeographicProjection()] The map projection to use in 2D and Columbus View modes.
* @param {Globe} [options.globe=new Globe(mapProjection.ellipsoid)] The globe to use in the scene. If set to <code>false</code>, no globe will be added.
* @param {Boolean} [options.orderIndependentTranslucency=true] If true and the configuration supports it, use order independent translucency.
* @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 {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 viewer is destroyed.
* @param {Number} [options.terrainExaggeration=1.0] A scalar used to exaggerate the terrain. Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
* @param {Boolean} [options.shadows=false] Determines if shadows are cast by the sun.
* @param {Boolean} [options.terrainShadows=false] Determines if the terrain casts shadows from the sun.
*
* @exception {DeveloperError} Element with id "container" does not exist in the document.
* @exception {DeveloperError} options.imageryProvider is not available when using the BaseLayerPicker widget, specify options.selectedImageryProviderViewModel instead.
* @exception {DeveloperError} options.terrainProvider is not available when using the BaseLayerPicker widget, specify options.selectedTerrainProviderViewModel instead.
* @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 http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Hello%20World.html|Cesium Sandcastle Hello World Demo}
*
* @example
* //Initialize the viewer widget with several custom options and mixins.
* var viewer = new Cesium.Viewer('cesiumContainer', {
* //Start in Columbus Viewer
* sceneMode : Cesium.SceneMode.COLUMBUS_VIEW,
* //Use standard Cesium terrain
* terrainProvider : new Cesium.CesiumTerrainProvider({
* url : 'https://assets.agi.com/stk-terrain/world'
* }),
* //Hide the base layer picker
* baseLayerPicker : false,
* //Use OpenStreetMaps
* imageryProvider : Cesium.createOpenStreetMapImageryProvider({
* url : 'https://a.tile.openstreetmap.org/'
* }),
* // Use high-res stars downloaded from https://github.com/AnalyticalGraphicsInc/cesium-assets
* 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);
var createBaseLayerPicker = (!defined(options.globe) || options.globe !== false) &&
(!defined(options.baseLayerPicker) || options.baseLayerPicker !== false);
//>>includeStart('debug', pragmas.debug);
// If using BaseLayerPicker, imageryProvider is an invalid option
if (createBaseLayerPicker && defined(options.imageryProvider)) {
throw new DeveloperError('options.imageryProvider is not available when using the BaseLayerPicker widget. \
Either specify options.selectedImageryProviderViewModel instead or set options.baseLayerPicker to false.');
}
// 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 using BaseLayerPicker, terrainProvider is an invalid option
if (createBaseLayerPicker && defined(options.terrainProvider)) {
throw new DeveloperError('options.terrainProvider is not available when using the BaseLayerPicker widget. \
Either specify options.selectedTerrainProviderViewModel instead or set options.baseLayerPicker to false.');
}
// 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')
var that = this;
var viewerContainer = document.createElement('div');
viewerContainer.className = 'cesium-viewer';
container.appendChild(viewerContainer);
// Cesium widget container
var cesiumWidgetContainer = document.createElement('div');
cesiumWidgetContainer.className = 'cesium-viewer-cesiumWidgetContainer';
viewerContainer.appendChild(cesiumWidgetContainer);
// Bottom container
var bottomContainer = document.createElement('div');
bottomContainer.className = 'cesium-viewer-bottom';
viewerContainer.appendChild(bottomContainer);
var scene3DOnly = defaultValue(options.scene3DOnly, false);
// Cesium widget
var cesiumWidget = new CesiumWidget(cesiumWidgetContainer, {
terrainProvider : options.terrainProvider,
imageryProvider : createBaseLayerPicker ? false : options.imageryProvider,
clock : options.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,
creditContainer : defined(options.creditContainer) ? options.creditContainer : bottomContainer,
scene3DOnly : scene3DOnly,
terrainExaggeration : options.terrainExaggeration,
shadows : options.shadows,
terrainShadows : options.terrainShadows
});
var dataSourceCollection = options.dataSources;
var destroyDataSourceCollection = false;
if (!defined(dataSourceCollection)) {
dataSourceCollection = new DataSourceCollection();
destroyDataSourceCollection = true;
}
var dataSourceDisplay = new DataSourceDisplay({
scene : cesiumWidget.scene,
dataSourceCollection : dataSourceCollection
});
var clock = cesiumWidget.clock;
var clockViewModel = new ClockViewModel(clock);
var eventHelper = new EventHelper();
eventHelper.add(clock.onTick, Viewer.prototype._onTick, this);
eventHelper.add(cesiumWidget.scene.morphStart, Viewer.prototype._clearTrackedObject, this);
// Selection Indicator
var selectionIndicator;
if (!defined(options.selectionIndicator) || options.selectionIndicator !== false) {
var selectionIndicatorContainer = document.createElement('div');
selectionIndicatorContainer.className = 'cesium-viewer-selectionIndicatorContainer';
viewerContainer.appendChild(selectionIndicatorContainer);
selectionIndicator = new SelectionIndicator(selectionIndicatorContainer, cesiumWidget.scene);
}
// Info Box
var infoBox;
if (!defined(options.infoBox) || options.infoBox !== false) {
var infoBoxContainer = document.createElement('div');
infoBoxContainer.className = 'cesium-viewer-infoBoxContainer';
viewerContainer.appendChild(infoBoxContainer);
infoBox = new InfoBox(infoBoxContainer);
var infoBoxViewModel = infoBox.viewModel;
eventHelper.add(infoBoxViewModel.cameraClicked, Viewer.prototype._onInfoBoxCameraClicked, this);
eventHelper.add(infoBoxViewModel.closeClicked, Viewer.prototype._onInfoBoxClockClicked, this);
}
// Main Toolbar
var toolbar = document.createElement('div');
toolbar.className = 'cesium-viewer-toolbar';
viewerContainer.appendChild(toolbar);
// Geocoder
var geocoder;
if (!defined(options.geocoder) || options.geocoder !== false) {
var geocoderContainer = document.createElement('div');
geocoderContainer.className = 'cesium-viewer-geocoderContainer';
toolbar.appendChild(geocoderContainer);
geocoder = new Geocoder({
container : geocoderContainer,
scene : cesiumWidget.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
var homeButton;
if (!defined(options.homeButton) || options.homeButton !== false) {
homeButton = new HomeButton(toolbar, cesiumWidget.scene);
if (defined(geocoder)) {
eventHelper.add(homeButton.viewModel.command.afterExecute, function() {
var 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.
if ((options.sceneModePicker === true) && scene3DOnly) {
throw new DeveloperError('options.sceneModePicker is not available when options.scene3DOnly is set to true.');
}
var sceneModePicker;
if (!scene3DOnly && (!defined(options.sceneModePicker) || options.sceneModePicker !== false)) {
sceneModePicker = new SceneModePicker(toolbar, cesiumWidget.scene);
}
// BaseLayerPicker
var baseLayerPicker;
var baseLayerPickerDropDown;
if (createBaseLayerPicker) {
var imageryProviderViewModels = defaultValue(options.imageryProviderViewModels, createDefaultImageryProviderViewModels());
var terrainProviderViewModels = defaultValue(options.terrainProviderViewModels, createDefaultTerrainProviderViewModels());
baseLayerPicker = new BaseLayerPicker(toolbar, {
globe : cesiumWidget.scene.globe,
imageryProviderViewModels : imageryProviderViewModels,
selectedImageryProviderViewModel : options.selectedImageryProviderViewModel,
terrainProviderViewModels : terrainProviderViewModels,
selectedTerrainProviderViewModel : options.selectedTerrainProviderViewModel
});
//Grab the dropdown for resize code.
var elements = toolbar.getElementsByClassName('cesium-baseLayerPicker-dropDown');
baseLayerPickerDropDown = elements[0];
}
// Navigation Help Button
var navigationHelpButton;
if (!defined(options.navigationHelpButton) || options.navigationHelpButton !== false) {
var showNavHelp = true;
try {
//window.localStorage is null if disabled in Firefox or undefined in browsers with implementation
if (defined(window.localStorage)) {
var 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
var animation;
if (!defined(options.animation) || options.animation !== false) {
var animationContainer = document.createElement('div');
animationContainer.className = 'cesium-viewer-animationContainer';
viewerContainer.appendChild(animationContainer);
animation = new Animation(animationContainer, new AnimationViewModel(clockViewModel));
}
// Timeline
var timeline;
if (!defined(options.timeline) || options.timeline !== false) {
var 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
var fullscreenButton;
var fullscreenSubscription;
if (!defined(options.fullscreenButton) || options.fullscreenButton !== false) {
var 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
var vrButton;
var vrSubscription;
var vrModeSubscription;
if (options.vrButton) {
var vrContainer = document.createElement('div');
vrContainer.className = 'cesium-viewer-vrContainer';
viewerContainer.appendChild(vrContainer);
vrButton = new VRButton(vrContainer, cesiumWidget.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._toolbar = toolbar;
this._homeButton = homeButton;
this._sceneModePicker = sceneModePicker;
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._clockTrackedDataSource = undefined;
this._forceResize = false;
this._zoomIsFlight = false;
this._zoomTarget = undefined;
this._zoomPromise = undefined;
this._zoomOptions = undefined;
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(cesiumWidget.scene.preRender, Viewer.prototype.resize, this);
eventHelper.add(cesiumWidget.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
var dataSourceLength = dataSourceCollection.length;
for (var 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) {
var 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);
}
}
}
function pickAndSelectObject(e) {
that.selectedEntity = pickEntity(that, e);
}
cesiumWidget.screenSpaceEventHandler.setInputAction(pickAndSelectObject, ScreenSpaceEventType.LEFT_CLICK);
cesiumWidget.screenSpaceEventHandler.setInputAction(pickAndTrackObject, ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
}
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 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 {Canvas}
* @readonly
*/
canvas : {
get : function() {
return this._cesiumWidget.canvas;
}
},
/**
* Gets the Cesium logo element.
* @memberof Viewer.prototype
* @type {Element}
* @readonly
*/
cesiumLogo : {
get : function() {
return this._cesiumWidget.cesiumLogo;
}
},
/**
* Gets the scene.
* @memberof Viewer.prototype
* @type {Scene}
* @readonly
*/
scene : {
get : function() {
return this._cesiumWidget.scene;
}
},
/**
* Determines if shadows are cast by the sun.
* @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 shadows from the sun.
* @memberof Viewer.prototype
* @type {Boolean}
*/
terrainShadows : {
get : function() {
return this.scene.globe.castShadows;
},
set : function(value) {
this.scene.globe.castShadows = 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 clock.
* @memberof Viewer.prototype
* @type {Clock}
* @readonly
*/
clock : {
get : function() {
return this._cesiumWidget.clock;
}
},
/**
* 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 <code>useDefaultRenderLoop</code>
* 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
* <code>resize</code>, <code>render</code> methods
* as part of a custom render loop. If an error occurs during rendering, {@link Scene}'s
* <code>renderError</code> 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;
this._forceResize = true;
}
},
/**
* 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}
*/
trackedEntity : {
get : function() {
return this._trackedEntity;
},
set : function(value) {
if (this._trackedEntity !== value) {
this._trackedEntity = value;
//Cancel any pending zoom
cancelZoom(this);
var scene = this.scene;
var 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);
return;
}
//We can't start tracking immediately, so we set a flag and start tracking
//when the bounding sphere is ready (most like