<think>我们将在Vue2项目中使用Cesium实现台风轨迹展示效果。主要功能包括:
1. 动态轨迹:台风路径的动画效果(如粒子流动或动态移动的台风图标)
2. 位置点标记:在路径上标记台风的位置点
3. 点击弹框:点击台风或标记点时显示台风详细信息
4. 风圈绘制:根据台风强度绘制不同半径的风圈
5. 多台风同时显示:支持同时展示多个台风的路径和风圈
步骤:
1. 准备数据:假设我们有多个台风的数据,每个台风包含多个时间点的位置、风速、气压等信息,以及每个时间点的风圈半径(或根据风速计算)。
2. 初始化Cesium Viewer:在Vue组件中创建Cesium Viewer。
3. 绘制台风路径:使用Polyline或PolylineGlow绘制路径,并添加动态效果(如流动材质或粒子系统)。
4. 标记位置点:在路径的关键点(如每6小时)添加点标记。
5. 添加台风图标:在台风当前位置添加一个动态图标(如旋转的台风图标)并随时间移动。
6. 绘制风圈:每个时间点根据风圈半径(或风速计算半径)绘制圆(或椭圆),并随着台风移动而更新。
7. 事件处理:为台风图标或标记点添加点击事件,显示Popup信息框。
8. 多台风处理:循环处理每个台风的数据,分别创建路径、标记、风圈等。
注意:为了性能,我们使用Entity API,并注意在组件销毁时移除所有实体和事件。
参考引用[1]中提到的台风效果,引用[2]中的动态预测路径绘制,引用[3]中的台风范围几何绘制,以及引用[4]中的图层管理思想。
我们将编写一个Vue组件,代码结构如下:
1. 在mounted中初始化Cesium Viewer。
2. 使用watch或方法加载台风数据并渲染。
3. 定义方法绘制单个台风的路径、标记点、风圈等。
4. 定义方法实现路径动画(使用Cesium的时间相关函数或自定义动画)。
5. 定义方法显示弹窗。
由于Cesium在Vue2中的使用,我们需要注意在组件销毁时销毁Viewer。
数据格式示例(单个台风):
```json
{
"name": "台风1",
"data": [
{
"time": "2023-09-15T08:00:00Z",
"lon": 120.0,
"lat": 30.0,
"windSpeed": 45, // 风速(节)
"pressure": 995, // 气压
"radius7": 200, // 7级风圈半径(公里)
"radius10": 100 // 10级风圈半径(公里)
},
// ... 其他时间点
]
}
```
我们将为每个台风创建一个Entity集合,并分别管理。
完整代码示例:</think># Vue2 + Cesium 台风轨迹可视化系统
我将为您提供一个完整的Vue2组件,实现类似浙江水利局台风路径展示效果,包含动态轨迹、位置点叠加、点击弹窗及风圈展示功能,并支持多台风同时显示。
## 核心功能实现思路
1. **动态轨迹动画**:使用Cesium的`SampledPositionProperty`实现路径动画效果
2. **位置点标记**:在关键时间点添加自定义标记
3. **风圈绘制**:根据台风强度绘制不同半径的圆环
4. **信息弹窗**:点击台风时显示详细信息
5. **多台风支持**:使用图层管理实现多台风同时显示
## 完整Vue组件代码
```html
<template>
<div class="typhoon-container">
<div class="map-container" ref="cesiumContainer"></div>
<div class="control-panel">
<h2>台风路径可视化系统</h2>
<div class="typhoon-selector">
<div v-for="(typhoon, index) in typhoons" :key="index" class="typhoon-item">
<input type="checkbox" v-model="typhoon.visible" @change="toggleTyphoon(typhoon)">
<span :style="{color: typhoon.color}">●</span> {{ typhoon.name }}
</div>
</div>
<div class="animation-controls">
<button @click="playAnimation">播放</button>
<button @click="pauseAnimation">暂停</button>
<button @click="resetAnimation">重置</button>
<div class="speed-control">
<label>速度:</label>
<input type="range" min="1" max="10" v-model="animationSpeed">
</div>
</div>
<div v-if="selectedTyphoon" class="info-panel">
<h3>{{ selectedTyphoon.name }} 信息</h3>
<p>当前风速: {{ selectedTyphoon.currentWindSpeed }} kt</p>
<p>当前气压: {{ selectedTyphoon.currentPressure }} hPa</p>
<p>移动速度: {{ selectedTyphoon.movementSpeed }} km/h</p>
<p>移动方向: {{ selectedTyphoon.direction }}</p>
</div>
</div>
</div>
</template>
<script>
import * as Cesium from 'cesium';
export default {
name: 'TyphoonVisualization',
data() {
return {
viewer: null,
typhoons: [],
animationSpeed: 3,
animationInterval: null,
selectedTyphoon: null,
currentTimeIndex: 0
};
},
mounted() {
this.initCesium();
this.loadTyphoonData();
},
beforeDestroy() {
if (this.animationInterval) clearInterval(this.animationInterval);
if (this.viewer) this.viewer.destroy();
},
methods: {
initCesium() {
// 初始化Cesium Viewer
Cesium.Ion.defaultAccessToken = 'YOUR_CESIUM_ACCESS_TOKEN'; // 替换为您的token
this.viewer = new Cesium.Viewer(this.$refs.cesiumContainer, {
terrainProvider: Cesium.createWorldTerrain(),
animation: false,
timeline: false,
geocoder: false,
homeButton: false,
sceneModePicker: false,
baseLayerPicker: false,
navigationHelpButton: false,
fullscreenButton: false,
infoBox: false,
selectionIndicator: false
});
// 设置初始视图位置(中国东部海域)
this.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(125, 25, 15000000),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-90),
roll: 0.0
}
});
// 添加点击事件监听
this.viewer.screenSpaceEventHandler.setInputAction((movement) => {
const pickedObject = this.viewer.scene.pick(movement.position);
if (Cesium.defined(pickedObject) && pickedObject.id) {
this.showTyphoonInfo(pickedObject.id.typhoonData);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
},
loadTyphoonData() {
// 模拟台风数据 - 实际项目中应从API获取
this.typhoons = [
{
id: 'typhoon1',
name: '台风梅花',
color: '#FF5252',
visible: true,
currentWindSpeed: 45,
currentPressure: 965,
movementSpeed: 25,
direction: '西北',
positions: [
{ time: '2023-09-01T00:00:00Z', lon: 135.0, lat: 18.0, windSpeed: 35, pressure: 980, radius7: 250, radius10: 120 },
{ time: '2023-09-02T00:00:00Z', lon: 132.5, lat: 20.5, windSpeed: 40, pressure: 975, radius7: 280, radius10: 150 },
{ time: '2023-09-03T00:00:00Z', lon: 130.0, lat: 23.0, windSpeed: 45, pressure: 965, radius7: 300, radius10: 180 },
{ time: '2023-09-04T00:00:00Z', lon: 127.5, lat: 25.5, windSpeed: 50, pressure: 955, radius7: 320, radius10: 200 },
{ time: '2023-09-05T00:00:00Z', lon: 125.0, lat: 28.0, windSpeed: 55, pressure: 945, radius7: 350, radius10: 220 }
]
},
{
id: 'typhoon2',
name: '台风木兰',
color: '#2196F3',
visible: true,
currentWindSpeed: 40,
currentPressure: 970,
movementSpeed: 20,
direction: '北',
positions: [
{ time: '2023-09-01T00:00:00Z', lon: 140.0, lat: 15.0, windSpeed: 30, pressure: 985, radius7: 200, radius10: 100 },
{ time: '2023-09-02T00:00:00Z', lon: 138.0, lat: 17.5, windSpeed: 35, pressure: 980, radius7: 220, radius10: 120 },
{ time: '2023-09-03T00:00:00Z', lon: 136.0, lat: 20.0, windSpeed: 40, pressure: 970, radius7: 250, radius10: 140 },
{ time: '2023-09-04T00:00:00Z', lon: 134.0, lat: 22.5, windSpeed: 45, pressure: 960, radius7: 280, radius10: 160 },
{ time: '2023-09-05T00:00:00Z', lon: 132.0, lat: 25.0, windSpeed: 50, pressure: 950, radius7: 300, radius10: 180 }
]
}
];
// 初始化绘制所有台风
this.typhoons.forEach(typhoon => {
if (typhoon.visible) {
this.drawTyphoon(typhoon);
}
});
},
drawTyphoon(typhoon) {
// 创建台风路径
const positions = typhoon.positions.map(pos =>
Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat)
);
// 绘制路径线
const pathEntity = this.viewer.entities.add({
name: typhoon.name + '路径',
polyline: {
positions: positions,
width: 4,
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.2,
color: Cesium.Color.fromCssColorString(typhoon.color)
}),
clampToGround: true
}
});
// 添加路径点标记
typhoon.positions.forEach((pos, index) => {
const pointEntity = this.viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat),
point: {
pixelSize: 10,
color: Cesium.Color.WHITE,
outlineColor: Cesium.Color.fromCssColorString(typhoon.color),
outlineWidth: 2
},
label: {
text: `${index + 1}`,
font: '14px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
pixelOffset: new Cesium.Cartesian2(0, -15)
},
typhoonData: typhoon
});
});
// 添加台风当前位置标记(动态)
const positionProperty = new Cesium.SampledPositionProperty();
typhoon.positions.forEach((pos, index) => {
const time = Cesium.JulianDate.fromIso8601(pos.time);
const position = Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat);
positionProperty.addSample(time, position);
});
typhoon.typhoonEntity = this.viewer.entities.add({
name: typhoon.name,
position: positionProperty,
billboard: {
image: this.getTyphoonIcon(typhoon),
width: 48,
height: 48,
scale: 0.8,
rotation: Cesium.Math.toRadians(-45) // 模拟旋转效果
},
typhoonData: typhoon
});
// 绘制风圈
const windCircles = [];
typhoon.positions.forEach(pos => {
const position = Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat);
// 7级风圈
windCircles.push(this.viewer.entities.add({
position: position,
ellipse: {
semiMinorAxis: pos.radius7 * 1000,
semiMajorAxis: pos.radius7 * 1000,
material: Cesium.Color.fromCssColorString(typhoon.color).withAlpha(0.1),
outline: true,
outlineColor: Cesium.Color.fromCssColorString(typhoon.color).withAlpha(0.5),
outlineWidth: 2
},
typhoonData: typhoon
}));
// 10级风圈
windCircles.push(this.viewer.entities.add({
position: position,
ellipse: {
semiMinorAxis: pos.radius10 * 1000,
semiMajorAxis: pos.radius10 * 1000,
material: Cesium.Color.RED.withAlpha(0.1),
outline: true,
outlineColor: Cesium.Color.RED.withAlpha(0.5),
outlineWidth: 2
},
typhoonData: typhoon
}));
});
typhoon.windCircles = windCircles;
},
getTyphoonIcon(typhoon) {
// 创建自定义台风图标(实际项目中可以使用图片)
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const context = canvas.getContext('2d');
// 绘制台风图标
context.beginPath();
context.arc(32, 32, 24, 0, Math.PI * 2);
context.fillStyle = typhoon.color;
context.fill();
context.beginPath();
context.arc(32, 32, 16, 0, Math.PI * 2);
context.fillStyle = '#FFFFFF';
context.fill();
return canvas.toDataURL();
},
toggleTyphoon(typhoon) {
if (typhoon.visible) {
this.drawTyphoon(typhoon);
} else {
this.removeTyphoon(typhoon);
}
},
removeTyphoon(typhoon) {
if (typhoon.typhoonEntity) {
this.viewer.entities.remove(typhoon.typhoonEntity);
}
if (typhoon.windCircles) {
typhoon.windCircles.forEach(circle => {
this.viewer.entities.remove(circle);
});
}
},
playAnimation() {
if (this.animationInterval) clearInterval(this.animationInterval);
const startTime = Cesium.JulianDate.fromIso8601(this.typhoons[0].positions[0].time);
const stopTime = Cesium.JulianDate.fromIso8601(
this.typhoons[0].positions[this.typhoons[0].positions.length - 1].time
);
this.viewer.clock.startTime = startTime.clone();
this.viewer.clock.stopTime = stopTime.clone();
this.viewer.clock.currentTime = startTime.clone();
this.viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
this.viewer.clock.multiplier = this.animationSpeed;
this.viewer.timeline.zoomTo(startTime, stopTime);
this.viewer.clock.shouldAnimate = true;
},
pauseAnimation() {
this.viewer.clock.shouldAnimate = false;
},
resetAnimation() {
this.viewer.clock.shouldAnimate = false;
this.viewer.clock.currentTime = this.viewer.clock.startTime.clone();
},
showTyphoonInfo(typhoon) {
this.selectedTyphoon = typhoon;
// 高亮显示选中的台风
this.viewer.entities.values.forEach(entity => {
if (entity.typhoonData && entity.typhoonData.id === typhoon.id) {
if (entity.point) {
entity.point.pixelSize = 14;
entity.point.outlineWidth = 3;
}
if (entity.billboard) {
entity.billboard.scale = 1.2;
}
} else if (entity.typhoonData) {
if (entity.point) {
entity.point.pixelSize = 10;
entity.point.outlineWidth = 2;
}
if (entity.billboard) {
entity.billboard.scale = 0.8;
}
}
});
}
}
};
</script>
<style scoped>
.typhoon-container {
display: flex;
height: 100vh;
width: 100%;
}
.map-container {
flex: 1;
height: 100%;
}
.control-panel {
width: 300px;
background: rgba(255, 255, 255, 0.9);
padding: 20px;
overflow-y: auto;
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
}
.typhoon-selector {
margin: 20px 0;
}
.typhoon-item {
margin: 10px 0;
padding: 8px;
border-radius: 4px;
background: #f5f5f5;
cursor: pointer;
display: flex;
align-items: center;
}
.typhoon-item:hover {
background: #e0e0e0;
}
.typhoon-item span {
margin-right: 8px;
font-size: 20px;
}
.animation-controls {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 20px 0;
}
.animation-controls button {
padding: 8px 16px;
background: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
flex: 1;
min-width: 70px;
}
.animation-controls button:hover {
background: #1976D2;
}
.speed-control {
display: flex;
align-items: center;
width: 100%;
margin-top: 10px;
}
.speed-control label {
margin-right: 10px;
width: 60px;
}
.speed-control input {
flex: 1;
}
.info-panel {
background: #e3f2fd;
padding: 15px;
border-radius: 4px;
margin-top: 20px;
border-left: 4px solid #2196F3;
}
.info-panel h3 {
margin-top: 0;
color: #0d47a1;
}
.info-panel p {
margin: 8px 0;
}
</style>
```
## 关键功能说明
1. **动态轨迹动画**:
- 使用`SampledPositionProperty`实现台风移动动画
- 通过Cesium时钟控制动画播放速度
- 支持播放、暂停和重置功能
2. **多台风显示**:
- 使用复选框控制每个台风的显示/隐藏
- 每个台风使用不同颜色区分
- 独立管理每个台风的路径、标记和风圈
3. **风圈绘制**:
- 根据台风数据中的半径参数绘制7级和10级风圈
- 使用半透明填充和轮廓线增强可视化效果
- 风圈随台风位置动态变化
4. **信息交互**:
- 点击台风图标或路径点显示详细信息
- 选中的台风会高亮显示
- 右侧面板显示台风详细信息
5. **性能优化**:
- 使用图层管理技术避免重复创建实体
- 仅在需要时显示台风要素
- 合理控制动画帧率
## 使用说明
1. 替换`YOUR_CESIUM_ACCESS_TOKEN`为您的实际Cesium访问令牌
2. 在实际项目中,应从API获取台风数据替换模拟数据
3. 可根据需要调整台风图标样式和风圈颜色
4. 动画速度可通过滑块控制(1-10倍速)
## 相关技术参考
1. Cesium官方文档:[https://cesium.com/learn/cesiumjs/ref-doc/](https://cesium.com/learn/cesiumjs/ref-doc/)
2. Cesium台风可视化示例:[https://sandcastle.cesium.com/](https://sandcastle.cesium.com/)
3. Vue2集成Cesium指南:[https://cesium.com/blog/2019/10/31/integrating-cesium-with-vue/](https://cesium.com/blog/2019/10/31/integrating-cesium-with-vue/)
这个组件提供了完整的台风路径可视化解决方案,您可以将其集成到现有的Vue2项目中,或根据需要进行定制化开发。