|
|
@@ -12,19 +12,32 @@
|
|
|
<button class="control-btn" @click="handleToggleTyphoon" title="显示/隐藏台风并切换视角">
|
|
|
<i class="fas fa-wind" :class="{ 'active': typhoonVisible }"></i>
|
|
|
</button>
|
|
|
+ <button class="control-btn" @click="toggleWmsLayer" title="显示/隐藏WMS风暴潮图层">
|
|
|
+ <i class="fas fa-layer-group" :class="{ 'active': wmsVisible }"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 时间轴组件:强制渲染 + 最高层级 -->
|
|
|
+ <div class="timeline-wrapper">
|
|
|
+ <TimeSlider
|
|
|
+ title="风暴潮WMS时间轴"
|
|
|
+ :start-time="startTime.getTime()"
|
|
|
+ :end-time="endTime.getTime()"
|
|
|
+ :current-time="currentTime.getTime()"
|
|
|
+ :step="timeStep"
|
|
|
+ :play-speed="2000"
|
|
|
+ @timeChange="handleTimeChange"
|
|
|
+ />
|
|
|
</div>
|
|
|
|
|
|
- <!-- 只有当viewer初始化完成后才渲染子组件 -->
|
|
|
+ <!-- POI/台风组件:保留viewer判断 -->
|
|
|
<template v-if="viewer">
|
|
|
- <!-- 引入POI可视化组件 -->
|
|
|
<POIVisualization
|
|
|
:viewer="viewer"
|
|
|
:visible="poiVisible"
|
|
|
:data="poiData"
|
|
|
@onPointSelected="handlePointSelected"
|
|
|
/>
|
|
|
-
|
|
|
- <!-- 动态控制台风组件的创建和销毁,而不仅仅是visible属性 -->
|
|
|
<TyphoonVisualization
|
|
|
v-if="typhoonVisible"
|
|
|
:viewer="viewer"
|
|
|
@@ -35,27 +48,34 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, onMounted, onUnmounted, nextTick} from 'vue'
|
|
|
+import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
|
|
import * as Cesium from 'cesium';
|
|
|
import "cesium/Build/CesiumUnminified/Widgets/widgets.css";
|
|
|
import JYLData from '@/assets/Data/THJYL.json'
|
|
|
import POIVisualization from './POIVisualization.vue';
|
|
|
import TyphoonVisualization from './TyphoonVisualization.vue';
|
|
|
+import TimeSlider from './TimeSlider.vue';
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-// 变量定义
|
|
|
+// 天地图密钥
|
|
|
const TDTTK = "d9e7aa2ad204aba6aeedea6f5ab48ed9";
|
|
|
let viewer = ref(null);
|
|
|
|
|
|
-// 状态管理
|
|
|
+// WMS核心配置
|
|
|
+let wmsLayer = null;
|
|
|
+const wmsVisible = ref(true);
|
|
|
+// 时间范围:2025102700 ~ 2025110822
|
|
|
+const startTime = ref(new Date('2025-10-27T00:00:00'));
|
|
|
+const endTime = ref(new Date('2025-11-08T22:00:00'));
|
|
|
+const currentTime = ref(new Date('2025-10-27T00:00:00'));
|
|
|
+const timeStep = ref(10 * 3600 * 1000); // 10小时步长
|
|
|
+
|
|
|
+// POI/台风状态
|
|
|
const poiVisible = ref(true);
|
|
|
const typhoonVisible = ref(true);
|
|
|
-const poiData = ref(JYLData); // POI点数据
|
|
|
+const poiData = ref(JYLData);
|
|
|
|
|
|
-// 处理POI点选中事件
|
|
|
+// 处理POI选中
|
|
|
const handlePointSelected = (pointData) => {
|
|
|
- // 可以在这里处理POI点选中后的逻辑
|
|
|
console.log('选中的POI点:', pointData);
|
|
|
};
|
|
|
|
|
|
@@ -63,32 +83,28 @@ const handlePointSelected = (pointData) => {
|
|
|
const goToHomeView = () => {
|
|
|
if (viewer.value) {
|
|
|
viewer.value.camera.flyTo({
|
|
|
- destination: Cesium.Cartesian3.fromDegrees(120.169103, 31.226174, 500000),
|
|
|
+ destination: Cesium.Cartesian3.fromDegrees(121.75, 31.15, 30000),
|
|
|
orientation: {
|
|
|
heading: Cesium.Math.toRadians(0),
|
|
|
- pitch: Cesium.Math.toRadians(-90),
|
|
|
+ pitch: Cesium.Math.toRadians(-60),
|
|
|
},
|
|
|
duration: 1
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-// 切换POI点显示状态
|
|
|
+// 切换POI显示
|
|
|
const togglePOIDisplay = () => {
|
|
|
poiVisible.value = !poiVisible.value;
|
|
|
};
|
|
|
|
|
|
-// 处理台风切换事件 - 增加延迟确保清理完成
|
|
|
+// 切换台风显示
|
|
|
const handleToggleTyphoon = async () => {
|
|
|
const newState = !typhoonVisible.value;
|
|
|
-
|
|
|
- // 如果是从显示切换到隐藏,先等待一段时间再更新状态
|
|
|
if (!newState) {
|
|
|
typhoonVisible.value = newState;
|
|
|
} else {
|
|
|
- // 如果是从隐藏切换到显示,先确保状态更新后再飞行到视角
|
|
|
typhoonVisible.value = newState;
|
|
|
- // 等待组件重新创建
|
|
|
await nextTick();
|
|
|
if (viewer.value) {
|
|
|
viewer.value.camera.flyTo({
|
|
|
@@ -97,110 +113,188 @@ const handleToggleTyphoon = async () => {
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
-// 处理台风组件状态变化
|
|
|
+
|
|
|
+// 处理台风组件状态
|
|
|
const handleTyphoonToggle = (newState) => {
|
|
|
typhoonVisible.value = newState;
|
|
|
};
|
|
|
|
|
|
+// 加载/更新WMS图层(带严格类型校验)
|
|
|
+const loadWmsLayer = (time) => {
|
|
|
+ try {
|
|
|
+ if (!viewer.value || !viewer.value.imageryLayers) return;
|
|
|
+
|
|
|
+ // 移除旧图层
|
|
|
+ if (wmsLayer && wmsLayer instanceof Cesium.ImageryLayer) {
|
|
|
+ viewer.value.imageryLayers.remove(wmsLayer);
|
|
|
+ wmsLayer = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 格式化时间
|
|
|
+ const timeStr = time.getFullYear() +
|
|
|
+ String(time.getMonth() + 1).padStart(2, '0') +
|
|
|
+ String(time.getDate()).padStart(2, '0') +
|
|
|
+ String(time.getHours()).padStart(2, '0');
|
|
|
+
|
|
|
+ // 创建WMS提供者(严格校验)
|
|
|
+ const wmsProvider = new Cesium.WebMapServiceImageryProvider({
|
|
|
+ url: 'http://localhost:8080/geoserver/surge_ws/wms',
|
|
|
+ layers: `surge_ws:ZS${timeStr}`,
|
|
|
+ parameters: {
|
|
|
+ service: 'WMS',
|
|
|
+ version: '1.1.0',
|
|
|
+ request: 'GetMap',
|
|
|
+ format: 'image/png',
|
|
|
+ transparent: true,
|
|
|
+ srs: 'EPSG:4326'
|
|
|
+ },
|
|
|
+ // 核心:强制限定图层的地理范围(必须和你WMS图层的实际范围一致)
|
|
|
+ rectangle: Cesium.Rectangle.fromDegrees(121.0, 30.5, 122.5, 31.8),
|
|
|
+ // 禁用Cesium的自动瓦片拆分,按整个范围出单张图
|
|
|
+ tilingScheme: new Cesium.GeographicTilingScheme({
|
|
|
+ rectangle: Cesium.Rectangle.fromDegrees(121.0, 30.5, 122.5, 31.8)
|
|
|
+ }),
|
|
|
+ // 固定瓦片尺寸为图层范围(避免拆分)
|
|
|
+ tileWidth: 2048,
|
|
|
+ tileHeight: 2048,
|
|
|
+ // 限制缩放级别(避免放大后重复请求)
|
|
|
+ maximumLevel: 10,
|
|
|
+ minimumLevel: 0,
|
|
|
+ enablePickFeatures: false
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ // 仅当提供者有效时创建图层
|
|
|
+ if (wmsProvider) {
|
|
|
+ wmsLayer = new Cesium.ImageryLayer(wmsProvider); // 显式创建ImageryLayer
|
|
|
+ wmsLayer.alpha = 0.7;
|
|
|
+ wmsLayer.show = wmsVisible.value;
|
|
|
+ viewer.value.imageryLayers.add(wmsLayer); // 加入图层集合
|
|
|
+ console.log(`成功加载WMS图层: surge_ws:ZS${timeStr}`, wmsLayer);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载WMS图层失败:', error);
|
|
|
+ wmsLayer = null;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 切换WMS显示/隐藏
|
|
|
+const toggleWmsLayer = () => {
|
|
|
+ console.log('点击WMS按钮,当前图层状态:', wmsLayer);
|
|
|
+
|
|
|
+ if (!wmsLayer) {
|
|
|
+ console.log('WMS图层未初始化,加载默认时间图层');
|
|
|
+ loadWmsLayer(currentTime.value);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ wmsVisible.value = !wmsVisible.value;
|
|
|
+ wmsLayer.show = wmsVisible.value;
|
|
|
+ console.log('WMS图层显示状态:', wmsVisible.value);
|
|
|
+};
|
|
|
+
|
|
|
+// 时间轴时间变化响应
|
|
|
+const handleTimeChange = (timeStamp) => {
|
|
|
+ const newTime = new Date(timeStamp);
|
|
|
+ currentTime.value = newTime;
|
|
|
+ loadWmsLayer(newTime);
|
|
|
+};
|
|
|
+
|
|
|
onMounted(async () => {
|
|
|
- // 初始化Cesium viewer
|
|
|
- viewer.value = new Cesium.Viewer('cesiumContainer', {
|
|
|
- timeline: false,
|
|
|
- baseLayer: false,
|
|
|
- geocoder: false,
|
|
|
- homeButton: false,
|
|
|
- sceneModePicker: false,
|
|
|
- navigationHelpButton: false,
|
|
|
- animation: false,
|
|
|
- fullscreenButton: false,
|
|
|
- vrButton: false,
|
|
|
- selectionIndicator: false,
|
|
|
- infoBox: false,
|
|
|
- // 加载地形效果
|
|
|
- terrainProvider: await Cesium.createWorldTerrainAsync({
|
|
|
- requestVertexNormals: true,
|
|
|
- })
|
|
|
- });
|
|
|
-
|
|
|
- // 多块倾斜摄影瓦片集的URL列表
|
|
|
- const tilesetUrls = [
|
|
|
- "http://localhost:9003/model/TSQ1234/tileset.json",
|
|
|
- "http://localhost:9003/model/SY123/tileset.json",
|
|
|
- ];
|
|
|
-
|
|
|
- // 存储加载成功的瓦片集
|
|
|
- const loadedTilesets = [];
|
|
|
-
|
|
|
- // 批量加载瓦片集
|
|
|
- async function loadMultipleTilesets() {
|
|
|
- try {
|
|
|
- const promises = tilesetUrls.map(async (url, index) => {
|
|
|
- try {
|
|
|
- const tileset = await Cesium.Cesium3DTileset.fromUrl(url, {
|
|
|
- maximumScreenSpaceError: 32,
|
|
|
- dynamicScreenSpaceError: true,
|
|
|
- skipLevelOfDetail: true,
|
|
|
- maximumConcurrentRequests: 5,
|
|
|
- tileCacheSize: 100
|
|
|
- });
|
|
|
- viewer.value.scene.primitives.add(tileset);
|
|
|
- loadedTilesets.push(tileset);
|
|
|
- console.log(`第${index + 1}块瓦片集加载完成`);
|
|
|
- return tileset;
|
|
|
- } catch (error) {
|
|
|
- console.error(`第${index + 1}块瓦片集加载失败:`, error);
|
|
|
- return null;
|
|
|
- }
|
|
|
+ try {
|
|
|
+ // 👉 核心修复1:禁用baseLayer,手动创建底图(避免布尔值冲突)
|
|
|
+ viewer.value = new Cesium.Viewer('cesiumContainer', {
|
|
|
+ timeline: false,
|
|
|
+ baseLayer: false, // 关键:设为false,不自动加载底图
|
|
|
+ geocoder: false,
|
|
|
+ homeButton: false,
|
|
|
+ sceneModePicker: false,
|
|
|
+ navigationHelpButton: false,
|
|
|
+ animation: false,
|
|
|
+ fullscreenButton: false,
|
|
|
+ vrButton: false,
|
|
|
+ selectionIndicator: false,
|
|
|
+ infoBox: false,
|
|
|
+ // 地形加载(保留)
|
|
|
+ terrainProvider: await Cesium.createWorldTerrainAsync({
|
|
|
+ requestVertexNormals: true,
|
|
|
+ })
|
|
|
+ });
|
|
|
+
|
|
|
+ // 👉 核心修复2:手动创建天地图底图(确保是ImageryLayer实例)
|
|
|
+ if (viewer.value && viewer.value.imageryLayers) {
|
|
|
+ const tdtProvider = new Cesium.WebMapTileServiceImageryProvider({
|
|
|
+ url: `https://t0.tianditu.com/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={TileMatrix}&TILEROW={TileRow}&TILECOL={TileCol}&tk=${TDTTK}`,
|
|
|
+ layer: "img",
|
|
|
+ style: "default",
|
|
|
+ format: "image/jpeg",
|
|
|
+ tileMatrixSetID: "w",
|
|
|
+ maximumLevel: 16,
|
|
|
+ show: true
|
|
|
});
|
|
|
+ // 显式创建ImageryLayer并添加
|
|
|
+ const tdtLayer = new Cesium.ImageryLayer(tdtProvider);
|
|
|
+ viewer.value.imageryLayers.add(tdtLayer);
|
|
|
+ }
|
|
|
|
|
|
- await Promise.all(promises);
|
|
|
- console.log("所有瓦片集加载操作已完成");
|
|
|
- } catch (error) {
|
|
|
- console.error("批量加载逻辑出错:", error);
|
|
|
+ // 加载倾斜摄影瓦片集
|
|
|
+ const tilesetUrls = [
|
|
|
+ "http://localhost:9003/model/TSQ1234/tileset.json",
|
|
|
+ "http://localhost:9003/model/SY123/tileset.json",
|
|
|
+ ];
|
|
|
+ const loadedTilesets = [];
|
|
|
+
|
|
|
+ async function loadMultipleTilesets() {
|
|
|
+ try {
|
|
|
+ const promises = tilesetUrls.map(async (url, index) => {
|
|
|
+ try {
|
|
|
+ const tileset = await Cesium.Cesium3DTileset.fromUrl(url, {
|
|
|
+ maximumScreenSpaceError: 32,
|
|
|
+ dynamicScreenSpaceError: true,
|
|
|
+ skipLevelOfDetail: true,
|
|
|
+ maximumConcurrentRequests: 5,
|
|
|
+ tileCacheSize: 100
|
|
|
+ });
|
|
|
+ viewer.value.scene.primitives.add(tileset);
|
|
|
+ loadedTilesets.push(tileset);
|
|
|
+ console.log(`第${index + 1}块瓦片集加载完成`);
|
|
|
+ return tileset;
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`第${index + 1}块瓦片集加载失败:`, error);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ await Promise.all(promises);
|
|
|
+ console.log("所有瓦片集加载完成");
|
|
|
+ } catch (error) {
|
|
|
+ console.error("瓦片集批量加载出错:", error);
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
+ loadMultipleTilesets();
|
|
|
|
|
|
- // 执行加载
|
|
|
- loadMultipleTilesets();
|
|
|
-
|
|
|
- // 添加天地图图层
|
|
|
-// const tdtLayer = new Cesium.WebMapTileServiceImageryProvider({
|
|
|
-// url: `https://t0.tianditu.com/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={TileMatrix}&TILEROW={TileRow}&TILECOL={TileCol}&tk=${TDTTK}`,
|
|
|
-// layer: "tdt",
|
|
|
-// style: "default",
|
|
|
-// format: "image/jpeg",
|
|
|
-// tileMatrixSetID: "w",
|
|
|
-// maximumLevel: 16,
|
|
|
-// show: true,
|
|
|
-// });
|
|
|
-// viewer.value.imageryLayers.addImageryProvider(tdtLayer);
|
|
|
-
|
|
|
-// const tdtAnnotionLayer = new Cesium.WebMapTileServiceImageryProvider({
|
|
|
-// url: `http://t0.tianditu.com/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={TileMatrix}&TILEROW={TileRow}&TILECOL={TileCol}&tk=${TDTTK}`,
|
|
|
-// layer: "tdtAnno",
|
|
|
-// style: "default",
|
|
|
-// format: "image/jpeg",
|
|
|
-// tileMatrixSetID: "w",
|
|
|
-// maximumLevel: 18,
|
|
|
-// show: false,
|
|
|
-// });
|
|
|
-// viewer.value.imageryLayers.addImageryProvider(tdtAnnotionLayer);
|
|
|
-
|
|
|
- // 初始化视图
|
|
|
- viewer.value.cesiumWidget.creditContainer.style.display = "none";
|
|
|
- viewer.value.camera.setView({
|
|
|
- destination: Cesium.Cartesian3.fromDegrees(120.169103, 31.226174, 500000),
|
|
|
- orientation: {
|
|
|
- heading: Cesium.Math.toRadians(0),
|
|
|
- pitch: Cesium.Math.toRadians(-90),
|
|
|
- },
|
|
|
- });
|
|
|
-
|
|
|
- // 初始触发一次 resize 确保地图正确显示
|
|
|
- viewer.value.resize();
|
|
|
+ // 初始化视图
|
|
|
+ viewer.value.cesiumWidget.creditContainer.style.display = "none";
|
|
|
+ viewer.value.camera.setView({
|
|
|
+ destination: Cesium.Cartesian3.fromDegrees(121.75, 31.15, 30000),
|
|
|
+ orientation: {
|
|
|
+ heading: Cesium.Math.toRadians(0),
|
|
|
+ pitch: Cesium.Math.toRadians(-60),
|
|
|
+ },
|
|
|
+ });
|
|
|
+ viewer.value.resize();
|
|
|
+
|
|
|
+ // 初始化加载WMS图层
|
|
|
+ await nextTick();
|
|
|
+ loadWmsLayer(currentTime.value);
|
|
|
+ } catch (error) {
|
|
|
+ // 捕获Viewer初始化错误,避免页面崩溃
|
|
|
+ console.error('Cesium Viewer初始化失败:', error);
|
|
|
+ viewer.value = null;
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
+ // 销毁Viewer实例,释放资源
|
|
|
if (viewer.value && !viewer.value.isDestroyed()) {
|
|
|
viewer.value.destroy();
|
|
|
}
|
|
|
@@ -213,7 +307,7 @@ onUnmounted(() => {
|
|
|
position: absolute;
|
|
|
top: 200px;
|
|
|
left: 150px;
|
|
|
- z-index: 100;
|
|
|
+ z-index: 1000;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 10px;
|
|
|
@@ -243,11 +337,52 @@ onUnmounted(() => {
|
|
|
color: #1E88E5;
|
|
|
}
|
|
|
|
|
|
+/* 时间轴样式:强制显示 + 最高层级 */
|
|
|
+.timeline-wrapper {
|
|
|
+ position: absolute !important;
|
|
|
+ bottom: 50px !important;
|
|
|
+ left: 0 !important;
|
|
|
+ right: 0 !important;
|
|
|
+ z-index: 9999 !important;
|
|
|
+ width: 80% !important;
|
|
|
+ margin: 0 auto !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 穿透TimeSlider组件样式 */
|
|
|
+:deep(.time-slider-container) {
|
|
|
+ width: 100% !important;
|
|
|
+ min-width: 300px !important;
|
|
|
+ background: rgba(0, 0, 0, 0.85) !important;
|
|
|
+ color: #fff !important;
|
|
|
+ padding: 15px !important;
|
|
|
+ border-radius: 8px !important;
|
|
|
+ box-sizing: border-box !important;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.time-slider-container .slider) {
|
|
|
+ width: 100% !important;
|
|
|
+ cursor: pointer !important;
|
|
|
+ accent-color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.time-slider-container .play-btn) {
|
|
|
+ background: #409eff !important;
|
|
|
+ color: #fff !important;
|
|
|
+ border: none !important;
|
|
|
+ padding: 4px 12px !important;
|
|
|
+ border-radius: 4px !important;
|
|
|
+ cursor: pointer !important;
|
|
|
+}
|
|
|
+
|
|
|
/* 响应式调整 */
|
|
|
@media (max-width: 1200px) {
|
|
|
.control-buttons {
|
|
|
top: 150px;
|
|
|
left: 20px;
|
|
|
}
|
|
|
+ .timeline-wrapper {
|
|
|
+ width: 90% !important;
|
|
|
+ bottom: 30px !important;
|
|
|
+ }
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|