UNPKG

cesium

Version:

CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

1,461 lines (1,340 loc) 78.1 kB
import BoundingSphere from "../../Core/BoundingSphere.js"; import Cartesian3 from "../../Core/Cartesian3.js"; import Cartographic from "../../Core/Cartographic.js"; import Clock from "../../Core/Clock.js"; import defaultValue from "../../Core/defaultValue.js"; import defined from "../../Core/defined.js"; import destroyObject from "../../Core/destroyObject.js"; import DeveloperError from "../../Core/DeveloperError.js"; import 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 when from "../../ThirdParty/when.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"; var boundingSphereScratch = new BoundingSphere(); function onTimelineScrubfunction(e) { var clock = e.clock; clock.currentTime = e.timeJulian; clock.shouldAnimate = false; } function getCesium3DTileFeatureDescription(feature) { var propertyNames = feature.getPropertyNames(); var html = ""; propertyNames.forEach(function (propertyName) { var value = feature.getProperty(propertyName); if (defined(value)) { html += "<tr><th>" + propertyName + "</th><td>" + value + "</td></tr>"; } }); if (html.length > 0) { html = '<table class="cesium-infoBox-defaultTable"><tbody>' + html + "</tbody></table>"; } return html; } function getCesium3DTileFeatureName(feature) { // We need to iterate all property names to find potential // candidates, but since we prefer some property names // over others, we store them in an indexed array // and then use the first defined element in the array // as the preferred choice. var i; var possibleNames = []; var propertyNames = feature.getPropertyNames(); for (i = 0; i < propertyNames.length; i++) { var propertyName = propertyNames[i]; if (/^name$/i.test(propertyName)) { possibleNames[0] = feature.getProperty(propertyName); } else if (/name/i.test(propertyName)) { possibleNames[1] = feature.getProperty(propertyName); } else if (/^title$/i.test(propertyName)) { possibleNames[2] = feature.getProperty(propertyName); } else if (/^(id|identifier)$/i.test(propertyName)) { possibleNames[3] = feature.getProperty(propertyName); } else if (/element/i.test(propertyName)) { possibleNames[4] = feature.getProperty(propertyName); } else if (/(id|identifier)$/i.test(propertyName)) { possibleNames[5] = feature.getProperty(propertyName); } } var length = possibleNames.length; for (i = 0; i < length; i++) { var item = possibleNames[i]; if (defined(item) && item !== "") { return item; } } return "Unnamed Feature"; } 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; } if (picked instanceof Cesium3DTileFeature) { return new Entity({ name: getCesium3DTileFeatureName(picked), description: getCesium3DTileFeatureDescription(picked), feature: picked, }); } } // 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 projectionPicker = viewer._projectionPicker; 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(projectionPicker)) { projectionPicker.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(); } } /** * @typedef {Object} Viewer.ConstructorOptions * * Initialization options for the Viewer constructor * * @property {Boolean} [animation=true] If set to false, the Animation widget will not be created. * @property {Boolean} [baseLayerPicker=true] If set to false, the BaseLayerPicker widget will not be created. * @property {Boolean} [fullscreenButton=true] If set to false, the FullscreenButton widget will not be created. * @property {Boolean} [vrButton=false] If set to true, the VRButton widget will be created. * @property {Boolean|GeocoderService[]} [geocoder=true] If set to false, the Geocoder widget will not be created. * @property {Boolean} [homeButton=true] If set to false, the HomeButton widget will not be created. * @property {Boolean} [infoBox=true] If set to false, the InfoBox widget will not be created. * @property {Boolean} [sceneModePicker=true] If set to false, the SceneModePicker widget will not be created. * @property {Boolean} [selectionIndicator=true] If set to false, the SelectionIndicator widget will not be created. * @property {Boolean} [timeline=true] If set to false, the Timeline widget will not be created. * @property {Boolean} [navigationHelpButton=true] If set to false, the navigation help button will not be created. * @property {Boolean} [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. * @property {Boolean} [scene3DOnly=false] When <code>true</code>, each geometry instance will only be rendered in 3D to save GPU memory. * @property {Boolean} [shouldAnimate=false] <code>true</code> if the clock should attempt to advance simulation time by default, <code>false</code> 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 <code>undefined</code>, the default stars are used. If set to <code>false</code>, no skyBox, Sun, or Moon will be added. * @property {SkyAtmosphere|false} [skyAtmosphere] Blue sky, and the glow around the Earth's limb. Set to <code>false</code> 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 <code>window.devicePixelRatio</code>. * @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 <code>options</code> 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 <code>false</code>, 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 {Number} [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. * @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}. */ /** * 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. * var 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); var 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') 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); var clock; var clockViewModel; var 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 var 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, terrainExaggeration: options.terrainExaggeration, shadows: options.shadows, terrainShadows: options.terrainShadows, mapMode2D: options.mapMode2D, requestRenderMode: options.requestRenderMode, maximumRenderTimeChange: options.maximumRenderTimeChange, }); var dataSourceCollection = options.dataSources; var destroyDataSourceCollection = false; if (!defined(dataSourceCollection)) { dataSourceCollection = new DataSourceCollection(); destroyDataSourceCollection = true; } var scene = cesiumWidget.scene; var dataSourceDisplay = new DataSourceDisplay({ scene: scene, dataSourceCollection: dataSourceCollection, }); var eventHelper = new EventHelper(); eventHelper.add(clock.onTick, Viewer.prototype._onTick, this); eventHelper.add(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, 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); var 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 var homeButton; if (!defined(options.homeButton) || options.homeButton !== false) { homeButton = new HomeButton(toolbar, 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. //>>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'); var sceneModePicker; if ( !scene3DOnly && (!defined(options.sceneModePicker) || options.sceneModePicker !== false) ) { sceneModePicker = new SceneModePicker(toolbar, scene); } var projectionPicker; if (options.projectionPicker) { projectionPicker = new ProjectionPicker(toolbar, 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: 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]; } // 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 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; var 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 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, 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._clockTrackedDataSource = 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 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); } } 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 <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; }, }, /** * 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); 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); } 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; var selectionIndicatorViewModel = defined(this._selectionIndicator) ? this._selectionIndicator.viewMode