UNPKG

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
/*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