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";
|
import JulianDate from "../../Core/JulianDate.js";
|
import CesiumMath from "../../Core/Math.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);
|
}
|
}
|
|
var scratchStopTime = new JulianDate();
|
|
function trackDataSourceClock(timeline, clock, dataSource) {
|
if (defined(dataSource)) {
|
var dataSourceClock = dataSource.clock;
|
if (defined(dataSourceClock)) {
|
dataSourceClock.getValue(clock);
|
if (defined(timeline)) {
|
var startTime = dataSourceClock.startTime;
|
var stopTime = dataSourceClock.stopTime;
|
// When the start and stop times are equal, set the timeline to the shortest interval
|
// starting at the start time. This prevents an invalid timeline configuration.
|
if (JulianDate.equals(startTime, stopTime)) {
|
stopTime = JulianDate.addSeconds(
|
startTime,
|
CesiumMath.EPSILON2,
|
scratchStopTime
|
);
|
}
|
timeline.updateFromClock();
|
timeline.zoomTo(startTime, stopTime);
|
}
|
}
|
}
|
}
|
|
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 {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,
|
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.viewModel
|
: undefined;
|
if (defined(value)) {
|
if (defined(selectionIndicatorViewModel)) {
|
selectionIndicatorViewModel.animateAppear();
|
}
|
} else if (defined(selectionIndicatorViewModel)) {
|
// Leave the info text in place here, it is needed during the exit animation.
|
selectionIndicatorViewModel.animateDepart();
|
}
|
this._selectedEntityChanged.raiseEvent(value);
|
}
|
},
|
},
|
/**
|
* Gets the event that is raised when the selected entity changes.
|
* @memberof Viewer.prototype
|
* @type {Event}
|
* @readonly
|
*/
|
selectedEntityChanged: {
|
get: function () {
|
return this._selectedEntityChanged;
|
},
|
},
|
/**
|
* Gets the event that is raised when the tracked entity changes.
|
* @memberof Viewer.prototype
|
* @type {Event}
|
* @readonly
|
*/
|
trackedEntityChanged: {
|
get: function () {
|
return this._trackedEntityChanged;
|
},
|
},
|
/**
|
* Gets or sets the data source to track with the viewer's clock.
|
* @memberof Viewer.prototype
|
* @type {DataSource}
|
*/
|
clockTrackedDataSource: {
|
get: function () {
|
return this._clockTrackedDataSource;
|
},
|
set: function (value) {
|
if (this._clockTrackedDataSource !== value) {
|
this._clockTrackedDataSource = value;
|
trackDataSourceClock(this._timeline, this.clock, value);
|
}
|
},
|
},
|
});
|
|
/**
|
* Extends the base viewer functionality with the provided mixin.
|
* A mixin may add additional properties, functions, or other behavior
|
* to the provided viewer instance.
|
*
|
* @param {Viewer.ViewerMixin} mixin The Viewer mixin to add to this instance.
|
* @param {Object} [options] The options object to be passed to the mixin function.
|
*
|
* @see viewerDragDropMixin
|
*/
|
Viewer.prototype.extend = function (mixin, options) {
|
//>>includeStart('debug', pragmas.debug);
|
if (!defined(mixin)) {
|
throw new DeveloperError("mixin is required.");
|
}
|
//>>includeEnd('debug')
|
|
mixin(this, options);
|
};
|
|
/**
|
* Resizes the widget to match the container size.
|
* This function is called automatically as needed unless
|
* <code>useDefaultRenderLoop</code> is set to false.
|
*/
|
Viewer.prototype.resize = function () {
|
var cesiumWidget = this._cesiumWidget;
|
var container = this._container;
|
var width = container.clientWidth;
|
var height = container.clientHeight;
|
var animationExists = defined(this._animation);
|
var timelineExists = defined(this._timeline);
|
|
cesiumWidget.resize();
|
|
if (width === this._lastWidth && height === this._lastHeight) {
|
return;
|
}
|
|
var panelMaxHeight = height - 125;
|
var baseLayerPickerDropDown = this._baseLayerPickerDropDown;
|
|
if (defined(baseLayerPickerDropDown)) {
|
baseLayerPickerDropDown.style.maxHeight = panelMaxHeight + "px";
|
}
|
|
if (defined(this._geocoder)) {
|
var geocoderSuggestions = this._geocoder.searchSuggestionsContainer;
|
geocoderSuggestions.style.maxHeight = panelMaxHeight + "px";
|
}
|
|
if (defined(this._infoBox)) {
|
this._infoBox.viewModel.maxHeight = panelMaxHeight;
|
}
|
|
var timeline = this._timeline;
|
var animationContainer;
|
var animationWidth = 0;
|
var creditLeft = 0;
|
var creditBottom = 0;
|
|
if (
|
animationExists &&
|
window.getComputedStyle(this._animation.container).visibility !== "hidden"
|
) {
|
var lastWidth = this._lastWidth;
|
animationContainer = this._animation.container;
|
if (width > 900) {
|
animationWidth = 169;
|
if (lastWidth <= 900) {
|
animationContainer.style.width = "169px";
|
animationContainer.style.height = "112px";
|
this._animation.resize();
|
}
|
} else if (width >= 600) {
|
animationWidth = 136;
|
if (lastWidth < 600 || lastWidth > 900) {
|
animationContainer.style.width = "136px";
|
animationContainer.style.height = "90px";
|
this._animation.resize();
|
}
|
} else {
|
animationWidth = 106;
|
if (lastWidth > 600 || lastWidth === 0) {
|
animationContainer.style.width = "106px";
|
animationContainer.style.height = "70px";
|
this._animation.resize();
|
}
|
}
|
creditLeft = animationWidth + 5;
|
}
|
|
if (
|
timelineExists &&
|
window.getComputedStyle(this._timeline.container).visibility !== "hidden"
|
) {
|
var fullscreenButton = this._fullscreenButton;
|
var vrButton = this._vrButton;
|
var timelineContainer = timeline.container;
|
var timelineStyle = timelineContainer.style;
|
|
creditBottom = timelineContainer.clientHeight + 3;
|
timelineStyle.left = animationWidth + "px";
|
|
var pixels = 0;
|
if (defined(fullscreenButton)) {
|
pixels += fullscreenButton.container.clientWidth;
|
}
|
if (defined(vrButton)) {
|
pixels += vrButton.container.clientWidth;
|
}
|
|
timelineStyle.right = pixels + "px";
|
timeline.resize();
|
}
|
|
this._bottomContainer.style.left = creditLeft + "px";
|
this._bottomContainer.style.bottom = creditBottom + "px";
|
|
this._lastWidth = width;
|
this._lastHeight = height;
|
};
|
|
/**
|
* This forces the widget to re-think its layout, including
|
* widget sizes and credit placement.
|
*/
|
Viewer.prototype.forceResize = function () {
|
this._lastWidth = 0;
|
this.resize();
|
};
|
|
/**
|
* Renders the scene. This function is called automatically
|
* unless <code>useDefaultRenderLoop</code> is set to false;
|
*/
|
Viewer.prototype.render = function () {
|
this._cesiumWidget.render();
|
};
|
|
/**
|
* @returns {Boolean} true if the object has been destroyed, false otherwise.
|
*/
|
Viewer.prototype.isDestroyed = function () {
|
return false;
|
};
|
|
/**
|
* Destroys the widget. Should be called if permanently
|
* removing the widget from layout.
|
*/
|
Viewer.prototype.destroy = function () {
|
var i;
|
|
this.screenSpaceEventHandler.removeInputAction(
|
ScreenSpaceEventType.LEFT_CLICK
|
);
|
this.screenSpaceEventHandler.removeInputAction(
|
ScreenSpaceEventType.LEFT_DOUBLE_CLICK
|
);
|
|
// Unsubscribe from data sources
|
var dataSources = this.dataSources;
|
var dataSourceLength = dataSources.length;
|
for (i = 0; i < dataSourceLength; i++) {
|
this._dataSourceRemoved(dataSources, dataSources.get(i));
|
}
|
this._dataSourceRemoved(undefined, this._dataSourceDisplay.defaultDataSource);
|
|
this._container.removeChild(this._element);
|
this._element.removeChild(this._toolbar);
|
|
this._eventHelper.removeAll();
|
|
if (defined(this._geocoder)) {
|
this._geocoder = this._geocoder.destroy();
|
}
|
|
if (defined(this._homeButton)) {
|
this._homeButton = this._homeButton.destroy();
|
}
|
|
if (defined(this._sceneModePicker)) {
|
this._sceneModePicker = this._sceneModePicker.destroy();
|
}
|
|
if (defined(this._projectionPicker)) {
|
this._projectionPicker = this._projectionPicker.destroy();
|
}
|
|
if (defined(this._baseLayerPicker)) {
|
this._baseLayerPicker = this._baseLayerPicker.destroy();
|
}
|
|
if (defined(this._animation)) {
|
this._element.removeChild(this._animation.container);
|
this._animation = this._animation.destroy();
|
}
|
|
if (defined(this._timeline)) {
|
this._timeline.removeEventListener(
|
"settime",
|
onTimelineScrubfunction,
|
false
|
);
|
this._element.removeChild(this._timeline.container);
|
this._timeline = this._timeline.destroy();
|
}
|
|
if (defined(this._fullscreenButton)) {
|
this._fullscreenSubscription.dispose();
|
this._element.removeChild(this._fullscreenButton.container);
|
this._fullscreenButton = this._fullscreenButton.destroy();
|
}
|
|
if (defined(this._vrButton)) {
|
this._vrSubscription.dispose();
|
this._vrModeSubscription.dispose();
|
this._element.removeChild(this._vrButton.container);
|
this._vrButton = this._vrButton.destroy();
|
}
|
|
if (defined(this._infoBox)) {
|
this._element.removeChild(this._infoBox.container);
|
this._infoBox = this._infoBox.destroy();
|
}
|
|
if (defined(this._selectionIndicator)) {
|
this._element.removeChild(this._selectionIndicator.container);
|
this._selectionIndicator = this._selectionIndicator.destroy();
|
}
|
|
if (this._destroyClockViewModel) {
|
this._clockViewModel = this._clockViewModel.destroy();
|
}
|
this._dataSourceDisplay = this._dataSourceDisplay.destroy();
|
this._cesiumWidget = this._cesiumWidget.destroy();
|
|
if (this._destroyDataSourceCollection) {
|
this._dataSourceCollection = this._dataSourceCollection.destroy();
|
}
|
|
return destroyObject(this);
|
};
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._dataSourceAdded = function (
|
dataSourceCollection,
|
dataSource
|
) {
|
var entityCollection = dataSource.entities;
|
entityCollection.collectionChanged.addEventListener(
|
Viewer.prototype._onEntityCollectionChanged,
|
this
|
);
|
};
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._dataSourceRemoved = function (
|
dataSourceCollection,
|
dataSource
|
) {
|
var entityCollection = dataSource.entities;
|
entityCollection.collectionChanged.removeEventListener(
|
Viewer.prototype._onEntityCollectionChanged,
|
this
|
);
|
|
if (defined(this.trackedEntity)) {
|
if (
|
entityCollection.getById(this.trackedEntity.id) === this.trackedEntity
|
) {
|
this.trackedEntity = undefined;
|
}
|
}
|
|
if (defined(this.selectedEntity)) {
|
if (
|
entityCollection.getById(this.selectedEntity.id) === this.selectedEntity
|
) {
|
this.selectedEntity = undefined;
|
}
|
}
|
};
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._onTick = function (clock) {
|
var time = clock.currentTime;
|
|
var isUpdated = this._dataSourceDisplay.update(time);
|
if (this._allowDataSourcesToSuspendAnimation) {
|
this._clockViewModel.canAnimate = isUpdated;
|
}
|
|
var entityView = this._entityView;
|
if (defined(entityView)) {
|
var trackedEntity = this._trackedEntity;
|
var trackedState = this._dataSourceDisplay.getBoundingSphere(
|
trackedEntity,
|
false,
|
boundingSphereScratch
|
);
|
if (trackedState === BoundingSphereState.DONE) {
|
entityView.update(time, boundingSphereScratch);
|
}
|
}
|
|
var position;
|
var enableCamera = false;
|
var selectedEntity = this.selectedEntity;
|
var showSelection = defined(selectedEntity) && this._enableInfoOrSelection;
|
|
if (
|
showSelection &&
|
selectedEntity.isShowing &&
|
selectedEntity.isAvailable(time)
|
) {
|
var state = this._dataSourceDisplay.getBoundingSphere(
|
selectedEntity,
|
true,
|
boundingSphereScratch
|
);
|
if (state !== BoundingSphereState.FAILED) {
|
position = boundingSphereScratch.center;
|
} else if (defined(selectedEntity.position)) {
|
position = selectedEntity.position.getValue(time, position);
|
}
|
enableCamera = defined(position);
|
}
|
|
var selectionIndicatorViewModel = defined(this._selectionIndicator)
|
? this._selectionIndicator.viewModel
|
: undefined;
|
if (defined(selectionIndicatorViewModel)) {
|
selectionIndicatorViewModel.position = Cartesian3.clone(
|
position,
|
selectionIndicatorViewModel.position
|
);
|
selectionIndicatorViewModel.showSelection = showSelection && enableCamera;
|
selectionIndicatorViewModel.update();
|
}
|
|
var infoBoxViewModel = defined(this._infoBox)
|
? this._infoBox.viewModel
|
: undefined;
|
if (defined(infoBoxViewModel)) {
|
infoBoxViewModel.showInfo = showSelection;
|
infoBoxViewModel.enableCamera = enableCamera;
|
infoBoxViewModel.isCameraTracking =
|
this.trackedEntity === this.selectedEntity;
|
|
if (showSelection) {
|
infoBoxViewModel.titleText = defaultValue(
|
selectedEntity.name,
|
selectedEntity.id
|
);
|
infoBoxViewModel.description = Property.getValueOrDefault(
|
selectedEntity.description,
|
time,
|
""
|
);
|
} else {
|
infoBoxViewModel.titleText = "";
|
infoBoxViewModel.description = "";
|
}
|
}
|
};
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._onEntityCollectionChanged = function (
|
collection,
|
added,
|
removed
|
) {
|
var length = removed.length;
|
for (var i = 0; i < length; i++) {
|
var removedObject = removed[i];
|
if (this.trackedEntity === removedObject) {
|
this.trackedEntity = undefined;
|
}
|
if (this.selectedEntity === removedObject) {
|
this.selectedEntity = undefined;
|
}
|
}
|
};
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._onInfoBoxCameraClicked = function (infoBoxViewModel) {
|
if (
|
infoBoxViewModel.isCameraTracking &&
|
this.trackedEntity === this.selectedEntity
|
) {
|
this.trackedEntity = undefined;
|
} else {
|
var selectedEntity = this.selectedEntity;
|
var position = selectedEntity.position;
|
if (defined(position)) {
|
this.trackedEntity = this.selectedEntity;
|
} else {
|
this.zoomTo(this.selectedEntity);
|
}
|
}
|
};
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._clearTrackedObject = function () {
|
this.trackedEntity = undefined;
|
};
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._onInfoBoxClockClicked = function (infoBoxViewModel) {
|
this.selectedEntity = undefined;
|
};
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._clearObjects = function () {
|
this.trackedEntity = undefined;
|
this.selectedEntity = undefined;
|
};
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._onDataSourceChanged = function (dataSource) {
|
if (this.clockTrackedDataSource === dataSource) {
|
trackDataSourceClock(this.timeline, this.clock, dataSource);
|
}
|
};
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._onDataSourceAdded = function (
|
dataSourceCollection,
|
dataSource
|
) {
|
if (this._automaticallyTrackDataSourceClocks) {
|
this.clockTrackedDataSource = dataSource;
|
}
|
var id = dataSource.entities.id;
|
var removalFunc = this._eventHelper.add(
|
dataSource.changedEvent,
|
Viewer.prototype._onDataSourceChanged,
|
this
|
);
|
this._dataSourceChangedListeners[id] = removalFunc;
|
};
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._onDataSourceRemoved = function (
|
dataSourceCollection,
|
dataSource
|
) {
|
var resetClock = this.clockTrackedDataSource === dataSource;
|
var id = dataSource.entities.id;
|
this._dataSourceChangedListeners[id]();
|
this._dataSourceChangedListeners[id] = undefined;
|
if (resetClock) {
|
var numDataSources = dataSourceCollection.length;
|
if (this._automaticallyTrackDataSourceClocks && numDataSources > 0) {
|
this.clockTrackedDataSource = dataSourceCollection.get(
|
numDataSources - 1
|
);
|
} else {
|
this.clockTrackedDataSource = undefined;
|
}
|
}
|
};
|
|
/**
|
* Asynchronously sets the camera to view the provided entity, entities, or data source.
|
* If the data source is still in the process of loading or the visualization is otherwise still loading,
|
* this method waits for the data to be ready before performing the zoom.
|
*
|
* <p>The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
|
* The heading and the pitch angles are defined in the local east-north-up reference frame.
|
* The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
|
* angles are above the plane. Negative pitch angles are below the plane. The range is the distance from the center. If the range is
|
* zero, a range will be computed such that the whole bounding sphere is visible.</p>
|
*
|
* <p>In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
|
* target will be the range. The heading will be determined from the offset. If the heading cannot be
|
* determined from the offset, the heading will be north.</p>
|
*
|
* @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud>} target The entity, array of entities, entity collection, data source, Cesium3DTileset, point cloud, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types.
|
* @param {HeadingPitchRange} [offset] The offset from the center of the entity in the local east-north-up reference frame.
|
* @returns {Promise.<Boolean>} A Promise that resolves to true if the zoom was successful or false if the target is not currently visualized in the scene or the zoom was cancelled.
|
*/
|
Viewer.prototype.zoomTo = function (target, offset) {
|
var options = {
|
offset: offset,
|
};
|
return zoomToOrFly(this, target, options, false);
|
};
|
|
/**
|
* Flies the camera to the provided entity, entities, or data source.
|
* If the data source is still in the process of loading or the visualization is otherwise still loading,
|
* this method waits for the data to be ready before performing the flight.
|
*
|
* <p>The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
|
* The heading and the pitch angles are defined in the local east-north-up reference frame.
|
* The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
|
* angles are above the plane. Negative pitch angles are below the plane. The range is the distance from the center. If the range is
|
* zero, a range will be computed such that the whole bounding sphere is visible.</p>
|
*
|
* <p>In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
|
* target will be the range. The heading will be determined from the offset. If the heading cannot be
|
* determined from the offset, the heading will be north.</p>
|
*
|
* @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud>} target The entity, array of entities, entity collection, data source, Cesium3DTileset, point cloud, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types.
|
* @param {Object} [options] Object with the following properties:
|
* @param {Number} [options.duration=3.0] The duration of the flight in seconds.
|
* @param {Number} [options.maximumHeight] The maximum height at the peak of the flight.
|
* @param {HeadingPitchRange} [options.offset] The offset from the target in the local east-north-up reference frame centered at the target.
|
* @returns {Promise.<Boolean>} A Promise that resolves to true if the flight was successful or false if the target is not currently visualized in the scene or the flight was cancelled. //TODO: Cleanup entity mentions
|
*/
|
Viewer.prototype.flyTo = function (target, options) {
|
return zoomToOrFly(this, target, options, true);
|
};
|
|
function zoomToOrFly(that, zoomTarget, options, isFlight) {
|
//>>includeStart('debug', pragmas.debug);
|
if (!defined(zoomTarget)) {
|
throw new DeveloperError("zoomTarget is required.");
|
}
|
//>>includeEnd('debug');
|
|
cancelZoom(that);
|
|
//We can't actually perform the zoom until all visualization is ready and
|
//bounding spheres have been computed. Therefore we create and return
|
//a deferred which will be resolved as part of the post-render step in the
|
//frame that actually performs the zoom
|
var zoomPromise = when.defer();
|
that._zoomPromise = zoomPromise;
|
that._zoomIsFlight = isFlight;
|
that._zoomOptions = options;
|
|
when(zoomTarget, function (zoomTarget) {
|
//Only perform the zoom if it wasn't cancelled before the promise resolved.
|
if (that._zoomPromise !== zoomPromise) {
|
return;
|
}
|
|
//If the zoom target is a rectangular imagery in an ImageLayer
|
if (zoomTarget instanceof ImageryLayer) {
|
zoomTarget
|
.getViewableRectangle()
|
.then(function (rectangle) {
|
return computeFlyToLocationForRectangle(rectangle, that.scene);
|
})
|
.then(function (position) {
|
//Only perform the zoom if it wasn't cancelled before the promise was resolved
|
if (that._zoomPromise === zoomPromise) {
|
that._zoomTarget = position;
|
}
|
});
|
return;
|
}
|
|
//If the zoom target is a Cesium3DTileset
|
if (zoomTarget instanceof Cesium3DTileset) {
|
that._zoomTarget = zoomTarget;
|
return;
|
}
|
|
//If the zoom target is a TimeDynamicPointCloud
|
if (zoomTarget instanceof TimeDynamicPointCloud) {
|
that._zoomTarget = zoomTarget;
|
return;
|
}
|
|
//If the zoom target is a data source, and it's in the middle of loading, wait for it to finish loading.
|
if (zoomTarget.isLoading && defined(zoomTarget.loadingEvent)) {
|
var removeEvent = zoomTarget.loadingEvent.addEventListener(function () {
|
removeEvent();
|
|
//Only perform the zoom if it wasn't cancelled before the data source finished.
|
if (that._zoomPromise === zoomPromise) {
|
that._zoomTarget = zoomTarget.entities.values.slice(0);
|
}
|
});
|
return;
|
}
|
|
//Zoom target is already an array, just copy it and return.
|
if (Array.isArray(zoomTarget)) {
|
that._zoomTarget = zoomTarget.slice(0);
|
return;
|
}
|
|
//If zoomTarget is an EntityCollection, this will retrieve the array
|
zoomTarget = defaultValue(zoomTarget.values, zoomTarget);
|
|
//If zoomTarget is a DataSource, this will retrieve the array.
|
if (defined(zoomTarget.entities)) {
|
zoomTarget = zoomTarget.entities.values;
|
}
|
|
//Zoom target is already an array, just copy it and return.
|
if (Array.isArray(zoomTarget)) {
|
that._zoomTarget = zoomTarget.slice(0);
|
} else {
|
//Single entity
|
that._zoomTarget = [zoomTarget];
|
}
|
});
|
|
that.scene.requestRender();
|
return zoomPromise.promise;
|
}
|
|
function clearZoom(viewer) {
|
viewer._zoomPromise = undefined;
|
viewer._zoomTarget = undefined;
|
viewer._zoomOptions = undefined;
|
}
|
|
function cancelZoom(viewer) {
|
var zoomPromise = viewer._zoomPromise;
|
if (defined(zoomPromise)) {
|
clearZoom(viewer);
|
zoomPromise.resolve(false);
|
}
|
}
|
|
/**
|
* @private
|
*/
|
Viewer.prototype._postRender = function () {
|
updateZoomTarget(this);
|
updateTrackedEntity(this);
|
};
|
|
function updateZoomTarget(viewer) {
|
var target = viewer._zoomTarget;
|
if (!defined(target) || viewer.scene.mode === SceneMode.MORPHING) {
|
return;
|
}
|
|
var scene = viewer.scene;
|
var camera = scene.camera;
|
var zoomPromise = viewer._zoomPromise;
|
var zoomOptions = defaultValue(viewer._zoomOptions, {});
|
var options;
|
var boundingSphere;
|
|
// If zoomTarget was Cesium3DTileset
|
if (target instanceof Cesium3DTileset) {
|
return target.readyPromise.then(function () {
|
var boundingSphere = target.boundingSphere;
|
// If offset was originally undefined then give it base value instead of empty object
|
if (!defined(zoomOptions.offset)) {
|
zoomOptions.offset = new HeadingPitchRange(
|
0.0,
|
-0.5,
|
boundingSphere.radius
|
);
|
}
|
|
options = {
|
offset: zoomOptions.offset,
|
duration: zoomOptions.duration,
|
maximumHeight: zoomOptions.maximumHeight,
|
complete: function () {
|
zoomPromise.resolve(true);
|
},
|
cancel: function () {
|
zoomPromise.resolve(false);
|
},
|
};
|
|
if (viewer._zoomIsFlight) {
|
camera.flyToBoundingSphere(target.boundingSphere, options);
|
} else {
|
camera.viewBoundingSphere(boundingSphere, zoomOptions.offset);
|
camera.lookAtTransform(Matrix4.IDENTITY);
|
|
// Finish the promise
|
zoomPromise.resolve(true);
|
}
|
|
clearZoom(viewer);
|
});
|
}
|
|
// If zoomTarget was TimeDynamicPointCloud
|
if (target instanceof TimeDynamicPointCloud) {
|
return target.readyPromise.then(function () {
|
var boundingSphere = target.boundingSphere;
|
// If offset was originally undefined then give it base value instead of empty object
|
if (!defined(zoomOptions.offset)) {
|
zoomOptions.offset = new HeadingPitchRange(
|
0.0,
|
-0.5,
|
boundingSphere.radius
|
);
|
}
|
|
options = {
|
offset: zoomOptions.offset,
|
duration: zoomOptions.duration,
|
maximumHeight: zoomOptions.maximumHeight,
|
complete: function () {
|
zoomPromise.resolve(true);
|
},
|
cancel: function () {
|
zoomPromise.resolve(false);
|
},
|
};
|
|
if (viewer._zoomIsFlight) {
|
camera.flyToBoundingSphere(boundingSphere, options);
|
} else {
|
camera.viewBoundingSphere(boundingSphere, zoomOptions.offset);
|
camera.lookAtTransform(Matrix4.IDENTITY);
|
|
// Finish the promise
|
zoomPromise.resolve(true);
|
}
|
|
clearZoom(viewer);
|
});
|
}
|
|
// If zoomTarget was an ImageryLayer
|
if (target instanceof Cartographic) {
|
options = {
|
destination: scene.mapProjection.ellipsoid.cartographicToCartesian(
|
target
|
),
|
duration: zoomOptions.duration,
|
maximumHeight: zoomOptions.maximumHeight,
|
complete: function () {
|
zoomPromise.resolve(true);
|
},
|
cancel: function () {
|
zoomPromise.resolve(false);
|
},
|
};
|
|
if (viewer._zoomIsFlight) {
|
camera.flyTo(options);
|
} else {
|
camera.setView(options);
|
zoomPromise.resolve(true);
|
}
|
clearZoom(viewer);
|
return;
|
}
|
|
var entities = target;
|
|
var boundingSpheres = [];
|
for (var i = 0, len = entities.length; i < len; i++) {
|
var state = viewer._dataSourceDisplay.getBoundingSphere(
|
entities[i],
|
false,
|
boundingSphereScratch
|
);
|
|
if (state === BoundingSphereState.PENDING) {
|
return;
|
} else if (state !== BoundingSphereState.FAILED) {
|
boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
|
}
|
}
|
|
if (boundingSpheres.length === 0) {
|
cancelZoom(viewer);
|
return;
|
}
|
|
//Stop tracking the current entity.
|
viewer.trackedEntity = undefined;
|
|
boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
|
|
if (!viewer._zoomIsFlight) {
|
camera.viewBoundingSphere(boundingSphere, zoomOptions.offset);
|
camera.lookAtTransform(Matrix4.IDENTITY);
|
clearZoom(viewer);
|
zoomPromise.resolve(true);
|
} else {
|
clearZoom(viewer);
|
camera.flyToBoundingSphere(boundingSphere, {
|
duration: zoomOptions.duration,
|
maximumHeight: zoomOptions.maximumHeight,
|
complete: function () {
|
zoomPromise.resolve(true);
|
},
|
cancel: function () {
|
zoomPromise.resolve(false);
|
},
|
offset: zoomOptions.offset,
|
});
|
}
|
}
|
|
function updateTrackedEntity(viewer) {
|
if (!viewer._needTrackedEntityUpdate) {
|
return;
|
}
|
|
var trackedEntity = viewer._trackedEntity;
|
var currentTime = viewer.clock.currentTime;
|
|
//Verify we have a current position at this time. This is only triggered if a position
|
//has become undefined after trackedEntity is set but before the boundingSphere has been
|
//computed. In this case, we will track the entity once it comes back into existence.
|
var currentPosition = Property.getValueOrUndefined(
|
trackedEntity.position,
|
currentTime
|
);
|
|
if (!defined(currentPosition)) {
|
return;
|
}
|
|
var scene = viewer.scene;
|
|
var state = viewer._dataSourceDisplay.getBoundingSphere(
|
trackedEntity,
|
false,
|
boundingSphereScratch
|
);
|
if (state === BoundingSphereState.PENDING) {
|
return;
|
}
|
|
var sceneMode = scene.mode;
|
if (
|
sceneMode === SceneMode.COLUMBUS_VIEW ||
|
sceneMode === SceneMode.SCENE2D
|
) {
|
scene.screenSpaceCameraController.enableTranslate = false;
|
}
|
|
if (
|
sceneMode === SceneMode.COLUMBUS_VIEW ||
|
sceneMode === SceneMode.SCENE3D
|
) {
|
scene.screenSpaceCameraController.enableTilt = false;
|
}
|
|
var bs =
|
state !== BoundingSphereState.FAILED ? boundingSphereScratch : undefined;
|
viewer._entityView = new EntityView(
|
trackedEntity,
|
scene,
|
scene.mapProjection.ellipsoid
|
);
|
viewer._entityView.update(currentTime, bs);
|
viewer._needTrackedEntityUpdate = false;
|
}
|
|
/**
|
* A function that augments a Viewer instance with additional functionality.
|
* @callback Viewer.ViewerMixin
|
* @param {Viewer} viewer The viewer instance.
|
* @param {Object} options Options object to be passed to the mixin function.
|
*
|
* @see Viewer#extend
|
*/
|
export default Viewer;
|