|
@@ -1,20 +1,20 @@
|
|
|
<template>
|
|
|
<div id="cesiumContainer" style="height: 100%;width: 100%;"></div>
|
|
|
|
|
|
- <!-- 控制按钮 -->
|
|
|
- <div class="control-buttons">
|
|
|
- <button class="control-btn" @click="goToHomeView" title="返回首页视角">
|
|
|
- <i class="fas fa-home"></i>
|
|
|
- </button>
|
|
|
- <button class="control-btn" @click="togglePOIDisplay" title="显示/隐藏POI点">
|
|
|
- <i class="fas fa-map-marker-alt" :class="{ 'active': poiVisible }"></i>
|
|
|
- </button>
|
|
|
- <button class="control-btn" @click="toggleTyphoon" title="显示/隐藏台风并切换视角">
|
|
|
- <i class="fas fa-wind" :class="{ 'active': typhoonVisible }"></i>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+ <!-- 控制按钮 -->
|
|
|
+ <div class="control-buttons">
|
|
|
+ <button class="control-btn" @click="goToHomeView" title="返回首页视角">
|
|
|
+ <i class="fas fa-home"></i>
|
|
|
+ </button>
|
|
|
+ <button class="control-btn" @click="togglePOIDisplay" title="显示/隐藏POI点">
|
|
|
+ <i class="fas fa-map-marker-alt" :class="{ 'active': poiVisible }"></i>
|
|
|
+ </button>
|
|
|
+ <button class="control-btn" @click="toggleTyphoon" title="显示/隐藏台风并切换视角">
|
|
|
+ <i class="fas fa-wind" :class="{ 'active': typhoonVisible }"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- 自定义弹框组件 -->
|
|
|
+ <!-- 自定义弹框组件 - POI点 -->
|
|
|
<div v-if="selectedPoint" class="custom-popup" :style="{
|
|
|
left: `${popupPosition.x}px`,
|
|
|
top: `${popupPosition.y}px`
|
|
@@ -26,9 +26,28 @@
|
|
|
<div class="popup-arrow"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- 台风图例面板 -->
|
|
|
- <ul class="legend">
|
|
|
+
|
|
|
+ <!-- 台风路径点信息弹窗 -->
|
|
|
+ <div v-if="typhoonInfoVisible && typhoonVisible" class="typhoon-popup" :style="{
|
|
|
+ left: `${typhoonPopupPosition.x}px`,
|
|
|
+ top: `${typhoonPopupPosition.y}px`
|
|
|
+ }">
|
|
|
+ <div class="popup-content">
|
|
|
+ <h3>{{ typhoonInfo.name }}({{ typhoonInfo.enname }})</h3>
|
|
|
+ <p>{{ typhoonInfo.time }}</p>
|
|
|
+ <p><strong>中心位置:</strong> {{ typhoonInfo.lng }}° / {{ typhoonInfo.lat }}°</p>
|
|
|
+ <p><strong>风速风力:</strong> {{ typhoonInfo.speed }} m/s ({{ typhoonInfo.power }}级)</p>
|
|
|
+ <p><strong>中心气压:</strong> {{ typhoonInfo.pressure }} hPa</p>
|
|
|
+ <p><strong>移速移向:</strong> {{ typhoonInfo.movedirection }} {{ typhoonInfo.movespeed }} km/h</p>
|
|
|
+ <p><strong>七级半径:</strong> {{ typhoonInfo.radius7 || '--' }}</p>
|
|
|
+ <p><strong>十级半径:</strong> {{ typhoonInfo.radius10 || '--' }}</p>
|
|
|
+ <p><strong>十二级半径:</strong> {{ typhoonInfo.radius12 || '--' }}</p>
|
|
|
+ <div class="popup-arrow"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 台风图例面板 - 添加v-if控制显示 -->
|
|
|
+ <ul class="legend" v-if="typhoonVisible">
|
|
|
<li>
|
|
|
<span class="dot green"></span>
|
|
|
<span>热带低压</span>
|
|
@@ -53,7 +72,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, onMounted, onUnmounted, watch } from 'vue'
|
|
|
+import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
import * as Cesium from 'cesium';
|
|
|
import "cesium/Build/CesiumUnminified/Widgets/widgets.css";
|
|
|
import JYLData from '@/assets/Data/THJYL.json'
|
|
@@ -66,6 +85,12 @@ const popupPosition = ref({ x: 0, y: 0 });
|
|
|
let handler = null;
|
|
|
let viewer = null;
|
|
|
|
|
|
+// 新增台风信息弹窗变量
|
|
|
+const typhoonInfoVisible = ref(false);
|
|
|
+const typhoonInfo = ref({});
|
|
|
+const typhoonPopupPosition = ref({ x: 0, y: 0 });
|
|
|
+const typhoonPointDataMap = ref(new Map()); // 存储台风点与数据的映射
|
|
|
+
|
|
|
// 控制按钮相关变量
|
|
|
const poiVisible = ref(true); // POI点显示状态
|
|
|
const typhoonVisible = ref(true); // 台风显示状态
|
|
@@ -120,45 +145,50 @@ const togglePOIDisplay = () => {
|
|
|
// 切换台风显示/隐藏并跳转视角
|
|
|
const toggleTyphoon = () => {
|
|
|
typhoonVisible.value = !typhoonVisible.value;
|
|
|
-
|
|
|
+
|
|
|
// 控制台风相关实体显示/隐藏
|
|
|
if (myEntityCollection.value) {
|
|
|
myEntityCollection.value.show = typhoonVisible.value;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 控制风圈显示/隐藏
|
|
|
fengquanLayers.value.forEach(entity => {
|
|
|
if (entity) {
|
|
|
entity.show = typhoonVisible.value;
|
|
|
}
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 控制路径线显示/隐藏
|
|
|
typhoonRelatedEntities.value.paths.forEach(entity => {
|
|
|
if (entity) {
|
|
|
entity.show = typhoonVisible.value;
|
|
|
}
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 控制预报路径显示/隐藏
|
|
|
typhoonRelatedEntities.value.forecasts.forEach(entity => {
|
|
|
if (entity) {
|
|
|
entity.show = typhoonVisible.value;
|
|
|
}
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 控制警戒线显示/隐藏
|
|
|
typhoonRelatedEntities.value.warnings.forEach(entity => {
|
|
|
if (entity) {
|
|
|
entity.show = typhoonVisible.value;
|
|
|
}
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 控制台风标记显示/隐藏
|
|
|
if (tbentity.value) {
|
|
|
tbentity.value.show = typhoonVisible.value;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ // 如果台风隐藏,同时隐藏信息弹窗
|
|
|
+ if (!typhoonVisible.value) {
|
|
|
+ typhoonInfoVisible.value = false;
|
|
|
+ }
|
|
|
+
|
|
|
// 如果显示台风,则跳转到台风视角
|
|
|
if (typhoonVisible.value) {
|
|
|
viewer.camera.flyTo({
|
|
@@ -174,7 +204,7 @@ onMounted(async () => {
|
|
|
document.body.style.height = '100%';
|
|
|
document.body.style.margin = '0';
|
|
|
document.body.style.padding = '0';
|
|
|
-
|
|
|
+
|
|
|
// 初始化Cesium viewer
|
|
|
viewer = new Cesium.Viewer('cesiumContainer', {
|
|
|
timeline: false,
|
|
@@ -245,7 +275,7 @@ onMounted(async () => {
|
|
|
JYLData.forEach((item) => {
|
|
|
const position = Cesium.Cartesian3.fromDegrees(
|
|
|
parseFloat(item.LGTD),
|
|
|
- parseFloat(item.LTTD)
|
|
|
+ parseFloat(item.LTTD)
|
|
|
);
|
|
|
const entity = viewer.entities.add({
|
|
|
position: position,
|
|
@@ -326,6 +356,9 @@ onMounted(async () => {
|
|
|
const correctedY = click.position.y / scaleRatio;
|
|
|
const pickedObject = viewer.scene.pick(new Cesium.Cartesian2(correctedX, correctedY));
|
|
|
|
|
|
+ // 隐藏台风信息弹窗
|
|
|
+ typhoonInfoVisible.value = false;
|
|
|
+
|
|
|
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
|
|
|
const entityId = pickedObject.id.id;
|
|
|
const data = entityDataMap.get(entityId) || pickedObject.id.properties?.data?.getValue();
|
|
@@ -349,9 +382,50 @@ onMounted(async () => {
|
|
|
}
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|
|
|
|
|
+ // 添加鼠标移动事件处理 - 用于台风路径点悬停
|
|
|
+ handler.setInputAction((movement) => {
|
|
|
+ // 只有台风可见时才处理台风信息弹窗
|
|
|
+ if (!typhoonVisible.value) {
|
|
|
+ typhoonInfoVisible.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const scaleRatio = getScaleRatio();
|
|
|
+ const correctedX = movement.endPosition.x / scaleRatio;
|
|
|
+ const correctedY = movement.endPosition.y / scaleRatio;
|
|
|
+ const pickedObject = viewer.scene.pick(new Cesium.Cartesian2(correctedX, correctedY));
|
|
|
+
|
|
|
+ // 检查是否悬停在台风路径点上
|
|
|
+ if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
|
|
|
+ const entityId = pickedObject.id.id;
|
|
|
+ // 检查是否是台风路径点
|
|
|
+ if (entityId && entityId.startsWith('typhoon-point-')) {
|
|
|
+ const typhoonData = typhoonPointDataMap.value.get(entityId);
|
|
|
+ if (typhoonData) {
|
|
|
+ // 显示台风信息弹窗
|
|
|
+ typhoonInfo.value = typhoonData;
|
|
|
+ typhoonInfoVisible.value = true;
|
|
|
+
|
|
|
+ // 计算弹窗位置
|
|
|
+ const entityPosition = viewer.scene.cartesianToCanvasCoordinates(pickedObject.id.position._value);
|
|
|
+ if (entityPosition) {
|
|
|
+ typhoonPopupPosition.value = {
|
|
|
+ x: (entityPosition.x / scaleRatio) - 120,
|
|
|
+ y: (entityPosition.y / scaleRatio) - 200
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return; // 找到台风点后不再处理其他逻辑
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果不是台风路径点,隐藏台风信息弹窗
|
|
|
+ typhoonInfoVisible.value = false;
|
|
|
+ }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
|
|
+
|
|
|
// 初始化台风相关功能
|
|
|
initTyphoonVisualization();
|
|
|
-
|
|
|
+
|
|
|
// 监听窗口大小变化,确保地图铺满
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
// 初始触发一次 resize 确保地图正确显示
|
|
@@ -362,14 +436,14 @@ onMounted(async () => {
|
|
|
const initTyphoonVisualization = () => {
|
|
|
// 设置Cesium Ion访问令牌
|
|
|
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1ODIwOGQ2Ny1hMTFhLTQ4OGQtODJhZi0wNmMzZGNhNjU5OWMiLCJpZCI6NTkzMTMsImlhdCI6MTYyMzk4ODQ4NX0.40CU0i0LswshdxVXAXEJgfEDJN3EK_jPbo_S8lece9E';
|
|
|
-
|
|
|
+
|
|
|
// 创建台风数据源
|
|
|
myEntityCollection.value = new Cesium.CustomDataSource("typhoonPoints");
|
|
|
viewer.dataSources.add(myEntityCollection.value);
|
|
|
-
|
|
|
+
|
|
|
// 初始化警戒线
|
|
|
initJJ();
|
|
|
-
|
|
|
+
|
|
|
// 加载台风数据
|
|
|
initPoints();
|
|
|
};
|
|
@@ -378,53 +452,77 @@ const initTyphoonVisualization = () => {
|
|
|
const initPoints = () => {
|
|
|
// 加载台风JSON数据
|
|
|
const jsonUrl = new URL('../assets/Data/202508.json', import.meta.url).href;
|
|
|
-
|
|
|
+
|
|
|
axios.get(jsonUrl)
|
|
|
.then(response => {
|
|
|
- const points = response.data.points;
|
|
|
- processPoints(points);
|
|
|
+ const typhoonData = response.data;
|
|
|
+ // 处理台风数据,使用response.data.points
|
|
|
+ processPoints(typhoonData.points, typhoonData);
|
|
|
})
|
|
|
.catch(error => {
|
|
|
console.error('加载台风JSON数据失败,使用测试数据', error);
|
|
|
- // 测试数据
|
|
|
- const testPoints = [
|
|
|
- {
|
|
|
- "lng": 120,
|
|
|
- "lat": 20,
|
|
|
- "strong": "台风",
|
|
|
- "forecast": [
|
|
|
- {
|
|
|
- "forecastpoints": [
|
|
|
- {"lng": 121, "lat": 19},
|
|
|
- {"lng": 122, "lat": 18}
|
|
|
- ]
|
|
|
- }
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- "lng": 121,
|
|
|
- "lat": 19.5,
|
|
|
- "strong": "强台风",
|
|
|
- "forecast": []
|
|
|
- }
|
|
|
- ];
|
|
|
- processPoints(testPoints);
|
|
|
+ // 测试数据 - 包含详细台风信息
|
|
|
+ const testTyphoonData = {
|
|
|
+ "name": "竹节草",
|
|
|
+ "enname": "CO-MAY",
|
|
|
+ "points": [
|
|
|
+ {
|
|
|
+ "time": "2025-07-23 14:00:00",
|
|
|
+ "lng": "119.20",
|
|
|
+ "lat": "18.40",
|
|
|
+ "strong": "热带低压",
|
|
|
+ "power": "7",
|
|
|
+ "speed": "15",
|
|
|
+ "pressure": "998",
|
|
|
+ "movespeed": "14",
|
|
|
+ "movedirection": "南西",
|
|
|
+ "radius7": "150-280公里",
|
|
|
+ "radius10": "--",
|
|
|
+ "radius12": "--",
|
|
|
+ "forecast": [
|
|
|
+ {
|
|
|
+ "tm": "中国",
|
|
|
+ "forecastpoints": [
|
|
|
+ { "lng": 121, "lat": 19 },
|
|
|
+ { "lng": 122, "lat": 18 }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "time": "2025-07-23 17:00:00",
|
|
|
+ "lng": "119.10",
|
|
|
+ "lat": "18.00",
|
|
|
+ "strong": "热带低压",
|
|
|
+ "power": "7",
|
|
|
+ "speed": "15",
|
|
|
+ "pressure": "996",
|
|
|
+ "movespeed": "13",
|
|
|
+ "movedirection": "南南西",
|
|
|
+ "radius7": "180-300公里",
|
|
|
+ "radius10": "50-80公里",
|
|
|
+ "radius12": "--",
|
|
|
+ "forecast": []
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ processPoints(testTyphoonData.points, testTyphoonData);
|
|
|
});
|
|
|
};
|
|
|
|
|
|
// 处理台风数据
|
|
|
-const processPoints = (points) => {
|
|
|
+const processPoints = (points, typhoonData) => {
|
|
|
const lineArr = [];
|
|
|
-
|
|
|
- points.forEach(element => {
|
|
|
+
|
|
|
+ points.forEach((element, index) => {
|
|
|
// 强制转换为数字类型
|
|
|
const lng = Number(element.lng);
|
|
|
const lat = Number(element.lat);
|
|
|
-
|
|
|
+
|
|
|
let color = Cesium.Color.RED;
|
|
|
-
|
|
|
+
|
|
|
// 根据台风强度设置颜色
|
|
|
- switch(element.strong) {
|
|
|
+ switch (element.strong) {
|
|
|
case "热带低压":
|
|
|
color = Cesium.Color.GREEN;
|
|
|
break;
|
|
@@ -444,18 +542,40 @@ const processPoints = (points) => {
|
|
|
color = Cesium.Color.RED;
|
|
|
break;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
lineArr.push(lng, lat);
|
|
|
+ // 为每个台风路径点创建唯一ID
|
|
|
+ const pointId = `typhoon-point-${index}`;
|
|
|
const entity = new Cesium.Entity({
|
|
|
+ id: pointId,
|
|
|
position: Cesium.Cartesian3.fromDegrees(lng, lat),
|
|
|
point: {
|
|
|
- pixelSize: 5,
|
|
|
- color: color
|
|
|
+ pixelSize: 8, // 增大点的尺寸,更容易被鼠标选中
|
|
|
+ color: color,
|
|
|
+ outlineColor: Cesium.Color.WHITE,
|
|
|
+ outlineWidth: 2
|
|
|
}
|
|
|
});
|
|
|
myEntityCollection.value.entities.add(entity);
|
|
|
+
|
|
|
+ // 存储台风点与数据的映射关系,使用正确的属性名
|
|
|
+ typhoonPointDataMap.value.set(pointId, {
|
|
|
+ name: typhoonData.name || '',
|
|
|
+ enname: typhoonData.enname || '',
|
|
|
+ time: element.time || '',
|
|
|
+ lng: element.lng,
|
|
|
+ lat: element.lat,
|
|
|
+ speed: element.speed,
|
|
|
+ power: element.power,
|
|
|
+ pressure: element.pressure,
|
|
|
+ movedirection: element.movedirection,
|
|
|
+ movespeed: element.movespeed,
|
|
|
+ radius7: element.radius7 || '--',
|
|
|
+ radius10: element.radius10 || '--',
|
|
|
+ radius12: element.radius12 || '--'
|
|
|
+ });
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 添加台风路径线
|
|
|
const pathEntity = viewer.entities.add({
|
|
|
polyline: {
|
|
@@ -466,12 +586,39 @@ const processPoints = (points) => {
|
|
|
}
|
|
|
});
|
|
|
typhoonRelatedEntities.value.paths.push(pathEntity);
|
|
|
-
|
|
|
+
|
|
|
+ // 添加台风登陆点标记
|
|
|
+ if (typhoonData.land && typhoonData.land.length > 0) {
|
|
|
+ typhoonData.land.forEach((landPoint, index) => {
|
|
|
+ const landEntity = viewer.entities.add({
|
|
|
+ position: Cesium.Cartesian3.fromDegrees(
|
|
|
+ Number(landPoint.lng),
|
|
|
+ Number(landPoint.lat)
|
|
|
+ ),
|
|
|
+ point: {
|
|
|
+ pixelSize: 12,
|
|
|
+ color: Cesium.Color.BLACK,
|
|
|
+ outlineColor: Cesium.Color.YELLOW,
|
|
|
+ outlineWidth: 2
|
|
|
+ },
|
|
|
+ label: {
|
|
|
+ text: `登陆 ${index + 1}`,
|
|
|
+ font: '14px 微软雅黑',
|
|
|
+ fillColor: Cesium.Color.YELLOW,
|
|
|
+ backgroundColor: Cesium.Color.BLACK.withAlpha(0.7),
|
|
|
+ showBackground: true,
|
|
|
+ pixelOffset: new Cesium.Cartesian2(0, -20)
|
|
|
+ }
|
|
|
+ });
|
|
|
+ myEntityCollection.value.entities.add(landEntity);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
if (points.length > 0) {
|
|
|
// 初始化预报路径
|
|
|
initForeast(points[points.length - 1]);
|
|
|
// 添加台风动画
|
|
|
- adds(points);
|
|
|
+ adds(points, typhoonData);
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -485,37 +632,40 @@ const initForeast = (data) => {
|
|
|
Cesium.Color.fromCssColorString("#E76F15"),
|
|
|
Cesium.Color.fromCssColorString("#15D9E7")
|
|
|
];
|
|
|
-
|
|
|
+
|
|
|
forecast.forEach((ele, ii) => {
|
|
|
- const lineArr = [];
|
|
|
- ele.forecastpoints.forEach(e => {
|
|
|
- // 强制转换为数字
|
|
|
- const lng = Number(e.lng);
|
|
|
- const lat = Number(e.lat);
|
|
|
- lineArr.push(lng, lat);
|
|
|
-
|
|
|
- const entity = new Cesium.Entity({
|
|
|
- position: Cesium.Cartesian3.fromDegrees(lng, lat),
|
|
|
- point: {
|
|
|
- pixelSize: 7,
|
|
|
- color: colorArr[ii]
|
|
|
+ // 适配新的数据结构,预报点在tm对象下的forecastpoints中
|
|
|
+ if (ele.forecastpoints && ele.forecastpoints.length > 0) {
|
|
|
+ const lineArr = [];
|
|
|
+ ele.forecastpoints.forEach(e => {
|
|
|
+ // 强制转换为数字
|
|
|
+ const lng = Number(e.lng);
|
|
|
+ const lat = Number(e.lat);
|
|
|
+ lineArr.push(lng, lat);
|
|
|
+
|
|
|
+ const entity = new Cesium.Entity({
|
|
|
+ position: Cesium.Cartesian3.fromDegrees(lng, lat),
|
|
|
+ point: {
|
|
|
+ pixelSize: 7,
|
|
|
+ color: colorArr[ii % colorArr.length]
|
|
|
+ }
|
|
|
+ });
|
|
|
+ myEntityCollection.value.entities.add(entity);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 添加预报路径线
|
|
|
+ const forecastEntity = viewer.entities.add({
|
|
|
+ polyline: {
|
|
|
+ positions: Cesium.Cartesian3.fromDegreesArray(lineArr),
|
|
|
+ width: 2,
|
|
|
+ clampToGround: true,
|
|
|
+ material: new Cesium.PolylineDashMaterialProperty({
|
|
|
+ color: colorArr[ii % colorArr.length]
|
|
|
+ })
|
|
|
}
|
|
|
});
|
|
|
- myEntityCollection.value.entities.add(entity);
|
|
|
- });
|
|
|
-
|
|
|
- // 添加预报路径线
|
|
|
- const forecastEntity = viewer.entities.add({
|
|
|
- polyline: {
|
|
|
- positions: Cesium.Cartesian3.fromDegreesArray(lineArr),
|
|
|
- width: 2,
|
|
|
- clampToGround: true,
|
|
|
- material: new Cesium.PolylineDashMaterialProperty({
|
|
|
- color: colorArr[ii]
|
|
|
- })
|
|
|
- }
|
|
|
- });
|
|
|
- typhoonRelatedEntities.value.forecasts.push(forecastEntity);
|
|
|
+ typhoonRelatedEntities.value.forecasts.push(forecastEntity);
|
|
|
+ }
|
|
|
});
|
|
|
};
|
|
|
|
|
@@ -534,7 +684,7 @@ const initJJ = () => {
|
|
|
}
|
|
|
});
|
|
|
typhoonRelatedEntities.value.warnings.push(line24h);
|
|
|
-
|
|
|
+
|
|
|
// 48小时警戒线
|
|
|
const line48h = viewer.entities.add({
|
|
|
name: '48小时警戒线',
|
|
@@ -548,7 +698,7 @@ const initJJ = () => {
|
|
|
}
|
|
|
});
|
|
|
typhoonRelatedEntities.value.warnings.push(line48h);
|
|
|
-
|
|
|
+
|
|
|
// 警戒线标签
|
|
|
const label24h = viewer.entities.add({
|
|
|
position: Cesium.Cartesian3.fromDegrees(126.129019, 29.104287),
|
|
@@ -559,7 +709,7 @@ const initJJ = () => {
|
|
|
}
|
|
|
});
|
|
|
typhoonRelatedEntities.value.warnings.push(label24h);
|
|
|
-
|
|
|
+
|
|
|
const label48h = viewer.entities.add({
|
|
|
position: Cesium.Cartesian3.fromDegrees(132, 20),
|
|
|
label: {
|
|
@@ -572,20 +722,23 @@ const initJJ = () => {
|
|
|
};
|
|
|
|
|
|
// 添加台风动画
|
|
|
-const adds = (data) => {
|
|
|
- addTB();
|
|
|
-
|
|
|
+const adds = (data, typhoonData) => {
|
|
|
+ addTB(typhoonData);
|
|
|
+
|
|
|
// 清除可能存在的旧定时器
|
|
|
if (typhoonInterval) {
|
|
|
clearInterval(typhoonInterval);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 设置台风动画定时器
|
|
|
typhoonInterval = setInterval(() => {
|
|
|
+ // 只有台风可见时才更新动画
|
|
|
+ if (!typhoonVisible.value) return;
|
|
|
+
|
|
|
if (iii.value >= data.length) {
|
|
|
iii.value = 0;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
const kkk = iii.value * 2;
|
|
|
// 确保经纬度是数字类型
|
|
|
const currentData = data[iii.value];
|
|
@@ -611,14 +764,14 @@ const adds = (data) => {
|
|
|
radius4: 170 - kkk
|
|
|
}
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
if (tbentity.value) {
|
|
|
tbentity.value.position = Cesium.Cartesian3.fromDegrees(
|
|
|
Number(currentData.lng),
|
|
|
Number(currentData.lat)
|
|
|
);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
iii.value = (iii.value + 1) % data.length;
|
|
|
removeTFLayer();
|
|
|
// 只有当台风可见时才添加风圈
|
|
@@ -629,24 +782,28 @@ const adds = (data) => {
|
|
|
};
|
|
|
|
|
|
// 添加台风标记
|
|
|
-const addTB = () => {
|
|
|
+const addTB = (typhoonData) => {
|
|
|
// 尝试使用GIF动画标记
|
|
|
const SuperGif = window.SuperGif;
|
|
|
if (typeof SuperGif !== 'function') {
|
|
|
console.warn('SuperGif未加载成功,使用默认标记');
|
|
|
- useDefaultMarker();
|
|
|
+ useDefaultMarker(typhoonData);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
const img = document.createElement('img');
|
|
|
img.src = '/tf.gif'; // public目录下的GIF
|
|
|
-
|
|
|
+
|
|
|
img.onload = () => {
|
|
|
try {
|
|
|
const rub = new SuperGif({ gif: img });
|
|
|
rub.load(() => {
|
|
|
tbentity.value = viewer.entities.add({
|
|
|
- position: Cesium.Cartesian3.fromDegrees(75.166493, 39.9060534),
|
|
|
+ // 使用台风的中心经纬度作为初始位置
|
|
|
+ position: Cesium.Cartesian3.fromDegrees(
|
|
|
+ Number(typhoonData.centerlng || 123.75),
|
|
|
+ Number(typhoonData.centerlat || 28.95)
|
|
|
+ ),
|
|
|
billboard: {
|
|
|
image: new Cesium.CallbackProperty(() => {
|
|
|
return rub.get_canvas().toDataURL('image/png');
|
|
@@ -657,20 +814,23 @@ const addTB = () => {
|
|
|
});
|
|
|
} catch (error) {
|
|
|
console.error('GIF处理失败,使用默认标记', error);
|
|
|
- useDefaultMarker();
|
|
|
+ useDefaultMarker(typhoonData);
|
|
|
}
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
img.onerror = () => {
|
|
|
console.error('GIF加载失败,使用默认标记');
|
|
|
- useDefaultMarker();
|
|
|
+ useDefaultMarker(typhoonData);
|
|
|
};
|
|
|
};
|
|
|
|
|
|
// 使用默认标记
|
|
|
-const useDefaultMarker = () => {
|
|
|
+const useDefaultMarker = (typhoonData) => {
|
|
|
tbentity.value = viewer.entities.add({
|
|
|
- position: Cesium.Cartesian3.fromDegrees(75.166493, 39.9060534),
|
|
|
+ position: Cesium.Cartesian3.fromDegrees(
|
|
|
+ Number(typhoonData.centerlng || 123.75),
|
|
|
+ Number(typhoonData.centerlat || 28.95)
|
|
|
+ ),
|
|
|
point: {
|
|
|
pixelSize: 15,
|
|
|
color: Cesium.Color.RED,
|
|
@@ -691,7 +851,7 @@ const removeTFLayer = () => {
|
|
|
// 添加台风风圈
|
|
|
const addTyphoonCircle = () => {
|
|
|
if (!currentPointObj.value || !typhoonVisible.value) return;
|
|
|
-
|
|
|
+
|
|
|
const circles = ['circle7', 'circle10', 'circle12'];
|
|
|
circles.forEach(item => {
|
|
|
const entity = viewer.entities.add({
|
|
@@ -739,12 +899,12 @@ const getTyphoonPolygonPoints = (pointObj, cNum) => {
|
|
|
];
|
|
|
const startAngleList = [0, 90, 180, 270];
|
|
|
let fx, fy;
|
|
|
-
|
|
|
+
|
|
|
startAngleList.forEach((startAngle, index) => {
|
|
|
const radius = radiusList[index] / 100;
|
|
|
const pointNum = 90;
|
|
|
const endAngle = startAngle + 90;
|
|
|
-
|
|
|
+
|
|
|
for (let i = 0; i <= pointNum; i++) {
|
|
|
const angle = startAngle + ((endAngle - startAngle) * i) / pointNum;
|
|
|
const sin = Math.sin((angle * Math.PI) / 180);
|
|
@@ -752,34 +912,41 @@ const getTyphoonPolygonPoints = (pointObj, cNum) => {
|
|
|
const x = center[0] + radius * sin;
|
|
|
const y = center[1] + radius * cos;
|
|
|
points.push(x, y);
|
|
|
-
|
|
|
+
|
|
|
if (startAngle === 0 && i === 0) {
|
|
|
fx = x;
|
|
|
fy = y;
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
points.push(fx, fy);
|
|
|
return points;
|
|
|
};
|
|
|
|
|
|
+// 处理窗口大小变化
|
|
|
+const handleResize = () => {
|
|
|
+ if (viewer) {
|
|
|
+ viewer.resize();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
// 清理函数
|
|
|
onUnmounted(() => {
|
|
|
// 清除台风动画定时器
|
|
|
if (typhoonInterval) {
|
|
|
clearInterval(typhoonInterval);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 移除窗口大小变化监听
|
|
|
window.removeEventListener('resize', handleResize);
|
|
|
-
|
|
|
+
|
|
|
// 销毁事件处理器
|
|
|
if (handler) {
|
|
|
handler.destroy();
|
|
|
handler = null;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 销毁viewer
|
|
|
if (viewer && !viewer.isDestroyed()) {
|
|
|
viewer.destroy();
|
|
@@ -788,7 +955,6 @@ onUnmounted(() => {
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-
|
|
|
/* 控制按钮样式 */
|
|
|
.control-buttons {
|
|
|
position: absolute;
|
|
@@ -825,7 +991,8 @@ onUnmounted(() => {
|
|
|
}
|
|
|
|
|
|
|
|
|
-.custom-popup {
|
|
|
+.custom-popup,
|
|
|
+.typhoon-popup {
|
|
|
position: absolute;
|
|
|
z-index: 1000;
|
|
|
display: block;
|
|
@@ -837,7 +1004,7 @@ onUnmounted(() => {
|
|
|
border-radius: 5px;
|
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
|
padding: 10px 15px;
|
|
|
- width: 200px;
|
|
|
+ width: 240px;
|
|
|
pointer-events: all;
|
|
|
}
|
|
|
|
|
@@ -857,6 +1024,7 @@ onUnmounted(() => {
|
|
|
margin-top: 0;
|
|
|
margin-bottom: 8px;
|
|
|
color: #333;
|
|
|
+ font-size: 16px;
|
|
|
}
|
|
|
|
|
|
.popup-content p {
|
|
@@ -877,6 +1045,7 @@ onUnmounted(() => {
|
|
|
padding: 10px 15px;
|
|
|
border-radius: 4px;
|
|
|
margin: 0;
|
|
|
+ transition: opacity 0.3s ease, transform 0.3s ease;
|
|
|
}
|
|
|
|
|
|
.legend li {
|
|
@@ -913,3 +1082,4 @@ onUnmounted(() => {
|
|
|
background: red;
|
|
|
}
|
|
|
</style>
|
|
|
+
|