You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. /**
  2. * @Author: Caven
  3. * @Date: 2019-12-27 17:13:24
  4. */
  5. import { Cesium } from '@dc-modules/namespace'
  6. import Parse from '@dc-modules/parse/Parse'
  7. import {
  8. LayerGroupEventType,
  9. LayerEventType,
  10. MouseEvent,
  11. ViewerEvent,
  12. SceneEvent
  13. } from '@dc-modules/event'
  14. import { ViewerOption, CameraOption } from '@dc-modules/option'
  15. import { Util, DomUtil } from '@dc-modules/utils'
  16. import { Transform } from '@dc-modules/transform'
  17. import createWidgets from '@dc-modules/widget'
  18. import createTools from '@dc-modules/tools'
  19. const DEF_OPTS = {
  20. creditContainer: undefined,
  21. shouldAnimate: true
  22. }
  23. class Viewer {
  24. constructor(id, options = {}) {
  25. if (!id || (typeof id === 'string' && !document.getElementById(id))) {
  26. throw new Error('Viewer:the id is empty')
  27. }
  28. this._delegate = new Cesium.Viewer(id, {
  29. ...DEF_OPTS,
  30. ...options
  31. }) // Initialize the viewer
  32. /**
  33. * Registers events
  34. */
  35. new MouseEvent(this) // Register global mouse events
  36. this._viewerEvent = new ViewerEvent() // Register viewer events
  37. this._sceneEvent = new SceneEvent(this) // Register scene events
  38. this._viewerOption = new ViewerOption(this) // Initialize the viewer option
  39. this._cameraOption = new CameraOption(this) // Initialize the camera option
  40. this._dcContainer = DomUtil.create(
  41. 'div',
  42. 'dc-container',
  43. typeof id === 'string' ? document.getElementById(id) : id
  44. ) //Register the custom container
  45. this._baseLayerPicker = new Cesium.BaseLayerPicker({
  46. globe: this._delegate.scene.globe
  47. }) //Initialize the baseLayer picker
  48. this._layerGroupCache = {}
  49. this._layerCache = {}
  50. /**
  51. * Registers default widgets
  52. */
  53. let widgets = createWidgets()
  54. Object.keys(widgets).forEach(key => {
  55. this.use(widgets[key])
  56. })
  57. /**
  58. * Registers default tools
  59. */
  60. let tools = createTools()
  61. Object.keys(tools).forEach(key => {
  62. this.use(tools[key])
  63. })
  64. }
  65. get delegate() {
  66. return this._delegate
  67. }
  68. get dcContainer() {
  69. return this._dcContainer
  70. }
  71. get scene() {
  72. return this._delegate.scene
  73. }
  74. get camera() {
  75. return this._delegate.camera
  76. }
  77. get canvas() {
  78. return this._delegate.scene.canvas
  79. }
  80. get dataSources() {
  81. return this._delegate.dataSources
  82. }
  83. get imageryLayers() {
  84. return this._delegate.imageryLayers
  85. }
  86. get terrainProvider() {
  87. return this._delegate.terrainProvider
  88. }
  89. get entities() {
  90. return this._delegate.entities
  91. }
  92. get postProcessStages() {
  93. return this._delegate.scene.postProcessStages
  94. }
  95. get clock() {
  96. return this._delegate.clock
  97. }
  98. get viewerEvent() {
  99. return this._viewerEvent
  100. }
  101. get cameraPosition() {
  102. let position = Transform.transformCartesianToWGS84(this.camera.positionWC)
  103. if (position) {
  104. position.heading = Cesium.Math.toDegrees(this.camera.heading)
  105. position.pitch = Cesium.Math.toDegrees(this.camera.pitch)
  106. position.roll = Cesium.Math.toDegrees(this.camera.roll)
  107. }
  108. return position
  109. }
  110. get resolution() {
  111. let width = this.scene.canvas.width
  112. let height = this.scene.canvas.height
  113. let min = Transform.transformWindowToWGS84(
  114. new Cesium.Cartesian2((width / 2) | 0, height - 1),
  115. this
  116. )
  117. let max = Transform.transformWindowToWGS84(
  118. new Cesium.Cartesian2((1 + width / 2) | 0, height - 1),
  119. this
  120. )
  121. if (!min || !max) {
  122. return 1
  123. }
  124. return Math.abs(min.lng - max.lng)
  125. }
  126. get viewBounds() {
  127. let width = this.scene.canvas.width
  128. let height = this.scene.canvas.height
  129. let min = Transform.transformWindowToWGS84(
  130. new Cesium.Cartesian2(0, height),
  131. this
  132. )
  133. let max = Transform.transformWindowToWGS84(
  134. new Cesium.Cartesian2(width, 0),
  135. this
  136. )
  137. if (!min || !max) {
  138. return Cesium.Rectangle.MAX_VALUE
  139. }
  140. return Cesium.Rectangle.fromDegrees(min.lng, min.lat, max.lng, max.lat)
  141. }
  142. get level() {
  143. return this._delegate.scene.globe._surface._debug.maxDepthVisited
  144. }
  145. /***
  146. *
  147. * @param layerGroup
  148. * @private
  149. */
  150. _addLayerGroup(layerGroup) {
  151. if (
  152. layerGroup?.layerGroupEvent &&
  153. !Object(this._layerGroupCache).hasOwnProperty(layerGroup.id)
  154. ) {
  155. layerGroup.layerGroupEvent.fire(LayerGroupEventType.ADD, this)
  156. this._layerGroupCache[layerGroup.id] = layerGroup
  157. }
  158. }
  159. /**
  160. *
  161. * @param layerGroup
  162. * @private
  163. */
  164. _removeLayerGroup(layerGroup) {
  165. if (
  166. layerGroup?.layerGroupEvent &&
  167. Object(this._layerGroupCache).hasOwnProperty(layerGroup.id)
  168. ) {
  169. layerGroup.layerGroupEvent.fire(LayerGroupEventType.REMOVE, this)
  170. delete this._layerGroupCache[layerGroup.id]
  171. }
  172. }
  173. /**
  174. * @param layer
  175. * @private
  176. */
  177. _addLayer(layer) {
  178. !this._layerCache[layer.type] && (this._layerCache[layer.type] = {})
  179. if (!Object(this._layerCache[layer.type]).hasOwnProperty(layer.id)) {
  180. layer.fire(LayerEventType.ADD, this)
  181. this._layerCache[layer.type][layer.id] = layer
  182. }
  183. }
  184. /**
  185. * @param layer
  186. * @private
  187. */
  188. _removeLayer(layer) {
  189. if (Object(this._layerCache[layer.type]).hasOwnProperty(layer.id)) {
  190. layer.fire(LayerEventType.REMOVE, this)
  191. delete this._layerCache[layer.type][layer.id]
  192. }
  193. }
  194. /**
  195. * Sets viewer options
  196. * @param options
  197. * @returns {Viewer}
  198. */
  199. setOptions(options) {
  200. this._viewerOption.setOptions(options)
  201. return this
  202. }
  203. /**
  204. * Sets camera pitch range
  205. * @param min
  206. * @param max
  207. * @returns {Viewer}
  208. */
  209. setPitchRange(min = -90, max = -20) {
  210. this._cameraOption.setPitchRange(min, max)
  211. return this
  212. }
  213. /**
  214. * @param west
  215. * @param south
  216. * @param east
  217. * @param north
  218. * @returns {Viewer}
  219. */
  220. setBounds(west, south, east, north) {
  221. this._cameraOption.setBounds(west, south, east, north)
  222. return this
  223. }
  224. /**
  225. * Changes Scene Mode,2:2D,2.5:2.5D,3:3D
  226. * @param sceneMode
  227. * @param duration
  228. * @returns {Viewer}
  229. */
  230. changeSceneMode(sceneMode, duration = 0) {
  231. if (sceneMode === 2) {
  232. this._delegate.scene.morphTo2D(duration)
  233. } else if (sceneMode === 3) {
  234. this._delegate.scene.morphTo3D(duration)
  235. } else if (sceneMode === 2.5) {
  236. this._delegate.scene.morphToColumbusView(duration)
  237. }
  238. return this
  239. }
  240. /**
  241. * Changes Mouse Mode,0:Default,1: Change the tiltEventTypes to CameraEventType.RIGHT_DRAG
  242. * @param mouseMode
  243. * @returns {Viewer}
  244. */
  245. changeMouseMode(mouseMode) {
  246. this._cameraOption.changeMouseMode(mouseMode)
  247. return this
  248. }
  249. /**
  250. * Adds the baseLayer .
  251. * The baseLayer can be a single or an array,
  252. * and when the baseLayer is an array, the baseLayer will be loaded together
  253. * @param baseLayers
  254. * @param options
  255. * @returns {Viewer}
  256. */
  257. addBaseLayer(baseLayers, options = {}) {
  258. if (!baseLayers) {
  259. return this
  260. }
  261. this._baseLayerPicker.addImageryProvider(baseLayers)
  262. if (!this._baseLayerPicker.selectedImagery) {
  263. this._baseLayerPicker.changeImagery(0)
  264. }
  265. this.mapSwitch && this.mapSwitch.addMap(options)
  266. return this
  267. }
  268. /**
  269. * Changes the current globe display of the baseLayer
  270. * @param index
  271. * @returns {Viewer}
  272. */
  273. changeBaseLayer(index) {
  274. this._baseLayerPicker.changeImagery(index)
  275. return this
  276. }
  277. /**
  278. *
  279. * @param windowPosition
  280. * @returns {Promise}
  281. */
  282. getImageryLayerInfo(windowPosition) {
  283. let ray = this._delegate.camera.getPickRay(windowPosition)
  284. return this._delegate.imageryLayers.pickImageryLayerFeatures(
  285. ray,
  286. this._delegate.scene
  287. )
  288. }
  289. /**
  290. *
  291. * @param terrain
  292. * @return {Viewer}
  293. */
  294. addTerrain(terrain) {
  295. if (!terrain) {
  296. return this
  297. }
  298. this._baseLayerPicker.addTerrainProvider(terrain)
  299. if (!this._baseLayerPicker.selectedTerrain) {
  300. this._baseLayerPicker.changeTerrain(0)
  301. }
  302. return this
  303. }
  304. /**
  305. * Changes the current globe display of the terrain
  306. * @param index
  307. * @returns {Viewer}
  308. */
  309. changeTerrain(index) {
  310. this._baseLayerPicker.changeTerrain(index)
  311. return this
  312. }
  313. /**
  314. * Removes terrain
  315. * @returns {Viewer}
  316. */
  317. removeTerrain() {
  318. this._delegate.terrainProvider = new Cesium.EllipsoidTerrainProvider()
  319. return this
  320. }
  321. /**
  322. *
  323. * @param layerGroup
  324. * @returns {Viewer}
  325. */
  326. addLayerGroup(layerGroup) {
  327. this._addLayerGroup(layerGroup)
  328. return this
  329. }
  330. /**
  331. *
  332. * @param layerGroup
  333. * @returns {Viewer}
  334. */
  335. removeLayerGroup(layerGroup) {
  336. this._removeLayerGroup(layerGroup)
  337. return this
  338. }
  339. /**
  340. *
  341. * @param id
  342. * @returns {undefined}
  343. */
  344. getLayerGroup(id) {
  345. return this._layerGroupCache[id] || undefined
  346. }
  347. /**
  348. * add a layer
  349. * @param layer
  350. * @returns {Viewer}
  351. */
  352. addLayer(layer) {
  353. this._addLayer(layer)
  354. return this
  355. }
  356. /**
  357. * Removes a layer
  358. * @param layer
  359. * @returns {Viewer}
  360. */
  361. removeLayer(layer) {
  362. this._removeLayer(layer)
  363. return this
  364. }
  365. /**
  366. * Checks to see if the layer is included
  367. * @param layer
  368. * @returns {boolean}
  369. */
  370. hasLayer(layer) {
  371. return Object(this._layerCache[layer.type]).hasOwnProperty(layer.id)
  372. }
  373. /**
  374. * Returns a layer by id
  375. * @param id
  376. * @returns {*|undefined}
  377. */
  378. getLayer(id) {
  379. let filters = this.getLayers().filter(item => item.id === id)
  380. return filters && filters.length ? filters[0] : undefined
  381. }
  382. /**
  383. * Returns all layers
  384. * @returns {[]}
  385. */
  386. getLayers() {
  387. let result = []
  388. Object.keys(this._layerCache).forEach(type => {
  389. let cache = this._layerCache[type]
  390. Object.keys(cache).forEach(layerId => {
  391. result.push(cache[layerId])
  392. })
  393. })
  394. return result
  395. }
  396. /**
  397. * Iterate through each layer and pass it as an argument to the callback function
  398. * @param method
  399. * @param context
  400. * @returns {Viewer}
  401. */
  402. eachLayer(method, context) {
  403. Object.keys(this._layerCache).forEach(type => {
  404. let cache = this._layerCache[type]
  405. Object.keys(cache).forEach(layerId => {
  406. method.call(context, cache[layerId])
  407. })
  408. })
  409. return this
  410. }
  411. /**
  412. * @param target
  413. * @param duration
  414. * @returns {Viewer}
  415. */
  416. flyTo(target, duration) {
  417. this._delegate.flyTo(target?.delegate || target, {
  418. duration
  419. })
  420. return this
  421. }
  422. /**
  423. * @param target
  424. * @returns {Viewer}
  425. */
  426. zoomTo(target) {
  427. this._delegate.zoomTo(target?.delegate || target)
  428. return this
  429. }
  430. /**
  431. * Camera fly to a position
  432. * @param position
  433. * @param completeCallback
  434. * @param duration
  435. * @returns {Viewer}
  436. */
  437. flyToPosition(position, completeCallback, duration) {
  438. position = Parse.parsePosition(position)
  439. this.camera.flyTo({
  440. destination: Transform.transformWGS84ToCartesian(position),
  441. orientation: {
  442. heading: Cesium.Math.toRadians(position.heading),
  443. pitch: Cesium.Math.toRadians(position.pitch),
  444. roll: Cesium.Math.toRadians(position.roll)
  445. },
  446. complete: completeCallback,
  447. duration: duration
  448. })
  449. return this
  450. }
  451. /**
  452. * Camera zoom to a position
  453. * @param position
  454. * @param completeCallback
  455. * @returns {Viewer}
  456. */
  457. zoomToPosition(position, completeCallback) {
  458. this.flyToPosition(position, completeCallback, 0)
  459. return this
  460. }
  461. /**
  462. * Camera fly to bounds
  463. * @param bounds
  464. * @param heading
  465. * @param pitch
  466. * @param roll
  467. * @param completeCallback
  468. * @param duration
  469. * @return {Viewer}
  470. */
  471. flyToBounds(
  472. bounds,
  473. { heading = 0, pitch = 0, roll = 0 },
  474. completeCallback,
  475. duration
  476. ) {
  477. if (!bounds) {
  478. return this
  479. }
  480. if (!Array.isArray(bounds)) {
  481. bounds = bounds.split(',')
  482. }
  483. this.camera.flyTo({
  484. destination: Cesium.Rectangle.fromDegrees(
  485. bounds[0],
  486. bounds[1],
  487. bounds[2],
  488. bounds[3]
  489. ),
  490. orientation: {
  491. heading: Cesium.Math.toRadians(heading),
  492. pitch: Cesium.Math.toRadians(pitch),
  493. roll: Cesium.Math.toRadians(roll)
  494. },
  495. complete: completeCallback,
  496. duration: duration
  497. })
  498. return this
  499. }
  500. /**
  501. *
  502. * @param bounds
  503. * @param heading
  504. * @param pitch
  505. * @param roll
  506. * @param completeCallback
  507. * @return {Viewer}
  508. */
  509. zoomToBounds(bounds, { heading = 0, pitch = 0, roll = 0 }, completeCallback) {
  510. this.flyToBounds(bounds, { heading, pitch, roll }, completeCallback)
  511. return this
  512. }
  513. /**
  514. *
  515. * @param type
  516. * @param callback
  517. * @param context
  518. * @returns {Viewer}
  519. */
  520. on(type, callback, context) {
  521. this._viewerEvent.on(type, callback, context || this)
  522. this._sceneEvent.on(type, callback, context || this)
  523. return this
  524. }
  525. /**
  526. *
  527. * @param type
  528. * @param callback
  529. * @param context
  530. * @returns {Viewer}
  531. */
  532. once(type, callback, context) {
  533. this._viewerEvent.once(type, callback, context || this)
  534. return this
  535. }
  536. /**
  537. *
  538. * @param type
  539. * @param callback
  540. * @param context
  541. * @returns {Viewer}
  542. */
  543. off(type, callback, context) {
  544. this._viewerEvent.off(type, callback, context || this)
  545. this._sceneEvent.off(type, callback, context || this)
  546. return this
  547. }
  548. /**
  549. * Destroys the viewer.
  550. */
  551. destroy() {
  552. Object.keys(this._layerCache).forEach(type => {
  553. let cache = this._layerCache[type]
  554. Object.keys(cache).forEach(layerId => {
  555. this._removeLayer(cache[layerId])
  556. })
  557. })
  558. this._delegate.destroy()
  559. this._delegate = undefined
  560. this._baseLayerPicker = undefined
  561. this._layerCache = {}
  562. this._dcContainer.parentNode.removeChild(this._dcContainer)
  563. this._dcContainer = undefined
  564. return this
  565. }
  566. /**
  567. * Export scene to image
  568. * @param name
  569. * @returns {Viewer}
  570. */
  571. exportScene(name) {
  572. this.scene.render()
  573. let canvas = this.canvas
  574. let image = canvas
  575. .toDataURL('image/png')
  576. .replace('image/png', 'image/octet-stream')
  577. let link = document.createElement('a')
  578. let blob = Util.dataURLtoBlob(image)
  579. let objUrl = URL.createObjectURL(blob)
  580. link.download = `${name || 'scene'}.png`
  581. link.href = objUrl
  582. link.click()
  583. return this
  584. }
  585. /**
  586. * Adds a plugin
  587. * @param plugin
  588. * @returns {Viewer}
  589. */
  590. use(plugin) {
  591. if (plugin && plugin.install) {
  592. plugin.install(this)
  593. }
  594. return this
  595. }
  596. }
  597. export default Viewer