12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085 |
- <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>
- <!-- 自定义弹框组件 - POI点 -->
- <div v-if="selectedPoint" class="custom-popup" :style="{
- left: `${popupPosition.x}px`,
- top: `${popupPosition.y}px`
- }">
- <div class="popup-content">
- <h3>{{ selectedPoint.STNM || '未知点' }}</h3>
- <p><strong>经度:</strong> {{ selectedPoint.LGTD }}</p>
- <p><strong>纬度:</strong> {{ selectedPoint.LTTD }}</p>
- <div class="popup-arrow"></div>
- </div>
- </div>
- <!-- 台风路径点信息弹窗 -->
- <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>
- </li>
- <li>
- <span class="dot blue"></span>
- <span>热带风暴</span>
- </li>
- <li>
- <span class="dot yellow"></span>
- <span>强热带风暴</span>
- </li>
- <li>
- <span class="dot orange"></span>
- <span>台风</span>
- </li>
- <li>
- <span class="dot red"></span>
- <span>超强台风</span>
- </li>
- </ul>
- </template>
- <script setup>
- 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'
- import axios from 'axios'
- // 原有变量
- const TDTTK = "d9e7aa2ad204aba6aeedea6f5ab48ed9";
- const selectedPoint = ref(null);
- 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); // 台风显示状态
- const poiEntities = ref([]); // 存储POI实体,用于控制显示隐藏
- // 台风相关变量
- const fengquanLayers = ref([]);
- const myEntityCollection = ref(null);
- const tbentity = ref(null);
- const iii = ref(0);
- const currentPointObj = ref(null);
- let typhoonInterval = null;
- // 台风相关实体集合,用于控制显示隐藏
- const typhoonRelatedEntities = ref({
- paths: [],
- forecasts: [],
- warnings: []
- });
- // 计算页面缩放比例
- const getScaleRatio = () => {
- const designWidth = 1920;
- const designHeight = 1080;
- return Math.min(window.innerWidth / designWidth, window.innerHeight / designHeight);
- };
- // 返回首页视角
- const goToHomeView = () => {
- if (viewer) {
- viewer.camera.flyTo({
- destination: Cesium.Cartesian3.fromDegrees(120.169103, 31.226174, 500000),
- orientation: {
- heading: Cesium.Math.toRadians(0),
- pitch: Cesium.Math.toRadians(-90),
- },
- duration: 1
- });
- }
- };
- // 切换POI点显示/隐藏
- const togglePOIDisplay = () => {
- poiVisible.value = !poiVisible.value;
- poiEntities.value.forEach(entity => {
- if (entity && entity.billboard) {
- entity.billboard.show = poiVisible.value;
- entity.label.show = poiVisible.value;
- }
- });
- };
- // 切换台风显示/隐藏并跳转视角
- 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({
- destination: Cesium.Cartesian3.fromDegrees(120, 20, 4025692.0),
- });
- }
- };
- onMounted(async () => {
- // 设置HTML和body样式确保没有边距
- document.documentElement.style.height = '100%';
- document.body.style.height = '100%';
- document.body.style.margin = '0';
- document.body.style.padding = '0';
- // 初始化Cesium viewer
- viewer = 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.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();
- // 定义距离显示条件和缩放属性
- const distanceDisplayCondition = new Cesium.DistanceDisplayCondition(0, 10000000);
- const pointNearFarScalar = new Cesium.NearFarScalar(10000, 1.0, 1000000, 0.3);
- const labelNearFarScalar = new Cesium.NearFarScalar(10000, 1.0, 400000, 0);
- // 存储实体与数据的映射
- const entityDataMap = new Map();
- JYLData.forEach((item) => {
- const position = Cesium.Cartesian3.fromDegrees(
- parseFloat(item.LGTD),
- parseFloat(item.LTTD)
- );
- const entity = viewer.entities.add({
- position: position,
- billboard: {
- image: '/src/assets/icon/blue.png',
- scale: 0.4,
- color: Cesium.Color.YELLOW,
- horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
- verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
- distanceDisplayCondition: distanceDisplayCondition,
- scaleByDistance: pointNearFarScalar
- },
- label: {
- text: item.STNM || '未知点',
- font: '25px 微软雅黑',
- fillColor: Cesium.Color.WHITE,
- backgroundColor: new Cesium.Color(0.1, 0.1, 0.1, 0.7),
- backgroundPadding: new Cesium.Cartesian2(8, 4),
- showBackground: true,
- cornerRadius: 4,
- outlineColor: Cesium.Color.BLACK,
- outlineWidth: 1,
- horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
- verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
- pixelOffset: new Cesium.Cartesian2(0, -32),
- scale: 1.0,
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
- distanceDisplayCondition: distanceDisplayCondition,
- scaleByDistance: labelNearFarScalar
- },
- id: `point-${item.STCD || item.LGTD + '-' + item.LTTD}`,
- properties: {
- data: item
- }
- });
- entityDataMap.set(entity.id, item);
- poiEntities.value.push(entity);
- });
- // 添加天地图图层
- 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.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.imageryLayers.addImageryProvider(tdtAnnotionLayer);
- // 初始化视图
- viewer.cesiumWidget.creditContainer.style.display = "none";
- viewer.camera.setView({
- destination: Cesium.Cartesian3.fromDegrees(120.169103, 31.226174, 500000),
- orientation: {
- heading: Cesium.Math.toRadians(0),
- pitch: Cesium.Math.toRadians(-90),
- },
- });
- // 点击事件处理
- handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
- handler.setInputAction((click) => {
- const scaleRatio = getScaleRatio();
- const correctedX = click.position.x / scaleRatio;
- 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();
- if (data) {
- selectedPoint.value = data;
- // 计算图标屏幕坐标
- const entityPosition = viewer.scene.cartesianToCanvasCoordinates(pickedObject.id.position._value);
- if (entityPosition) {
- popupPosition.value = {
- x: (entityPosition.x / scaleRatio) - 100,
- y: (entityPosition.y / scaleRatio) - 130
- };
- }
- viewer.flyTo(pickedObject.id, {
- offset: new Cesium.HeadingPitchRange(0, -0.5, 1500)
- });
- }
- } else {
- selectedPoint.value = null;
- }
- }, 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 确保地图正确显示
- handleResize();
- });
- // 初始化台风可视化
- 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();
- };
- // 加载台风数据
- const initPoints = () => {
- // 加载台风JSON数据
- const jsonUrl = new URL('../assets/Data/202508.json', import.meta.url).href;
- axios.get(jsonUrl)
- .then(response => {
- const typhoonData = response.data;
- // 处理台风数据,使用response.data.points
- processPoints(typhoonData.points, typhoonData);
- })
- .catch(error => {
- console.error('加载台风JSON数据失败,使用测试数据', error);
- // 测试数据 - 包含详细台风信息
- 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, typhoonData) => {
- const lineArr = [];
- points.forEach((element, index) => {
- // 强制转换为数字类型
- const lng = Number(element.lng);
- const lat = Number(element.lat);
- let color = Cesium.Color.RED;
- // 根据台风强度设置颜色
- switch (element.strong) {
- case "热带低压":
- color = Cesium.Color.GREEN;
- break;
- case "热带风暴":
- color = Cesium.Color.BLUE;
- break;
- case "强热带风暴":
- color = Cesium.Color.YELLOW;
- break;
- case "台风":
- color = Cesium.Color.fromCssColorString("#FBC712");
- break;
- case "强台风":
- color = Cesium.Color.PLUM;
- break;
- case "超强台风":
- 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: 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: {
- positions: Cesium.Cartesian3.fromDegreesArray(lineArr),
- width: 3,
- clampToGround: true,
- material: Cesium.Color.RED
- }
- });
- 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, typhoonData);
- }
- };
- // 初始化台风预报路径
- const initForeast = (data) => {
- const forecast = data.forecast || [];
- const colorArr = [
- Cesium.Color.fromCssColorString("#2D12FB"),
- Cesium.Color.fromCssColorString("#15E5E7"),
- Cesium.Color.fromCssColorString("#15E74A"),
- Cesium.Color.fromCssColorString("#E76F15"),
- Cesium.Color.fromCssColorString("#15D9E7")
- ];
- forecast.forEach((ele, 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]
- })
- }
- });
- typhoonRelatedEntities.value.forecasts.push(forecastEntity);
- }
- });
- };
- // 初始化警戒线
- const initJJ = () => {
- // 24小时警戒线
- const line24h = viewer.entities.add({
- name: '24小时警戒线',
- polyline: {
- positions: Cesium.Cartesian3.fromDegreesArray([
- 127, 34, 127, 22, 119, 18, 119, 11, 113, 4.5, 105, 0
- ]),
- width: 2,
- material: Cesium.Color.RED,
- clampToGround: true
- }
- });
- typhoonRelatedEntities.value.warnings.push(line24h);
- // 48小时警戒线
- const line48h = viewer.entities.add({
- name: '48小时警戒线',
- polyline: {
- positions: Cesium.Cartesian3.fromDegreesArray([
- 132, 34, 132, 22, 119, 0, 105, 0
- ]),
- width: 2,
- material: Cesium.Color.YELLOW,
- clampToGround: true
- }
- });
- typhoonRelatedEntities.value.warnings.push(line48h);
- // 警戒线标签
- const label24h = viewer.entities.add({
- position: Cesium.Cartesian3.fromDegrees(126.129019, 29.104287),
- label: {
- fillColor: Cesium.Color.RED,
- text: '24小时警戒线',
- font: '14pt monospace'
- }
- });
- typhoonRelatedEntities.value.warnings.push(label24h);
- const label48h = viewer.entities.add({
- position: Cesium.Cartesian3.fromDegrees(132, 20),
- label: {
- fillColor: Cesium.Color.YELLOW,
- text: '48小时警戒线',
- font: '14pt monospace'
- }
- });
- typhoonRelatedEntities.value.warnings.push(label48h);
- };
- // 添加台风动画
- 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];
- currentPointObj.value = {
- lon: Number(currentData.lng),
- lat: Number(currentData.lat),
- circle7: {
- radius1: 350 - kkk,
- radius2: 450 - kkk,
- radius3: 400 - kkk,
- radius4: 350 - kkk
- },
- circle10: {
- radius1: 250 - kkk,
- radius2: 270 - kkk,
- radius3: 250 - kkk,
- radius4: 220 - kkk
- },
- circle12: {
- radius1: 170 - kkk,
- radius2: 150 - kkk,
- radius3: 150 - kkk,
- 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();
- // 只有当台风可见时才添加风圈
- if (typhoonVisible.value) {
- addTyphoonCircle();
- }
- }, 200);
- };
- // 添加台风标记
- const addTB = (typhoonData) => {
- // 尝试使用GIF动画标记
- const SuperGif = window.SuperGif;
- if (typeof SuperGif !== 'function') {
- console.warn('SuperGif未加载成功,使用默认标记');
- 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(
- Number(typhoonData.centerlng || 123.75),
- Number(typhoonData.centerlat || 28.95)
- ),
- billboard: {
- image: new Cesium.CallbackProperty(() => {
- return rub.get_canvas().toDataURL('image/png');
- }, false),
- scale: 0.1
- }
- });
- });
- } catch (error) {
- console.error('GIF处理失败,使用默认标记', error);
- useDefaultMarker(typhoonData);
- }
- };
- img.onerror = () => {
- console.error('GIF加载失败,使用默认标记');
- useDefaultMarker(typhoonData);
- };
- };
- // 使用默认标记
- const useDefaultMarker = (typhoonData) => {
- tbentity.value = viewer.entities.add({
- position: Cesium.Cartesian3.fromDegrees(
- Number(typhoonData.centerlng || 123.75),
- Number(typhoonData.centerlat || 28.95)
- ),
- point: {
- pixelSize: 15,
- color: Cesium.Color.RED,
- outlineColor: Cesium.Color.WHITE,
- outlineWidth: 2
- }
- });
- };
- // 移除台风风圈图层
- const removeTFLayer = () => {
- fengquanLayers.value.forEach(entity => {
- viewer.entities.remove(entity);
- });
- fengquanLayers.value = [];
- };
- // 添加台风风圈
- const addTyphoonCircle = () => {
- if (!currentPointObj.value || !typhoonVisible.value) return;
- const circles = ['circle7', 'circle10', 'circle12'];
- circles.forEach(item => {
- const entity = viewer.entities.add({
- id: `tf_polygon_${item}`,
- polygon: {
- hierarchy: new Cesium.CallbackProperty(() => {
- const points = currentPointObj.value[item]
- ? getTyphoonPolygonPoints(currentPointObj.value, item)
- : [];
- return new Cesium.PolygonHierarchy(
- Cesium.Cartesian3.fromDegreesArray(points)
- );
- }, false),
- material: Cesium.Color.ORANGE.withAlpha(0.05),
- extrudedHeight: 1000,
- outline: true,
- outlineColor: Cesium.Color.ORANGE,
- outlineWidth: 2
- },
- polyline: {
- positions: new Cesium.CallbackProperty(() => {
- const points = currentPointObj.value[item]
- ? getTyphoonPolygonPoints(currentPointObj.value, item)
- : [];
- return Cesium.Cartesian3.fromDegreesArray(points);
- }, false),
- material: Cesium.Color.ORANGE,
- width: 2,
- height: 1000
- }
- });
- fengquanLayers.value.push(entity);
- });
- };
- // 生成台风风圈多边形点
- const getTyphoonPolygonPoints = (pointObj, cNum) => {
- const points = [];
- const center = [Number(pointObj.lon), Number(pointObj.lat)];
- const radiusList = [
- pointObj[cNum].radius1,
- pointObj[cNum].radius2,
- pointObj[cNum].radius3,
- pointObj[cNum].radius4
- ];
- 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);
- const cos = Math.cos((angle * Math.PI) / 180);
- 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();
- }
- });
- </script>
- <style scoped>
- /* 控制按钮样式 */
- .control-buttons {
- position: absolute;
- top: 200px;
- left: 150px;
- z-index: 100;
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .control-btn {
- width: 40px;
- height: 40px;
- border-radius: 50%;
- background-color: rgba(255, 255, 255, 0.9);
- border: none;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: all 0.2s ease;
- color: #333;
- }
- .control-btn:hover {
- background-color: white;
- transform: scale(1.1);
- }
- .control-btn .active {
- color: #1E88E5;
- }
- .custom-popup,
- .typhoon-popup {
- position: absolute;
- z-index: 1000;
- display: block;
- pointer-events: none;
- }
- .popup-content {
- background-color: white;
- border-radius: 5px;
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
- padding: 10px 15px;
- width: 240px;
- pointer-events: all;
- }
- .popup-arrow {
- position: absolute;
- bottom: -10px;
- left: 50%;
- transform: translateX(-50%);
- width: 0;
- height: 0;
- border-left: 10px solid transparent;
- border-right: 10px solid transparent;
- border-top: 10px solid white;
- }
- .popup-content h3 {
- margin-top: 0;
- margin-bottom: 8px;
- color: #333;
- font-size: 16px;
- }
- .popup-content p {
- margin: 5px 0;
- color: #666;
- font-size: 14px;
- }
- /* 台风图例样式 */
- .legend {
- position: absolute;
- z-index: 100;
- bottom: 30px;
- right: 10px;
- color: #fff;
- background: rgba(0, 0, 0, 0.8);
- list-style: none;
- padding: 10px 15px;
- border-radius: 4px;
- margin: 0;
- transition: opacity 0.3s ease, transform 0.3s ease;
- }
- .legend li {
- display: flex;
- align-items: center;
- margin: 5px 0;
- }
- .dot {
- border-radius: 50%;
- width: 10px;
- height: 10px;
- display: inline-block;
- margin-right: 8px;
- }
- .green {
- background: green;
- }
- .blue {
- background: blue;
- }
- .yellow {
- background: yellow;
- }
- .orange {
- background: #FBC712;
- }
- .red {
- background: red;
- }
- </style>
-
|