| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- <template>
- <div id="cesiumContainer" class="cesium-ignore-autofit" style="width: 100%; height: 100%; position: relative; z-index: 1; transform: none !important; zoom: 1 !important;"></div>
-
- <!-- POI详情弹窗 -->
- <div v-if="showPopup" class="poi-popup" :style="popupStyle">
- <div class="popup-header">
- <span class="popup-title">{{ poiData.name }} <a href="#" class="popup-detail" @click.prevent="goToTwinStation">详情</a></span>
- <button class="popup-close" @click="closePopup">×</button>
- </div>
- <div class="popup-content">
- <div class="popup-section">
- <div class="popup-label">实时数据</div>
- <div class="popup-value">水位: {{ poiData.waterLevel }}m</div>
- <div class="popup-value">流量: {{ poiData.flow }}m³/s</div>
- <div class="popup-value">降雨量: {{ poiData.rainfall }}mm/h</div>
- </div>
- <div class="popup-section">
- <div class="popup-label">历史同期值</div>
- <div class="popup-value">水位: {{ poiData.historyWaterLevel }}m</div>
- <div class="popup-value">流量: {{ poiData.historyFlow }}m³/s</div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, onMounted, onUnmounted, computed } from 'vue'
- import * as Cesium from 'cesium'
- import 'cesium/Build/CesiumUnminified/Widgets/widgets.css'
- import { useRouter } from 'vue-router'
- const emit = defineEmits(['toggleMap'])
- const viewer = ref(null)
- const showPopup = ref(false)
- const popupPosition = ref({ x: 0, y: 0 })
- const router = useRouter()
- const DESIGN_WIDTH = 1920
- const DESIGN_HEIGHT = 1080
- const getScaleRatio = () => {
- return Math.min(window.innerWidth / DESIGN_WIDTH, window.innerHeight / DESIGN_HEIGHT)
- }
- const poiData = ref({
- name: '黑林水文站',
- waterLevel: '3.25',
- flow: '12.5',
- rainfall: '0.5',
- historyWaterLevel: '3.10',
- historyFlow: '11.8',
- device: 'HL-Sensor-001'
- })
- const popupStyle = computed(() => {
- return {
- left: popupPosition.value.x + 'px',
- top: popupPosition.value.y + 'px'
- }
- })
- let blinkInterval = null
- const addPoiMarker = () => {
- // 添加黑林水文站POI
- const heilinPosition = Cesium.Cartesian3.fromDegrees(118.8770798, 35.0320627)
-
- // 创建一个不可见的点实体,用于扩大点击区域
- const clickEntity = viewer.value.entities.add({
- id: 'heilin-station-click',
- position: heilinPosition,
- point: {
- pixelSize: 30,
- color: Cesium.Color.TRANSPARENT,
- outlineColor: Cesium.Color.TRANSPARENT,
- outlineWidth: 0
- }
- })
-
- const entity = viewer.value.entities.add({
- id: 'heilin-station',
- position: heilinPosition,
- billboard: {
- image: '/src/assets/images/水文站.png',
- verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
- horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
- pixelOffset: new Cesium.Cartesian2(0, -15),
- scale: 0.8,
- width: 30,
- height: 37.5,
- disableDepthTestDistance: Number.POSITIVE_INFINITY
- },
- label: {
- text: '水位 3.25m\n流量 12.5m³/s\n降雨量 0.5mm/h',
- font: 'bold 14px 黑体',
- fillColor: Cesium.Color.WHITE,
- outlineColor: Cesium.Color.fromCssColorString('#003860'),
- outlineWidth: 2,
- style: Cesium.LabelStyle.FILL_AND_OUTLINE,
- verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
- horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
- pixelOffset: new Cesium.Cartesian2(0, -30),
- showBackground: true,
- backgroundColor: new Cesium.Color(0, 0.3, 0.5, 0.7),
- backgroundPadding: new Cesium.Cartesian2(8, 4),
- disableDepthTestDistance: Number.POSITIVE_INFINITY
- }
- })
-
- // 添加标题标签
- viewer.value.entities.add({
- id: 'heilin-station-title',
- position: heilinPosition,
- label: {
- text: '黑林水文站',
- font: 'bold 14px sans-serif',
- fillColor: Cesium.Color.WHITE,
- outlineColor: Cesium.Color.fromCssColorString('#003860'),
- outlineWidth: 2,
- style: Cesium.LabelStyle.FILL_AND_OUTLINE,
- verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
- horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
- pixelOffset: new Cesium.Cartesian2(0, -85),
- showBackground: true,
- backgroundColor: new Cesium.Color(0, 0.2, 0.4, 0.9),
- backgroundPadding: new Cesium.Cartesian2(22, 4),
- disableDepthTestDistance: Number.POSITIVE_INFINITY
- }
- })
- // 添加塔山小水库POI
- const reservoirPosition = Cesium.Cartesian3.fromDegrees(118.9540555, 34.9355329)
-
- const reservoirEntity = viewer.value.entities.add({
- id: 'tashan-reservoir',
- position: reservoirPosition,
- billboard: {
- image: '/src/assets/images/水库蓝.png',
- verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
- horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
- pixelOffset: new Cesium.Cartesian2(0, -15),
- scale: 0.8,
- width: 30,
- height: 30,
- disableDepthTestDistance: Number.POSITIVE_INFINITY
- },
- label: {
- text: '库水位 18.5m\n库容 2350.8万m³\n汛限 20.0m',
- font: 'bold 14px 黑体',
- fillColor: Cesium.Color.WHITE,
- outlineColor: Cesium.Color.fromCssColorString('#003860'),
- outlineWidth: 2,
- style: Cesium.LabelStyle.FILL_AND_OUTLINE,
- verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
- horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
- pixelOffset: new Cesium.Cartesian2(0, -30),
- showBackground: true,
- backgroundColor: new Cesium.Color(0, 0.3, 0.5, 0.7),
- backgroundPadding: new Cesium.Cartesian2(8, 4),
- disableDepthTestDistance: Number.POSITIVE_INFINITY
- }
- })
-
- // 添加标题标签
- viewer.value.entities.add({
- id: 'tashan-reservoir-title',
- position: reservoirPosition,
- label: {
- text: '小塔山水库',
- font: 'bold 14px sans-serif',
- fillColor: Cesium.Color.WHITE,
- outlineColor: Cesium.Color.fromCssColorString('#003860'),
- outlineWidth: 2,
- style: Cesium.LabelStyle.FILL_AND_OUTLINE,
- verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
- horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
- pixelOffset: new Cesium.Cartesian2(0, -85),
- showBackground: true,
- backgroundColor: new Cesium.Color(0, 0.2, 0.4, 0.9),
- backgroundPadding: new Cesium.Cartesian2(22, 4),
- disableDepthTestDistance: Number.POSITIVE_INFINITY
- }
- })
- const handler = new Cesium.ScreenSpaceEventHandler(viewer.value.scene.canvas)
-
- handler.setInputAction((movement) => {
- const scaleRatio = getScaleRatio()
- const correctedX = movement.position.x / scaleRatio
- const correctedY = movement.position.y / scaleRatio
-
- const pickedObjects = viewer.value.scene.drillPick(new Cesium.Cartesian2(correctedX, correctedY))
- for (let i = 0; i < pickedObjects.length; i++) {
- const pickedObj = pickedObjects[i]
- if (Cesium.defined(pickedObj) && pickedObj.id) {
- if (pickedObj.id.id === 'heilin-station' || pickedObj.id.id === 'heilin-station-click') {
- showPopup.value = true
- popupPosition.value = {
- x: movement.position.x - 120,
- y: movement.position.y - 150
- }
- break
- }
- }
- }
- }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
- // 移除闪烁效果
- entity.billboard.scale = 0.8
- reservoirEntity.billboard.scale = 0.8
- }
- const createPoiImage = () => {
- const canvas = document.createElement('canvas')
- canvas.width = 40
- canvas.height = 50
- const ctx = canvas.getContext('2d')
-
- ctx.beginPath()
- ctx.moveTo(20, 0)
- ctx.lineTo(40, 30)
- ctx.lineTo(20, 50)
- ctx.lineTo(0, 30)
- ctx.closePath()
-
- const gradient = ctx.createRadialGradient(20, 25, 5, 20, 25, 25)
- gradient.addColorStop(0, '#00ffff')
- gradient.addColorStop(1, '#0066ff')
- ctx.fillStyle = gradient
- ctx.fill()
-
- ctx.strokeStyle = '#ffffff'
- ctx.lineWidth = 2
- ctx.stroke()
-
- return canvas
- }
- const closePopup = () => {
- showPopup.value = false
- }
- const goToTwinStation = () => {
- // 执行平滑flyto动画到黑林水文站位置
- const heilinPosition = Cesium.Cartesian3.fromDegrees(118.8770798, 35.0320627, 10000)
- viewer.value.camera.flyTo({
- destination: heilinPosition,
- duration: 2.0,
- easingFunction: Cesium.EasingFunction.SINE_IN_OUT
- })
-
- // 延迟跳转到水文站详情页面
- setTimeout(() => {
- router.push('/heilin-station')
- }, 2000)
-
- closePopup()
- }
- onMounted(async () => {
- viewer.value = new Cesium.Viewer('cesiumContainer', {
- terrainProvider: await Cesium.createWorldTerrainAsync(),
- animation: false,
- baseLayerPicker: false,
- fullscreenButton: false,
- geocoder: false,
- homeButton: false,
- infoBox: false,
- sceneModePicker: false,
- selectionIndicator: false,
- timeline: false,
- navigationHelpButton: false,
- navigationInstructionsInitiallyVisible: false,
- shouldAnimate: false,
- showRenderLoopErrors: false
- })
-
- if (viewer.value.cesiumWidget.creditContainer) {
- viewer.value.cesiumWidget.creditContainer.style.display = 'none'
- }
-
- const toolbar = viewer.value._element.querySelector('.cesium-viewer-toolbar')
- if (toolbar) toolbar.style.display = 'none'
-
- const animationContainer = viewer.value._element.querySelector('.cesium-viewer-animationContainer')
- if (animationContainer) animationContainer.style.display = 'none'
-
- const timelineContainer = viewer.value._element.querySelector('.cesium-viewer-timelineContainer')
- if (timelineContainer) timelineContainer.style.display = 'none'
- try {
- // 加载流域边界GeoJSON
- const response = await fetch('/Heilin.geojson')
- const geojson = await response.json()
-
- const dataSource = await Cesium.GeoJsonDataSource.load(geojson, {
- stroke: Cesium.Color.fromCssColorString('#00d5ff'),
- strokeWidth: 3,
- fill: Cesium.Color.fromCssColorString('#00d5ff').withAlpha(0.1)
- })
-
- viewer.value.dataSources.add(dataSource)
-
- // 加载水系GeoJSON
- try {
- const waterResponse = await fetch('/黑林水系.geojson')
- const waterGeojson = await waterResponse.json()
-
- const waterDataSource = await Cesium.GeoJsonDataSource.load(waterGeojson, {
- stroke: Cesium.Color.fromCssColorString('#62f6fb'),
- strokeWidth: 2,
- fill: Cesium.Color.fromCssColorString('#62f6fb').withAlpha(0.2)
- })
-
- viewer.value.dataSources.add(waterDataSource)
- } catch (waterError) {
- console.error('加载水系GeoJSON失败:', waterError)
- }
-
- // 加载小塔山水库GeoJSON
- try {
- const reservoirResponse = await fetch('/小塔山水库.geojson')
- const reservoirGeojson = await reservoirResponse.json()
-
- const reservoirDataSource = await Cesium.GeoJsonDataSource.load(reservoirGeojson, {
- stroke: Cesium.Color.fromCssColorString('#00d4ff'),
- strokeWidth: 2,
- fill: Cesium.Color.fromCssColorString('#00d4ff').withAlpha(0.3)
- })
-
- viewer.value.dataSources.add(reservoirDataSource)
- } catch (reservoirError) {
- console.error('加载小塔山水库GeoJSON失败:', reservoirError)
- }
-
- const entities = dataSource.entities.values
- if (entities.length > 0) {
- let minLat = 90, maxLat = -90, minLon = 180, maxLon = -180
-
- for (const entity of entities) {
- if (entity.polygon && entity.polygon.hierarchy) {
- const hierarchy = entity.polygon.hierarchy.getValue()
- const positions = hierarchy.positions
-
- for (const pos of positions) {
- const cartographic = Cesium.Cartographic.fromCartesian(pos)
- const lat = Cesium.Math.toDegrees(cartographic.latitude)
- const lon = Cesium.Math.toDegrees(cartographic.longitude)
-
- minLat = Math.min(minLat, lat)
- maxLat = Math.max(maxLat, lat)
- minLon = Math.min(minLon, lon)
- maxLon = Math.max(maxLon, lon)
- }
- }
- }
-
- viewer.value.camera.flyTo({
- destination: Cesium.Cartesian3.fromDegrees(118.9019, 34.985, 33000),
- duration: 0
- })
- }
- } catch (error) {
- console.error('加载GeoJSON失败:', error)
- viewer.value.camera.flyTo({
- destination: Cesium.Cartesian3.fromDegrees(118.9019, 34.985, 33000),
- duration: 0
- })
- }
- addPoiMarker()
-
- window.addEventListener('resize', handleResize)
- })
- const handleResize = () => {
- if (viewer.value) {
- viewer.value.resize()
- }
- }
- onUnmounted(() => {
- window.removeEventListener('resize', handleResize)
- if (blinkInterval) {
- clearInterval(blinkInterval)
- }
- if (viewer.value) {
- viewer.value.destroy()
- }
- })
- </script>
- <style scoped>
- .poi-popup {
- position: fixed;
- width: 240px;
- background: rgba(0, 20, 40, 0.95);
- border: 1px solid rgba(0, 213, 255, 0.6);
- border-radius: 8px;
- box-shadow: 0 0 20px rgba(0, 213, 255, 0.4);
- z-index: 10;
- pointer-events: auto;
- overflow: hidden;
- }
- .popup-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 12px 15px;
- background: linear-gradient(90deg, rgba(0, 60, 120, 0.9), rgba(0, 100, 150, 0.7));
- border-bottom: 1px solid rgba(0, 213, 255, 0.4);
- }
- .popup-title {
- font-size: 16px;
- font-weight: bold;
- color: #00ffff;
- text-shadow: 0 0 5px rgba(0, 255, 255, 0.5);
- }
- .popup-detail {
- font-size: 12px;
- color: #62f6fb;
- text-decoration: none;
- margin-left: 8px;
- cursor: pointer;
- transition: color 0.3s;
- }
- .popup-detail:hover {
- color: #ffffff;
- text-decoration: underline;
- }
- .popup-close {
- background: none;
- border: none;
- color: #7bbef6;
- font-size: 20px;
- cursor: pointer;
- padding: 0;
- line-height: 1;
- transition: color 0.3s;
- }
- .popup-close:hover {
- color: #ffffff;
- }
- .popup-content {
- padding: 15px;
- }
- .popup-section {
- margin-bottom: 15px;
- }
- .popup-section:last-child {
- margin-bottom: 0;
- }
- .popup-label {
- font-size: 14px;
- font-weight: bold;
- color: #62f6fb;
- margin-bottom: 8px;
- text-shadow: 0 0 3px rgba(0, 212, 255, 0.5);
- }
- .popup-value {
- font-size: 13px;
- color: #e0fcff;
- margin-bottom: 5px;
- line-height: 1.5;
- }
- </style>
|