Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

CesiumViewer.js 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326
  1. /**
  2. * @Author: Caven
  3. * @Date: 2023-01-07 15:19:20
  4. */
  5. import {
  6. BoundingSphere,
  7. BoundingSphereState,
  8. Cartographic,
  9. CesiumWidget,
  10. Cesium3DTileset,
  11. Clock,
  12. computeFlyToLocationForRectangle,
  13. DataSourceCollection,
  14. DataSourceDisplay,
  15. defaultValue,
  16. defined,
  17. destroyObject,
  18. DeveloperError,
  19. Entity,
  20. EntityView,
  21. Event,
  22. EventHelper,
  23. getElement,
  24. HeadingPitchRange,
  25. ImageryLayer,
  26. Matrix4,
  27. Property,
  28. SceneMode,
  29. TimeDynamicPointCloud,
  30. Color
  31. } from '@cesium/engine'
  32. const boundingSphereScratch = new BoundingSphere()
  33. function trackDataSourceClock(timeline, clock, dataSource) {
  34. if (defined(dataSource)) {
  35. const dataSourceClock = dataSource.clock
  36. if (defined(dataSourceClock)) {
  37. dataSourceClock.getValue(clock)
  38. }
  39. }
  40. }
  41. /**
  42. *
  43. * @param container
  44. * @param options
  45. * @constructor
  46. */
  47. function Viewer(container, options) {
  48. //>>includeStart('debug', pragmas.debug);
  49. if (!defined(container)) {
  50. throw new DeveloperError('container is required.')
  51. }
  52. //>>includeEnd('debug');
  53. container = getElement(container)
  54. options = defaultValue(options, defaultValue.EMPTY_OBJECT)
  55. //>>includeEnd('debug')
  56. const viewerContainer = document.createElement('div')
  57. viewerContainer.className = 'dc-viewer'
  58. container.appendChild(viewerContainer)
  59. // Cesium widget container
  60. const cesiumWidgetContainer = document.createElement('div')
  61. cesiumWidgetContainer.className = 'dc-viewer-widget-container'
  62. viewerContainer.appendChild(cesiumWidgetContainer)
  63. // Bottom container
  64. const bottomContainer = document.createElement('div')
  65. bottomContainer.className = 'dc-viewer-bottom'
  66. const scene3DOnly = defaultValue(options.scene3DOnly, false)
  67. let clock = new Clock()
  68. if (defined(options.shouldAnimate)) {
  69. clock.shouldAnimate = options.shouldAnimate
  70. }
  71. // Cesium widget
  72. const cesiumWidget = new CesiumWidget(cesiumWidgetContainer, {
  73. imageryProvider: false,
  74. clock: clock,
  75. skyBox: options.skyBox,
  76. skyAtmosphere: options.skyAtmosphere,
  77. sceneMode: options.sceneMode,
  78. mapProjection: options.mapProjection,
  79. globe: options.globe,
  80. orderIndependentTranslucency: options.orderIndependentTranslucency,
  81. contextOptions: options.contextOptions,
  82. useDefaultRenderLoop: options.useDefaultRenderLoop,
  83. targetFrameRate: options.targetFrameRate,
  84. showRenderLoopErrors: options.showRenderLoopErrors,
  85. useBrowserRecommendedResolution: options.useBrowserRecommendedResolution,
  86. creditContainer: defined(options.creditContainer)
  87. ? options.creditContainer
  88. : bottomContainer,
  89. creditViewport: options.creditViewport,
  90. scene3DOnly: scene3DOnly,
  91. shadows: options.shadows,
  92. terrainShadows: options.terrainShadows,
  93. mapMode2D: options.mapMode2D,
  94. blurActiveElementOnCanvasFocus: options.blurActiveElementOnCanvasFocus,
  95. requestRenderMode: options.requestRenderMode,
  96. maximumRenderTimeChange: options.maximumRenderTimeChange,
  97. depthPlaneEllipsoidOffset: options.depthPlaneEllipsoidOffset,
  98. msaaSamples: options.msaaSamples
  99. })
  100. cesiumWidget.container.firstChild.className = 'dc-widget'
  101. cesiumWidget.scene.backgroundColor = Color.TRANSPARENT
  102. let childrens = cesiumWidget.creditViewport.children
  103. for (let i = 0; i < childrens.length; i++) {
  104. if (!(childrens[i] instanceof HTMLCanvasElement)) {
  105. cesiumWidget.creditViewport.removeChild(childrens[i])
  106. }
  107. }
  108. let dataSourceCollection = options.dataSources
  109. let destroyDataSourceCollection = false
  110. if (!defined(dataSourceCollection)) {
  111. dataSourceCollection = new DataSourceCollection()
  112. destroyDataSourceCollection = true
  113. }
  114. const scene = cesiumWidget.scene
  115. const dataSourceDisplay = new DataSourceDisplay({
  116. scene: scene,
  117. dataSourceCollection: dataSourceCollection
  118. })
  119. const eventHelper = new EventHelper()
  120. eventHelper.add(clock.onTick, Viewer.prototype._onTick, this)
  121. eventHelper.add(scene.morphStart, Viewer.prototype._clearTrackedObject, this)
  122. // Main Toolbar
  123. const toolbar = document.createElement('div')
  124. toolbar.className = 'dc-viewer-toolbar'
  125. //Assign all properties to this instance. No "this" assignments should
  126. //take place above this line.
  127. this._dataSourceChangedListeners = {}
  128. this._automaticallyTrackDataSourceClocks = defaultValue(
  129. options.automaticallyTrackDataSourceClocks,
  130. true
  131. )
  132. this._clock = clock
  133. this._container = container
  134. this._bottomContainer = bottomContainer
  135. this._element = viewerContainer
  136. this._cesiumWidget = cesiumWidget
  137. this._dataSourceCollection = dataSourceCollection
  138. this._destroyDataSourceCollection = destroyDataSourceCollection
  139. this._dataSourceDisplay = dataSourceDisplay
  140. this._toolbar = toolbar
  141. this._eventHelper = eventHelper
  142. this._lastWidth = 0
  143. this._lastHeight = 0
  144. this._allowDataSourcesToSuspendAnimation = true
  145. this._entityView = undefined
  146. this._enableInfoOrSelection = false
  147. this._clockTrackedDataSource = undefined
  148. this._trackedEntity = undefined
  149. this._needTrackedEntityUpdate = false
  150. this._selectedEntity = undefined
  151. this._zoomIsFlight = false
  152. this._zoomTarget = undefined
  153. this._zoomPromise = undefined
  154. this._zoomOptions = undefined
  155. this._selectedEntityChanged = new Event()
  156. this._trackedEntityChanged = new Event()
  157. //Listen to data source events in order to track clock changes.
  158. eventHelper.add(
  159. dataSourceCollection.dataSourceAdded,
  160. Viewer.prototype._onDataSourceAdded,
  161. this
  162. )
  163. eventHelper.add(
  164. dataSourceCollection.dataSourceRemoved,
  165. Viewer.prototype._onDataSourceRemoved,
  166. this
  167. )
  168. // Prior to each render, check if anything needs to be resized.
  169. eventHelper.add(scene.postUpdate, Viewer.prototype.resize, this)
  170. eventHelper.add(scene.postRender, Viewer.prototype._postRender, this)
  171. // We need to subscribe to the data sources and collections so that we can clear the
  172. // tracked object when it is removed from the scene.
  173. // Subscribe to current data sources
  174. const dataSourceLength = dataSourceCollection.length
  175. for (let i = 0; i < dataSourceLength; i++) {
  176. this._dataSourceAdded(dataSourceCollection, dataSourceCollection.get(i))
  177. }
  178. this._dataSourceAdded(undefined, dataSourceDisplay.defaultDataSource)
  179. // Hook up events so that we can subscribe to future sources.
  180. eventHelper.add(
  181. dataSourceCollection.dataSourceAdded,
  182. Viewer.prototype._dataSourceAdded,
  183. this
  184. )
  185. eventHelper.add(
  186. dataSourceCollection.dataSourceRemoved,
  187. Viewer.prototype._dataSourceRemoved,
  188. this
  189. )
  190. }
  191. /**
  192. *
  193. */
  194. Object.defineProperties(Viewer.prototype, {
  195. /**
  196. * Gets the parent container.
  197. * @memberof viewer.prototype
  198. * @type {Element}
  199. * @readonly
  200. */
  201. container: {
  202. get: function() {
  203. return this._container
  204. }
  205. },
  206. /**
  207. * Gets the DOM element for the area at the bottom of the window containing the
  208. * {@link CreditDisplay} and potentially other things.
  209. * @memberof viewer.prototype
  210. * @type {Element}
  211. * @readonly
  212. */
  213. bottomContainer: {
  214. get: function() {
  215. return this._bottomContainer
  216. }
  217. },
  218. /**
  219. * Gets the CesiumWidget.
  220. * @memberof viewer.prototype
  221. * @type {CesiumWidget}
  222. * @readonly
  223. */
  224. cesiumWidget: {
  225. get: function() {
  226. return this._cesiumWidget
  227. }
  228. },
  229. /**
  230. * Gets the display used for {@link DataSource} visualization.
  231. * @memberof viewer.prototype
  232. * @type {DataSourceDisplay}
  233. * @readonly
  234. */
  235. dataSourceDisplay: {
  236. get: function() {
  237. return this._dataSourceDisplay
  238. }
  239. },
  240. /**
  241. * Gets the collection of entities not tied to a particular data source.
  242. * This is a shortcut to [dataSourceDisplay.defaultDataSource.entities]{@link Viewer#dataSourceDisplay}.
  243. * @memberof viewer.prototype
  244. * @type {EntityCollection}
  245. * @readonly
  246. */
  247. entities: {
  248. get: function() {
  249. return this._dataSourceDisplay.defaultDataSource.entities
  250. }
  251. },
  252. /**
  253. * Gets the set of {@link DataSource} instances to be visualized.
  254. * @memberof viewer.prototype
  255. * @type {DataSourceCollection}
  256. * @readonly
  257. */
  258. dataSources: {
  259. get: function() {
  260. return this._dataSourceCollection
  261. }
  262. },
  263. /**
  264. * Gets the canvas.
  265. * @memberof viewer.prototype
  266. * @type {HTMLCanvasElement}
  267. * @readonly
  268. */
  269. canvas: {
  270. get: function() {
  271. return this._cesiumWidget.canvas
  272. }
  273. },
  274. /**
  275. * Gets the scene.
  276. * @memberof viewer.prototype
  277. * @type {Scene}
  278. * @readonly
  279. */
  280. scene: {
  281. get: function() {
  282. return this._cesiumWidget.scene
  283. }
  284. },
  285. /**
  286. * Determines if shadows are cast by light sources.
  287. * @memberof viewer.prototype
  288. * @type {Boolean}
  289. */
  290. shadows: {
  291. get: function() {
  292. return this.scene.shadowMap.enabled
  293. },
  294. set: function(value) {
  295. this.scene.shadowMap.enabled = value
  296. }
  297. },
  298. /**
  299. * Determines if the terrain casts or shadows from light sources.
  300. * @memberof viewer.prototype
  301. * @type {ShadowMode}
  302. */
  303. terrainShadows: {
  304. get: function() {
  305. return this.scene.globe.shadows
  306. },
  307. set: function(value) {
  308. this.scene.globe.shadows = value
  309. }
  310. },
  311. /**
  312. * Get the scene's shadow map
  313. * @memberof viewer.prototype
  314. * @type {ShadowMap}
  315. * @readonly
  316. */
  317. shadowMap: {
  318. get: function() {
  319. return this.scene.shadowMap
  320. }
  321. },
  322. /**
  323. * Gets the collection of image layers that will be rendered on the globe.
  324. * @memberof viewer.prototype
  325. *
  326. * @type {ImageryLayerCollection}
  327. * @readonly
  328. */
  329. imageryLayers: {
  330. get: function() {
  331. return this.scene.imageryLayers
  332. }
  333. },
  334. /**
  335. * The terrain provider providing surface geometry for the globe.
  336. * @memberof viewer.prototype
  337. *
  338. * @type {TerrainProvider}
  339. */
  340. terrainProvider: {
  341. get: function() {
  342. return this.scene.terrainProvider
  343. },
  344. set: function(terrainProvider) {
  345. this.scene.terrainProvider = terrainProvider
  346. }
  347. },
  348. /**
  349. * Gets the camera.
  350. * @memberof viewer.prototype
  351. *
  352. * @type {Camera}
  353. * @readonly
  354. */
  355. camera: {
  356. get: function() {
  357. return this.scene.camera
  358. }
  359. },
  360. /**
  361. * Gets the post-process stages.
  362. * @memberof viewer.prototype
  363. *
  364. * @type {PostProcessStageCollection}
  365. * @readonly
  366. */
  367. postProcessStages: {
  368. get: function() {
  369. return this.scene.postProcessStages
  370. }
  371. },
  372. /**
  373. * Gets the clock.
  374. * @memberof viewer.prototype
  375. * @type {Clock}
  376. * @readonly
  377. */
  378. clock: {
  379. get: function() {
  380. return this._clock
  381. }
  382. },
  383. /**
  384. * Gets the screen space event handler.
  385. * @memberof viewer.prototype
  386. * @type {ScreenSpaceEventHandler}
  387. * @readonly
  388. */
  389. screenSpaceEventHandler: {
  390. get: function() {
  391. return this._cesiumWidget.screenSpaceEventHandler
  392. }
  393. },
  394. /**
  395. * Gets or sets the target frame rate of the widget when <code>useDefaultRenderLoop</code>
  396. * is true. If undefined, the browser's requestAnimationFrame implementation
  397. * determines the frame rate. If defined, this value must be greater than 0. A value higher
  398. * than the underlying requestAnimationFrame implementation will have no effect.
  399. * @memberof viewer.prototype
  400. *
  401. * @type {Number}
  402. */
  403. targetFrameRate: {
  404. get: function() {
  405. return this._cesiumWidget.targetFrameRate
  406. },
  407. set: function(value) {
  408. this._cesiumWidget.targetFrameRate = value
  409. }
  410. },
  411. /**
  412. * Gets or sets whether or not this widget should control the render loop.
  413. * If true the widget will use requestAnimationFrame to
  414. * perform rendering and resizing of the widget, as well as drive the
  415. * simulation clock. If set to false, you must manually call the
  416. * <code>resize</code>, <code>render</code> methods
  417. * as part of a custom render loop. If an error occurs during rendering, {@link Scene}'s
  418. * <code>renderError</code> event will be raised and this property
  419. * will be set to false. It must be set back to true to continue rendering
  420. * after the error.
  421. * @memberof viewer.prototype
  422. *
  423. * @type {Boolean}
  424. */
  425. useDefaultRenderLoop: {
  426. get: function() {
  427. return this._cesiumWidget.useDefaultRenderLoop
  428. },
  429. set: function(value) {
  430. this._cesiumWidget.useDefaultRenderLoop = value
  431. }
  432. },
  433. /**
  434. * Gets or sets a scaling factor for rendering resolution. Values less than 1.0 can improve
  435. * performance on less powerful devices while values greater than 1.0 will render at a higher
  436. * resolution and then scale down, resulting in improved visual fidelity.
  437. * For example, if the widget is laid out at a size of 640x480, setting this value to 0.5
  438. * will cause the scene to be rendered at 320x240 and then scaled up while setting
  439. * it to 2.0 will cause the scene to be rendered at 1280x960 and then scaled down.
  440. * @memberof viewer.prototype
  441. *
  442. * @type {Number}
  443. * @default 1.0
  444. */
  445. resolutionScale: {
  446. get: function() {
  447. return this._cesiumWidget.resolutionScale
  448. },
  449. set: function(value) {
  450. this._cesiumWidget.resolutionScale = value
  451. }
  452. },
  453. /**
  454. * Boolean flag indicating if the browser's recommended resolution is used.
  455. * If true, the browser's device pixel ratio is ignored and 1.0 is used instead,
  456. * effectively rendering based on CSS pixels instead of device pixels. This can improve
  457. * performance on less powerful devices that have high pixel density. When false, rendering
  458. * will be in device pixels. {@link Viewer#resolutionScale} will still take effect whether
  459. * this flag is true or false.
  460. * @memberof viewer.prototype
  461. *
  462. * @type {Boolean}
  463. * @default true
  464. */
  465. useBrowserRecommendedResolution: {
  466. get: function() {
  467. return this._cesiumWidget.useBrowserRecommendedResolution
  468. },
  469. set: function(value) {
  470. this._cesiumWidget.useBrowserRecommendedResolution = value
  471. }
  472. },
  473. /**
  474. * Gets or sets whether or not data sources can temporarily pause
  475. * animation in order to avoid showing an incomplete picture to the user.
  476. * For example, if asynchronous primitives are being processed in the
  477. * background, the clock will not advance until the geometry is ready.
  478. *
  479. * @memberof viewer.prototype
  480. *
  481. * @type {Boolean}
  482. */
  483. allowDataSourcesToSuspendAnimation: {
  484. get: function() {
  485. return this._allowDataSourcesToSuspendAnimation
  486. },
  487. set: function(value) {
  488. this._allowDataSourcesToSuspendAnimation = value
  489. }
  490. },
  491. /**
  492. * Gets or sets the Entity instance currently being tracked by the camera.
  493. * @memberof viewer.prototype
  494. * @type {Entity | undefined}
  495. */
  496. trackedEntity: {
  497. get: function() {
  498. return this._trackedEntity
  499. },
  500. set: function(value) {
  501. if (this._trackedEntity !== value) {
  502. this._trackedEntity = value
  503. //Cancel any pending zoom
  504. cancelZoom(this)
  505. const scene = this.scene
  506. const sceneMode = scene.mode
  507. //Stop tracking
  508. if (!defined(value) || !defined(value.position)) {
  509. this._needTrackedEntityUpdate = false
  510. if (
  511. sceneMode === SceneMode.COLUMBUS_VIEW ||
  512. sceneMode === SceneMode.SCENE2D
  513. ) {
  514. scene.screenSpaceCameraController.enableTranslate = true
  515. }
  516. if (
  517. sceneMode === SceneMode.COLUMBUS_VIEW ||
  518. sceneMode === SceneMode.SCENE3D
  519. ) {
  520. scene.screenSpaceCameraController.enableTilt = true
  521. }
  522. this._entityView = undefined
  523. this.camera.lookAtTransform(Matrix4.IDENTITY)
  524. } else {
  525. //We can't start tracking immediately, so we set a flag and start tracking
  526. //when the bounding sphere is ready (most likely next frame).
  527. this._needTrackedEntityUpdate = true
  528. }
  529. this._trackedEntityChanged.raiseEvent(value)
  530. this.scene.requestRender()
  531. }
  532. }
  533. },
  534. /**
  535. * Gets or sets the object instance for which to display a selection indicator.
  536. *
  537. * If a user interactively picks a Cesium3DTilesFeature instance, then this property
  538. * will contain a transient Entity instance with a property named "feature" that is
  539. * the instance that was picked.
  540. * @memberof viewer.prototype
  541. * @type {Entity | undefined}
  542. */
  543. selectedEntity: {
  544. get: function() {
  545. return this._selectedEntity
  546. },
  547. set: function(value) {
  548. if (this._selectedEntity !== value) {
  549. this._selectedEntity = value
  550. this._selectedEntityChanged.raiseEvent(value)
  551. }
  552. }
  553. },
  554. /**
  555. * Gets the event that is raised when the selected entity changes.
  556. * @memberof viewer.prototype
  557. * @type {Event}
  558. * @readonly
  559. */
  560. selectedEntityChanged: {
  561. get: function() {
  562. return this._selectedEntityChanged
  563. }
  564. },
  565. /**
  566. * Gets the event that is raised when the tracked entity changes.
  567. * @memberof viewer.prototype
  568. * @type {Event}
  569. * @readonly
  570. */
  571. trackedEntityChanged: {
  572. get: function() {
  573. return this._trackedEntityChanged
  574. }
  575. },
  576. /**
  577. * Gets or sets the data source to track with the viewer's clock.
  578. * @memberof viewer.prototype
  579. * @type {DataSource}
  580. */
  581. clockTrackedDataSource: {
  582. get: function() {
  583. return this._clockTrackedDataSource
  584. },
  585. set: function(value) {
  586. if (this._clockTrackedDataSource !== value) {
  587. this._clockTrackedDataSource = value
  588. trackDataSourceClock(undefined, this.clock, value)
  589. }
  590. }
  591. }
  592. })
  593. /**
  594. *
  595. * @param mixin
  596. * @param options
  597. */
  598. Viewer.prototype.extend = function(mixin, options) {
  599. //>>includeStart('debug', pragmas.debug);
  600. if (!defined(mixin)) {
  601. throw new DeveloperError('mixin is required.')
  602. }
  603. //>>includeEnd('debug')
  604. mixin(this, options)
  605. }
  606. /**
  607. * Resizes the widget to match the container size.
  608. * This function is called automatically as needed unless
  609. * <code>useDefaultRenderLoop</code> is set to false.
  610. */
  611. Viewer.prototype.resize = function() {
  612. const cesiumWidget = this._cesiumWidget
  613. const container = this._container
  614. const width = container.clientWidth
  615. const height = container.clientHeight
  616. cesiumWidget.resize()
  617. if (width === this._lastWidth && height === this._lastHeight) {
  618. return
  619. }
  620. let creditLeft = 0
  621. let creditBottom = 0
  622. this._bottomContainer.style.left = `${creditLeft}px`
  623. this._bottomContainer.style.bottom = `${creditBottom}px`
  624. this._lastWidth = width
  625. this._lastHeight = height
  626. }
  627. /**
  628. * This forces the widget to re-think its layout, including
  629. * widget sizes and credit placement.
  630. */
  631. Viewer.prototype.forceResize = function() {
  632. this._lastWidth = 0
  633. this.resize()
  634. }
  635. /**
  636. * Renders the scene. This function is called automatically
  637. * unless <code>useDefaultRenderLoop</code> is set to false;
  638. */
  639. Viewer.prototype.render = function() {
  640. this._cesiumWidget.render()
  641. }
  642. /**
  643. * @returns {Boolean} true if the object has been destroyed, false otherwise.
  644. */
  645. Viewer.prototype.isDestroyed = function() {
  646. return false
  647. }
  648. /**
  649. * Destroys the widget. Should be called if permanently
  650. * removing the widget from layout.
  651. */
  652. Viewer.prototype.destroy = function() {
  653. let i
  654. // Unsubscribe from data sources
  655. const dataSources = this.dataSources
  656. const dataSourceLength = dataSources.length
  657. for (i = 0; i < dataSourceLength; i++) {
  658. this._dataSourceRemoved(dataSources, dataSources.get(i))
  659. }
  660. this._dataSourceRemoved(undefined, this._dataSourceDisplay.defaultDataSource)
  661. this._container.removeChild(this._element)
  662. this._element.removeChild(this._toolbar)
  663. this._eventHelper.removeAll()
  664. this._dataSourceDisplay = this._dataSourceDisplay.destroy()
  665. this._cesiumWidget = this._cesiumWidget.destroy()
  666. if (this._destroyDataSourceCollection) {
  667. this._dataSourceCollection = this._dataSourceCollection.destroy()
  668. }
  669. return destroyObject(this)
  670. }
  671. /**
  672. * @private
  673. */
  674. Viewer.prototype._dataSourceAdded = function(dataSourceCollection, dataSource) {
  675. const entityCollection = dataSource.entities
  676. entityCollection.collectionChanged.addEventListener(
  677. Viewer.prototype._onEntityCollectionChanged,
  678. this
  679. )
  680. }
  681. /**
  682. * @private
  683. */
  684. Viewer.prototype._dataSourceRemoved = function(
  685. dataSourceCollection,
  686. dataSource
  687. ) {
  688. const entityCollection = dataSource.entities
  689. entityCollection.collectionChanged.removeEventListener(
  690. Viewer.prototype._onEntityCollectionChanged,
  691. this
  692. )
  693. if (defined(this.trackedEntity)) {
  694. if (
  695. entityCollection.getById(this.trackedEntity.id) === this.trackedEntity
  696. ) {
  697. this.trackedEntity = undefined
  698. }
  699. }
  700. if (defined(this.selectedEntity)) {
  701. if (
  702. entityCollection.getById(this.selectedEntity.id) === this.selectedEntity
  703. ) {
  704. this.selectedEntity = undefined
  705. }
  706. }
  707. }
  708. /**
  709. * @private
  710. */
  711. Viewer.prototype._onTick = function(clock) {
  712. const time = clock.currentTime
  713. const isUpdated = this._dataSourceDisplay.update(time)
  714. if (this._allowDataSourcesToSuspendAnimation) {
  715. this._clock.canAnimate = isUpdated
  716. }
  717. const entityView = this._entityView
  718. if (defined(entityView)) {
  719. const trackedEntity = this._trackedEntity
  720. const trackedState = this._dataSourceDisplay.getBoundingSphere(
  721. trackedEntity,
  722. false,
  723. boundingSphereScratch
  724. )
  725. if (trackedState === BoundingSphereState.DONE) {
  726. entityView.update(time, boundingSphereScratch)
  727. }
  728. }
  729. let position
  730. let enableCamera = false
  731. const selectedEntity = this.selectedEntity
  732. const showSelection = defined(selectedEntity) && this._enableInfoOrSelection
  733. if (
  734. showSelection &&
  735. selectedEntity.isShowing &&
  736. selectedEntity.isAvailable(time)
  737. ) {
  738. const state = this._dataSourceDisplay.getBoundingSphere(
  739. selectedEntity,
  740. true,
  741. boundingSphereScratch
  742. )
  743. if (state !== BoundingSphereState.FAILED) {
  744. position = boundingSphereScratch.center
  745. } else if (defined(selectedEntity.position)) {
  746. position = selectedEntity.position.getValue(time, position)
  747. }
  748. enableCamera = defined(position)
  749. }
  750. }
  751. /**
  752. * @private
  753. */
  754. Viewer.prototype._onEntityCollectionChanged = function(
  755. collection,
  756. added,
  757. removed
  758. ) {
  759. const length = removed.length
  760. for (let i = 0; i < length; i++) {
  761. const removedObject = removed[i]
  762. if (this.trackedEntity === removedObject) {
  763. this.trackedEntity = undefined
  764. }
  765. if (this.selectedEntity === removedObject) {
  766. this.selectedEntity = undefined
  767. }
  768. }
  769. }
  770. /**
  771. * @private
  772. */
  773. Viewer.prototype._onInfoBoxCameraClicked = function(infoBoxViewModel) {
  774. if (
  775. infoBoxViewModel.isCameraTracking &&
  776. this.trackedEntity === this.selectedEntity
  777. ) {
  778. this.trackedEntity = undefined
  779. } else {
  780. const selectedEntity = this.selectedEntity
  781. const position = selectedEntity.position
  782. if (defined(position)) {
  783. this.trackedEntity = this.selectedEntity
  784. } else {
  785. this.zoomTo(this.selectedEntity)
  786. }
  787. }
  788. }
  789. /**
  790. * @private
  791. */
  792. Viewer.prototype._clearTrackedObject = function() {
  793. this.trackedEntity = undefined
  794. }
  795. /**
  796. * @private
  797. */
  798. Viewer.prototype._clearObjects = function() {
  799. this.trackedEntity = undefined
  800. this.selectedEntity = undefined
  801. }
  802. /**
  803. * @private
  804. */
  805. Viewer.prototype._onDataSourceChanged = function(dataSource) {
  806. if (this.clockTrackedDataSource === dataSource) {
  807. trackDataSourceClock(undefined, this.clock, dataSource)
  808. }
  809. }
  810. /**
  811. * @private
  812. */
  813. Viewer.prototype._onDataSourceAdded = function(
  814. dataSourceCollection,
  815. dataSource
  816. ) {
  817. if (this._automaticallyTrackDataSourceClocks) {
  818. this.clockTrackedDataSource = dataSource
  819. }
  820. const id = dataSource.entities.id
  821. const removalFunc = this._eventHelper.add(
  822. dataSource.changedEvent,
  823. Viewer.prototype._onDataSourceChanged,
  824. this
  825. )
  826. this._dataSourceChangedListeners[id] = removalFunc
  827. }
  828. /**
  829. * @private
  830. */
  831. Viewer.prototype._onDataSourceRemoved = function(
  832. dataSourceCollection,
  833. dataSource
  834. ) {
  835. const resetClock = this.clockTrackedDataSource === dataSource
  836. const id = dataSource.entities.id
  837. this._dataSourceChangedListeners[id]()
  838. this._dataSourceChangedListeners[id] = undefined
  839. if (resetClock) {
  840. const numDataSources = dataSourceCollection.length
  841. if (this._automaticallyTrackDataSourceClocks && numDataSources > 0) {
  842. this.clockTrackedDataSource = dataSourceCollection.get(numDataSources - 1)
  843. } else {
  844. this.clockTrackedDataSource = undefined
  845. }
  846. }
  847. }
  848. /**
  849. * Asynchronously sets the camera to view the provided entity, entities, or data source.
  850. * If the data source is still in the process of loading or the visualization is otherwise still loading,
  851. * this method waits for the data to be ready before performing the zoom.
  852. *
  853. * <p>The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
  854. * The heading and the pitch angles are defined in the local east-north-up reference frame.
  855. * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
  856. * angles are above the plane. Negative pitch angles are below the plane. The range is the distance from the center. If the range is
  857. * zero, a range will be computed such that the whole bounding sphere is visible.</p>
  858. *
  859. * <p>In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
  860. * target will be the range. The heading will be determined from the offset. If the heading cannot be
  861. * determined from the offset, the heading will be north.</p>
  862. *
  863. * @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.
  864. * @param {HeadingPitchRange} [offset] The offset from the center of the entity in the local east-north-up reference frame.
  865. * @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.
  866. */
  867. Viewer.prototype.zoomTo = function(target, offset) {
  868. const options = {
  869. offset: offset
  870. }
  871. return zoomToOrFly(this, target, options, false)
  872. }
  873. /**
  874. * Flies the camera to the provided entity, entities, or data source.
  875. * If the data source is still in the process of loading or the visualization is otherwise still loading,
  876. * this method waits for the data to be ready before performing the flight.
  877. *
  878. * <p>The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
  879. * The heading and the pitch angles are defined in the local east-north-up reference frame.
  880. * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
  881. * angles are above the plane. Negative pitch angles are below the plane. The range is the distance from the center. If the range is
  882. * zero, a range will be computed such that the whole bounding sphere is visible.</p>
  883. *
  884. * <p>In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
  885. * target will be the range. The heading will be determined from the offset. If the heading cannot be
  886. * determined from the offset, the heading will be north.</p>
  887. *
  888. * @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.
  889. * @param {Object} [options] Object with the following properties:
  890. * @param {Number} [options.duration=3.0] The duration of the flight in seconds.
  891. * @param {Number} [options.maximumHeight] The maximum height at the peak of the flight.
  892. * @param {HeadingPitchRange} [options.offset] The offset from the target in the local east-north-up reference frame centered at the target.
  893. * @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
  894. */
  895. Viewer.prototype.flyTo = function(target, options) {
  896. return zoomToOrFly(this, target, options, true)
  897. }
  898. function zoomToOrFly(that, zoomTarget, options, isFlight) {
  899. //>>includeStart('debug', pragmas.debug);
  900. if (!defined(zoomTarget)) {
  901. throw new DeveloperError('zoomTarget is required.')
  902. }
  903. //>>includeEnd('debug');
  904. cancelZoom(that)
  905. //We can't actually perform the zoom until all visualization is ready and
  906. //bounding spheres have been computed. Therefore we create and return
  907. //a deferred which will be resolved as part of the post-render step in the
  908. //frame that actually performs the zoom.
  909. const zoomPromise = new Promise(resolve => {
  910. that._completeZoom = function(value) {
  911. resolve(value)
  912. }
  913. })
  914. that._zoomPromise = zoomPromise
  915. that._zoomIsFlight = isFlight
  916. that._zoomOptions = options
  917. Promise.resolve(zoomTarget).then(function(zoomTarget) {
  918. //Only perform the zoom if it wasn't cancelled before the promise resolved.
  919. if (that._zoomPromise !== zoomPromise) {
  920. return
  921. }
  922. //If the zoom target is a rectangular imagery in an ImageLayer
  923. if (zoomTarget instanceof ImageryLayer) {
  924. zoomTarget
  925. .getViewableRectangle()
  926. .then(function(rectangle) {
  927. return computeFlyToLocationForRectangle(rectangle, that.scene)
  928. })
  929. .then(function(position) {
  930. //Only perform the zoom if it wasn't cancelled before the promise was resolved
  931. if (that._zoomPromise === zoomPromise) {
  932. that._zoomTarget = position
  933. }
  934. })
  935. return
  936. }
  937. //If the zoom target is a Cesium3DTileset
  938. if (zoomTarget instanceof Cesium3DTileset) {
  939. that._zoomTarget = zoomTarget
  940. return
  941. }
  942. //If the zoom target is a TimeDynamicPointCloud
  943. if (zoomTarget instanceof TimeDynamicPointCloud) {
  944. that._zoomTarget = zoomTarget
  945. return
  946. }
  947. //If the zoom target is a data source, and it's in the middle of loading, wait for it to finish loading.
  948. if (zoomTarget.isLoading && defined(zoomTarget.loadingEvent)) {
  949. const removeEvent = zoomTarget.loadingEvent.addEventListener(function() {
  950. removeEvent()
  951. //Only perform the zoom if it wasn't cancelled before the data source finished.
  952. if (that._zoomPromise === zoomPromise) {
  953. that._zoomTarget = zoomTarget.entities.values.slice(0)
  954. }
  955. })
  956. return
  957. }
  958. //Zoom target is already an array, just copy it and return.
  959. if (Array.isArray(zoomTarget)) {
  960. that._zoomTarget = zoomTarget.slice(0)
  961. return
  962. }
  963. //If zoomTarget is an EntityCollection, this will retrieve the array
  964. zoomTarget = defaultValue(zoomTarget.values, zoomTarget)
  965. //If zoomTarget is a DataSource, this will retrieve the array.
  966. if (defined(zoomTarget.entities)) {
  967. zoomTarget = zoomTarget.entities.values
  968. }
  969. //Zoom target is already an array, just copy it and return.
  970. if (Array.isArray(zoomTarget)) {
  971. that._zoomTarget = zoomTarget.slice(0)
  972. } else {
  973. //Single entity
  974. that._zoomTarget = [zoomTarget]
  975. }
  976. })
  977. that.scene.requestRender()
  978. return zoomPromise
  979. }
  980. function clearZoom(viewer) {
  981. viewer._zoomPromise = undefined
  982. viewer._zoomTarget = undefined
  983. viewer._zoomOptions = undefined
  984. }
  985. function cancelZoom(viewer) {
  986. const zoomPromise = viewer._zoomPromise
  987. if (defined(zoomPromise)) {
  988. clearZoom(viewer)
  989. viewer._completeZoom(false)
  990. }
  991. }
  992. /**
  993. * @private
  994. */
  995. Viewer.prototype._postRender = function() {
  996. updateZoomTarget(this)
  997. updateTrackedEntity(this)
  998. }
  999. function updateZoomTarget(viewer) {
  1000. const target = viewer._zoomTarget
  1001. if (!defined(target) || viewer.scene.mode === SceneMode.MORPHING) {
  1002. return
  1003. }
  1004. const scene = viewer.scene
  1005. const camera = scene.camera
  1006. const zoomOptions = defaultValue(viewer._zoomOptions, {})
  1007. let options
  1008. // If zoomTarget was Cesium3DTileset
  1009. if (target instanceof Cesium3DTileset) {
  1010. return target.readyPromise
  1011. .then(function() {
  1012. const boundingSphere = target.boundingSphere
  1013. // If offset was originally undefined then give it base value instead of empty object
  1014. if (!defined(zoomOptions.offset)) {
  1015. zoomOptions.offset = new HeadingPitchRange(
  1016. 0.0,
  1017. -0.5,
  1018. boundingSphere.radius
  1019. )
  1020. }
  1021. options = {
  1022. offset: zoomOptions.offset,
  1023. duration: zoomOptions.duration,
  1024. maximumHeight: zoomOptions.maximumHeight,
  1025. complete: function() {
  1026. viewer._completeZoom(true)
  1027. },
  1028. cancel: function() {
  1029. viewer._completeZoom(false)
  1030. }
  1031. }
  1032. if (viewer._zoomIsFlight) {
  1033. camera.flyToBoundingSphere(target.boundingSphere, options)
  1034. } else {
  1035. camera.viewBoundingSphere(boundingSphere, zoomOptions.offset)
  1036. camera.lookAtTransform(Matrix4.IDENTITY)
  1037. // Finish the promise
  1038. viewer._completeZoom(true)
  1039. }
  1040. clearZoom(viewer)
  1041. })
  1042. .catch(() => {
  1043. cancelZoom(viewer)
  1044. })
  1045. }
  1046. // If zoomTarget was TimeDynamicPointCloud
  1047. if (target instanceof TimeDynamicPointCloud) {
  1048. return target.readyPromise.then(function() {
  1049. const boundingSphere = target.boundingSphere
  1050. // If offset was originally undefined then give it base value instead of empty object
  1051. if (!defined(zoomOptions.offset)) {
  1052. zoomOptions.offset = new HeadingPitchRange(
  1053. 0.0,
  1054. -0.5,
  1055. boundingSphere.radius
  1056. )
  1057. }
  1058. options = {
  1059. offset: zoomOptions.offset,
  1060. duration: zoomOptions.duration,
  1061. maximumHeight: zoomOptions.maximumHeight,
  1062. complete: function() {
  1063. viewer._completeZoom(true)
  1064. },
  1065. cancel: function() {
  1066. viewer._completeZoom(false)
  1067. }
  1068. }
  1069. if (viewer._zoomIsFlight) {
  1070. camera.flyToBoundingSphere(boundingSphere, options)
  1071. } else {
  1072. camera.viewBoundingSphere(boundingSphere, zoomOptions.offset)
  1073. camera.lookAtTransform(Matrix4.IDENTITY)
  1074. // Finish the promise
  1075. viewer._completeZoom(true)
  1076. }
  1077. clearZoom(viewer)
  1078. })
  1079. }
  1080. // If zoomTarget was an ImageryLayer
  1081. if (target instanceof Cartographic) {
  1082. options = {
  1083. destination: scene.mapProjection.ellipsoid.cartographicToCartesian(
  1084. target
  1085. ),
  1086. duration: zoomOptions.duration,
  1087. maximumHeight: zoomOptions.maximumHeight,
  1088. complete: function() {
  1089. viewer._completeZoom(true)
  1090. },
  1091. cancel: function() {
  1092. viewer._completeZoom(false)
  1093. }
  1094. }
  1095. if (viewer._zoomIsFlight) {
  1096. camera.flyTo(options)
  1097. } else {
  1098. camera.setView(options)
  1099. viewer._completeZoom(true)
  1100. }
  1101. clearZoom(viewer)
  1102. return
  1103. }
  1104. const entities = target
  1105. const boundingSpheres = []
  1106. for (let i = 0, len = entities.length; i < len; i++) {
  1107. const state = viewer._dataSourceDisplay.getBoundingSphere(
  1108. entities[i],
  1109. false,
  1110. boundingSphereScratch
  1111. )
  1112. if (state === BoundingSphereState.PENDING) {
  1113. return
  1114. } else if (state !== BoundingSphereState.FAILED) {
  1115. boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch))
  1116. }
  1117. }
  1118. if (boundingSpheres.length === 0) {
  1119. cancelZoom(viewer)
  1120. return
  1121. }
  1122. //Stop tracking the current entity.
  1123. viewer.trackedEntity = undefined
  1124. const boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres)
  1125. if (!viewer._zoomIsFlight) {
  1126. camera.viewBoundingSphere(boundingSphere, zoomOptions.offset)
  1127. camera.lookAtTransform(Matrix4.IDENTITY)
  1128. clearZoom(viewer)
  1129. viewer._completeZoom(true)
  1130. } else {
  1131. clearZoom(viewer)
  1132. camera.flyToBoundingSphere(boundingSphere, {
  1133. duration: zoomOptions.duration,
  1134. maximumHeight: zoomOptions.maximumHeight,
  1135. complete: function() {
  1136. viewer._completeZoom(true)
  1137. },
  1138. cancel: function() {
  1139. viewer._completeZoom(false)
  1140. },
  1141. offset: zoomOptions.offset
  1142. })
  1143. }
  1144. }
  1145. function updateTrackedEntity(viewer) {
  1146. if (!viewer._needTrackedEntityUpdate) {
  1147. return
  1148. }
  1149. const trackedEntity = viewer._trackedEntity
  1150. const currentTime = viewer.clock.currentTime
  1151. //Verify we have a current position at this time. This is only triggered if a position
  1152. //has become undefined after trackedEntity is set but before the boundingSphere has been
  1153. //computed. In this case, we will track the entity once it comes back into existence.
  1154. const currentPosition = Property.getValueOrUndefined(
  1155. trackedEntity.position,
  1156. currentTime
  1157. )
  1158. if (!defined(currentPosition)) {
  1159. return
  1160. }
  1161. const scene = viewer.scene
  1162. const state = viewer._dataSourceDisplay.getBoundingSphere(
  1163. trackedEntity,
  1164. false,
  1165. boundingSphereScratch
  1166. )
  1167. if (state === BoundingSphereState.PENDING) {
  1168. return
  1169. }
  1170. const sceneMode = scene.mode
  1171. if (
  1172. sceneMode === SceneMode.COLUMBUS_VIEW ||
  1173. sceneMode === SceneMode.SCENE2D
  1174. ) {
  1175. scene.screenSpaceCameraController.enableTranslate = false
  1176. }
  1177. if (
  1178. sceneMode === SceneMode.COLUMBUS_VIEW ||
  1179. sceneMode === SceneMode.SCENE3D
  1180. ) {
  1181. scene.screenSpaceCameraController.enableTilt = false
  1182. }
  1183. const bs =
  1184. state !== BoundingSphereState.FAILED ? boundingSphereScratch : undefined
  1185. viewer._entityView = new EntityView(
  1186. trackedEntity,
  1187. scene,
  1188. scene.mapProjection.ellipsoid
  1189. )
  1190. viewer._entityView.update(currentTime, bs)
  1191. viewer._needTrackedEntityUpdate = false
  1192. }
  1193. export default Viewer