CesiumMap_BACKUP_843.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. <template>
  2. <div id="cesiumContainer" class="cesium-ignore-autofit" style="width: 100%; height: 100%; position: relative; z-index: 1; transform: none !important; zoom: 1 !important;"></div>
  3. <!-- POI详情弹窗 -->
  4. <div v-if="showPopup" class="poi-popup" :style="popupStyle">
  5. <div class="popup-header">
  6. <span class="popup-title">{{ poiData.name }} <a href="#" class="popup-detail" @click.prevent="goToTwinStation">详情</a></span>
  7. <button class="popup-close" @click="closePopup">×</button>
  8. </div>
  9. <div class="popup-content">
  10. <div class="popup-section">
  11. <div class="popup-label">实时数据</div>
  12. <div class="popup-value">水位: {{ poiData.waterLevel }}m</div>
  13. <div class="popup-value">流量: {{ poiData.flow }}m³/s</div>
  14. <div class="popup-value">降雨量: {{ poiData.rainfall }}mm/h</div>
  15. </div>
  16. <div class="popup-section">
  17. <div class="popup-label">历史同期值</div>
  18. <div class="popup-value">水位: {{ poiData.historyWaterLevel }}m</div>
  19. <div class="popup-value">流量: {{ poiData.historyFlow }}m³/s</div>
  20. </div>
  21. </div>
  22. </div>
  23. </template>
  24. <script>
  25. import * as Cesium from 'cesium'
  26. import 'cesium/Build/CesiumUnminified/Widgets/widgets.css'
  27. <<<<<<< HEAD
  28. export default {
  29. name: 'CesiumMap',
  30. props: {
  31. simulationTime: {
  32. type: Number,
  33. default: 0
  34. },
  35. simulationData: {
  36. type: Object,
  37. default: () => ({})
  38. }
  39. },
  40. data() {
  41. return {
  42. viewer: null,
  43. showPopup: false,
  44. popupPosition: { x: 0, y: 0 },
  45. poiData: {
  46. name: '黑林水文站',
  47. waterLevel: '3.25',
  48. flow: '12.5',
  49. rainfall: '0.5',
  50. historyWaterLevel: '3.10',
  51. historyFlow: '11.8',
  52. device: 'HL-Sensor-001'
  53. =======
  54. const emit = defineEmits(['toggleMap'])
  55. const viewer = ref(null)
  56. const showPopup = ref(false)
  57. const popupPosition = ref({ x: 0, y: 0 })
  58. const router = useRouter()
  59. const DESIGN_WIDTH = 1920
  60. const DESIGN_HEIGHT = 1080
  61. const getScaleRatio = () => {
  62. return Math.min(window.innerWidth / DESIGN_WIDTH, window.innerHeight / DESIGN_HEIGHT)
  63. }
  64. const poiData = ref({
  65. name: '黑林水文站',
  66. waterLevel: '3.25',
  67. flow: '12.5',
  68. rainfall: '0.5',
  69. historyWaterLevel: '3.10',
  70. historyFlow: '11.8',
  71. device: 'HL-Sensor-001'
  72. })
  73. const popupStyle = computed(() => {
  74. return {
  75. left: popupPosition.value.x + 'px',
  76. top: popupPosition.value.y + 'px'
  77. }
  78. })
  79. let blinkInterval = null
  80. const addPoiMarker = () => {
  81. // 添加黑林水文站POI
  82. const heilinPosition = Cesium.Cartesian3.fromDegrees(118.8770798, 35.0320627)
  83. // 创建一个不可见的点实体,用于扩大点击区域
  84. const clickEntity = viewer.value.entities.add({
  85. id: 'heilin-station-click',
  86. position: heilinPosition,
  87. point: {
  88. pixelSize: 30,
  89. color: Cesium.Color.TRANSPARENT,
  90. outlineColor: Cesium.Color.TRANSPARENT,
  91. outlineWidth: 0
  92. }
  93. })
  94. const entity = viewer.value.entities.add({
  95. id: 'heilin-station',
  96. position: heilinPosition,
  97. billboard: {
  98. image: '/src/assets/images/水文站.png',
  99. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  100. horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
  101. pixelOffset: new Cesium.Cartesian2(0, -15),
  102. scale: 0.8,
  103. width: 30,
  104. height: 37.5,
  105. disableDepthTestDistance: Number.POSITIVE_INFINITY
  106. >>>>>>> origin/master
  107. },
  108. blinkInterval: null,
  109. heilinEntity: null,
  110. reservoirEntity: null
  111. }
  112. },
  113. computed: {
  114. popupStyle() {
  115. return {
  116. left: this.popupPosition.x + 'px',
  117. top: this.popupPosition.y + 'px'
  118. }
  119. }
  120. },
  121. watch: {
  122. simulationData: {
  123. handler(newData) {
  124. this.$nextTick(() => {
  125. this.updateMapMarkers(newData)
  126. })
  127. },
  128. deep: true,
  129. immediate: true
  130. }
  131. },
  132. methods: {
  133. addPoiMarker() {
  134. const heilinPosition = Cesium.Cartesian3.fromDegrees(118.8770798, 35.0320627)
  135. const clickEntity = this.viewer.entities.add({
  136. id: 'heilin-station-click',
  137. position: heilinPosition,
  138. point: {
  139. pixelSize: 30,
  140. color: Cesium.Color.TRANSPARENT,
  141. outlineColor: Cesium.Color.TRANSPARENT,
  142. outlineWidth: 0
  143. }
  144. })
  145. this.heilinEntity = this.viewer.entities.add({
  146. id: 'heilin-station',
  147. position: heilinPosition,
  148. billboard: {
  149. image: '/src/assets/images/水文站.png',
  150. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  151. horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
  152. pixelOffset: new Cesium.Cartesian2(0, -15),
  153. scale: 0.8,
  154. width: 30,
  155. height: 37.5,
  156. disableDepthTestDistance: Number.POSITIVE_INFINITY
  157. },
  158. label: {
  159. text: `水位 ${this.simulationData.heilinStation?.waterLevel || '3.25'}m\n流量 ${this.simulationData.heilinStation?.flow || '12.5'}m³/s\n降雨量 ${this.simulationData.heilinStation?.rainfall || '0.5'}mm/h`,
  160. font: 'bold 14px 黑体',
  161. fillColor: Cesium.Color.WHITE,
  162. outlineColor: Cesium.Color.fromCssColorString('#003860'),
  163. outlineWidth: 2,
  164. style: Cesium.LabelStyle.FILL_AND_OUTLINE,
  165. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  166. horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
  167. pixelOffset: new Cesium.Cartesian2(0, -30),
  168. showBackground: true,
  169. backgroundColor: new Cesium.Color(0, 0.3, 0.5, 0.7),
  170. backgroundPadding: new Cesium.Cartesian2(8, 4),
  171. disableDepthTestDistance: Number.POSITIVE_INFINITY
  172. }
  173. })
  174. this.viewer.entities.add({
  175. id: 'heilin-station-title',
  176. position: heilinPosition,
  177. label: {
  178. text: '黑林水文站',
  179. font: 'bold 14px sans-serif',
  180. fillColor: Cesium.Color.WHITE,
  181. outlineColor: Cesium.Color.fromCssColorString('#003860'),
  182. outlineWidth: 2,
  183. style: Cesium.LabelStyle.FILL_AND_OUTLINE,
  184. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  185. horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
  186. pixelOffset: new Cesium.Cartesian2(0, -85),
  187. showBackground: true,
  188. backgroundColor: new Cesium.Color(0, 0.2, 0.4, 0.9),
  189. backgroundPadding: new Cesium.Cartesian2(22, 4),
  190. disableDepthTestDistance: Number.POSITIVE_INFINITY
  191. }
  192. })
  193. <<<<<<< HEAD
  194. const reservoirPosition = Cesium.Cartesian3.fromDegrees(118.9540555, 34.9355329)
  195. this.reservoirEntity = this.viewer.entities.add({
  196. id: 'tashan-reservoir',
  197. position: reservoirPosition,
  198. billboard: {
  199. image: '/src/assets/images/水库蓝.png',
  200. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  201. horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
  202. pixelOffset: new Cesium.Cartesian2(0, -15),
  203. scale: 0.8,
  204. width: 30,
  205. height: 30,
  206. disableDepthTestDistance: Number.POSITIVE_INFINITY
  207. },
  208. label: {
  209. text: `库水位 ${this.simulationData.reservoir?.waterLevel || '18.5'}m\n库容 ${this.simulationData.reservoir?.storage || '2350.8'}万m³\n汛限 20.0m`,
  210. font: 'bold 14px 黑体',
  211. fillColor: Cesium.Color.WHITE,
  212. outlineColor: Cesium.Color.fromCssColorString('#003860'),
  213. outlineWidth: 2,
  214. style: Cesium.LabelStyle.FILL_AND_OUTLINE,
  215. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  216. horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
  217. pixelOffset: new Cesium.Cartesian2(0, -30),
  218. showBackground: true,
  219. backgroundColor: new Cesium.Color(0, 0.3, 0.5, 0.7),
  220. backgroundPadding: new Cesium.Cartesian2(8, 4),
  221. disableDepthTestDistance: Number.POSITIVE_INFINITY
  222. }
  223. })
  224. this.viewer.entities.add({
  225. id: 'tashan-reservoir-title',
  226. position: reservoirPosition,
  227. label: {
  228. text: '小塔山水库',
  229. font: 'bold 14px sans-serif',
  230. fillColor: Cesium.Color.WHITE,
  231. outlineColor: Cesium.Color.fromCssColorString('#003860'),
  232. outlineWidth: 2,
  233. style: Cesium.LabelStyle.FILL_AND_OUTLINE,
  234. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  235. horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
  236. pixelOffset: new Cesium.Cartesian2(0, -85),
  237. showBackground: true,
  238. backgroundColor: new Cesium.Color(0, 0.2, 0.4, 0.9),
  239. backgroundPadding: new Cesium.Cartesian2(22, 4),
  240. disableDepthTestDistance: Number.POSITIVE_INFINITY
  241. }
  242. })
  243. const handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
  244. handler.setInputAction((movement) => {
  245. const pickedObjects = this.viewer.scene.drillPick(movement.position)
  246. for (let i = 0; i < pickedObjects.length; i++) {
  247. const pickedObj = pickedObjects[i]
  248. if (Cesium.defined(pickedObj) && pickedObj.id) {
  249. if (pickedObj.id.id === 'heilin-station' || pickedObj.id.id === 'heilin-station-click') {
  250. this.showPopup = true
  251. this.popupPosition = { x: movement.position.x - 120, y: movement.position.y - 150 }
  252. break
  253. }
  254. =======
  255. const handler = new Cesium.ScreenSpaceEventHandler(viewer.value.scene.canvas)
  256. handler.setInputAction((movement) => {
  257. const scaleRatio = getScaleRatio()
  258. const correctedX = movement.position.x / scaleRatio
  259. const correctedY = movement.position.y / scaleRatio
  260. const pickedObjects = viewer.value.scene.drillPick(new Cesium.Cartesian2(correctedX, correctedY))
  261. for (let i = 0; i < pickedObjects.length; i++) {
  262. const pickedObj = pickedObjects[i]
  263. if (Cesium.defined(pickedObj) && pickedObj.id) {
  264. if (pickedObj.id.id === 'heilin-station' || pickedObj.id.id === 'heilin-station-click') {
  265. showPopup.value = true
  266. popupPosition.value = {
  267. x: movement.position.x - 120,
  268. y: movement.position.y - 150
  269. }
  270. break
  271. >>>>>>> origin/master
  272. }
  273. }
  274. }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
  275. },
  276. updateMapMarkers(data) {
  277. if (!data) return
  278. if (this.heilinEntity && data.heilinStation) {
  279. const waterLevel = data.heilinStation.waterLevel || '3.25'
  280. const flow = data.heilinStation.flow || '12.5'
  281. const rainfall = data.heilinStation.rainfall || '0.5'
  282. this.heilinEntity.label.text = `水位 ${waterLevel}m\n流量 ${flow}m³/s\n降雨量 ${rainfall}mm/h`
  283. this.poiData.waterLevel = waterLevel
  284. this.poiData.flow = flow
  285. this.poiData.rainfall = rainfall
  286. }
  287. if (this.reservoirEntity && data.reservoir) {
  288. const waterLevel = data.reservoir.waterLevel || '18.5'
  289. const storage = data.reservoir.storage || '2350.8'
  290. this.reservoirEntity.label.text = `库水位 ${waterLevel}m\n库容 ${storage}万m³\n汛限 20.0m`
  291. }
  292. },
  293. closePopup() {
  294. this.showPopup = false
  295. },
  296. goToTwinStation() {
  297. const heilinPosition = Cesium.Cartesian3.fromDegrees(118.8770798, 35.0320627, 10000)
  298. this.viewer.camera.flyTo({
  299. destination: heilinPosition,
  300. duration: 2.0,
  301. easingFunction: Cesium.EasingFunction.SINE_IN_OUT
  302. })
  303. this.closePopup()
  304. }
  305. },
  306. async mounted() {
  307. this.viewer = new Cesium.Viewer('cesiumContainer', {
  308. terrainProvider: await Cesium.createWorldTerrainAsync(),
  309. animation: false,
  310. baseLayerPicker: false,
  311. fullscreenButton: false,
  312. geocoder: false,
  313. homeButton: false,
  314. infoBox: false,
  315. sceneModePicker: false,
  316. selectionIndicator: false,
  317. timeline: false,
  318. navigationHelpButton: false,
  319. navigationInstructionsInitiallyVisible: false,
  320. shouldAnimate: false,
  321. showRenderLoopErrors: false
  322. })
  323. if (this.viewer.cesiumWidget.creditContainer) {
  324. this.viewer.cesiumWidget.creditContainer.style.display = 'none'
  325. }
  326. const toolbar = this.viewer._element.querySelector('.cesium-viewer-toolbar')
  327. if (toolbar) toolbar.style.display = 'none'
  328. const animationContainer = this.viewer._element.querySelector('.cesium-viewer-animationContainer')
  329. if (animationContainer) animationContainer.style.display = 'none'
  330. const timelineContainer = this.viewer._element.querySelector('.cesium-viewer-timelineContainer')
  331. if (timelineContainer) timelineContainer.style.display = 'none'
  332. try {
  333. const response = await fetch('/Heilin.geojson')
  334. const geojson = await response.json()
  335. const dataSource = await Cesium.GeoJsonDataSource.load(geojson, {
  336. stroke: Cesium.Color.fromCssColorString('#00d5ff'),
  337. strokeWidth: 3,
  338. fill: Cesium.Color.fromCssColorString('#00d5ff').withAlpha(0.1)
  339. })
  340. this.viewer.dataSources.add(dataSource)
  341. try {
  342. const waterResponse = await fetch('/黑林水系.geojson')
  343. const waterGeojson = await waterResponse.json()
  344. const waterDataSource = await Cesium.GeoJsonDataSource.load(waterGeojson, {
  345. stroke: Cesium.Color.fromCssColorString('#62f6fb'),
  346. strokeWidth: 2,
  347. fill: Cesium.Color.fromCssColorString('#62f6fb').withAlpha(0.2)
  348. })
  349. this.viewer.dataSources.add(waterDataSource)
  350. } catch (waterError) {
  351. console.error('加载水系GeoJSON失败:', waterError)
  352. }
  353. try {
  354. const reservoirResponse = await fetch('/小塔山水库.geojson')
  355. const reservoirGeojson = await reservoirResponse.json()
  356. const reservoirDataSource = await Cesium.GeoJsonDataSource.load(reservoirGeojson, {
  357. stroke: Cesium.Color.fromCssColorString('#00d4ff'),
  358. strokeWidth: 2,
  359. fill: Cesium.Color.fromCssColorString('#00d4ff').withAlpha(0.3)
  360. })
  361. this.viewer.dataSources.add(reservoirDataSource)
  362. } catch (reservoirError) {
  363. console.error('加载小塔山水库GeoJSON失败:', reservoirError)
  364. }
  365. const entities = dataSource.entities.values
  366. if (entities.length > 0) {
  367. let minLat = 90, maxLat = -90, minLon = 180, maxLon = -180
  368. for (const entity of entities) {
  369. if (entity.polygon && entity.polygon.hierarchy) {
  370. const hierarchy = entity.polygon.hierarchy.getValue()
  371. const positions = hierarchy.positions
  372. for (const pos of positions) {
  373. const cartographic = Cesium.Cartographic.fromCartesian(pos)
  374. const lat = Cesium.Math.toDegrees(cartographic.latitude)
  375. const lon = Cesium.Math.toDegrees(cartographic.longitude)
  376. minLat = Math.min(minLat, lat)
  377. maxLat = Math.max(maxLat, lat)
  378. minLon = Math.min(minLon, lon)
  379. maxLon = Math.max(maxLon, lon)
  380. }
  381. }
  382. }
  383. this.viewer.camera.flyTo({
  384. destination: Cesium.Cartesian3.fromDegrees(118.9019, 34.985, 33000),
  385. duration: 0
  386. })
  387. }
  388. } catch (error) {
  389. console.error('加载GeoJSON失败:', error)
  390. this.viewer.camera.flyTo({
  391. destination: Cesium.Cartesian3.fromDegrees(118.9019, 34.985, 33000),
  392. duration: 0
  393. })
  394. }
  395. <<<<<<< HEAD
  396. this.addPoiMarker()
  397. this.$nextTick(() => {
  398. this.updateMapMarkers(this.simulationData)
  399. })
  400. },
  401. beforeUnmount() {
  402. if (this.blinkInterval) {
  403. clearInterval(this.blinkInterval)
  404. }
  405. if (this.viewer) {
  406. this.viewer.destroy()
  407. }
  408. =======
  409. addPoiMarker()
  410. window.addEventListener('resize', handleResize)
  411. })
  412. const handleResize = () => {
  413. if (viewer.value) {
  414. viewer.value.resize()
  415. }
  416. }
  417. onUnmounted(() => {
  418. window.removeEventListener('resize', handleResize)
  419. if (blinkInterval) {
  420. clearInterval(blinkInterval)
  421. >>>>>>> origin/master
  422. }
  423. }
  424. </script>
  425. <style scoped>
  426. .poi-popup {
  427. position: fixed;
  428. width: 240px;
  429. background: rgba(0, 20, 40, 0.95);
  430. border: 1px solid rgba(0, 213, 255, 0.6);
  431. border-radius: 8px;
  432. box-shadow: 0 0 20px rgba(0, 213, 255, 0.4);
  433. z-index: 10;
  434. pointer-events: auto;
  435. overflow: hidden;
  436. }
  437. .popup-header {
  438. display: flex;
  439. justify-content: space-between;
  440. align-items: center;
  441. padding: 12px 15px;
  442. background: linear-gradient(90deg, rgba(0, 60, 120, 0.9), rgba(0, 100, 150, 0.7));
  443. border-bottom: 1px solid rgba(0, 213, 255, 0.4);
  444. }
  445. .popup-title {
  446. font-size: 16px;
  447. font-weight: bold;
  448. color: #00ffff;
  449. text-shadow: 0 0 5px rgba(0, 255, 255, 0.5);
  450. }
  451. .popup-detail {
  452. font-size: 12px;
  453. color: #62f6fb;
  454. text-decoration: none;
  455. margin-left: 8px;
  456. cursor: pointer;
  457. transition: color 0.3s;
  458. }
  459. .popup-detail:hover {
  460. color: #ffffff;
  461. text-decoration: underline;
  462. }
  463. .popup-close {
  464. background: none;
  465. border: none;
  466. color: #7bbef6;
  467. font-size: 20px;
  468. cursor: pointer;
  469. padding: 0;
  470. line-height: 1;
  471. transition: color 0.3s;
  472. }
  473. .popup-close:hover {
  474. color: #ffffff;
  475. }
  476. .popup-content {
  477. padding: 15px;
  478. }
  479. .popup-section {
  480. margin-bottom: 15px;
  481. }
  482. .popup-section:last-child {
  483. margin-bottom: 0;
  484. }
  485. .popup-label {
  486. font-size: 14px;
  487. font-weight: bold;
  488. color: #62f6fb;
  489. margin-bottom: 8px;
  490. text-shadow: 0 0 3px rgba(0, 212, 255, 0.5);
  491. }
  492. .popup-value {
  493. font-size: 13px;
  494. color: #e0fcff;
  495. margin-bottom: 5px;
  496. line-height: 1.5;
  497. }
  498. </style>