vue3+cesium.加载无人机模型,并播放轨迹飞行动画.
轨迹全部显示,也能改为经过显示.至于叠加显示的功能,暂时还没时间做,暂时就这么处理了.
时间轴和动画控件css隐藏了.因为后端没有返回轨迹点时间,有需要的可以打开
<template>
<div
v-show="modalVisible"
class="dialog fixed w-1200px h-720px left-55% top-50% -translate-x-50% -translate-y-50% z-12"
>
<CustomModal
v-model:visible="modalVisible"
:show-close-btn="true"
:overlay-opacity="0.92"
blur-px="5px"
@close="closeModal"
>
<div id="track-map-container" class="w-full h-full"></div>
</CustomModal>
<div id="credit"></div>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue';
import * as Cesium from 'cesium';
import ChongQingCity from '@/assets/data/重庆市.json';
import { addTMap } from '~/src/utils';
import { getPosition } from '~/src/service';
const modalVisible = ref(false);
let viewer: any = null;
const animateEntity: any = ref(null);
const clockTickListener: any = ref(null);
const polylineList = [
{
height: '2000',
lng: '106.3874',
lat: '29.491311',
dynamicTime: '1708486991000'
},
{
height: '2000',
lng: '106.447766',
lat: '29.506903',
dynamicTime: '1708490591000'
},
{
height: '2000',
lng: '106.544352',
lat: '29.576786',
dynamicTime: '1708494191000'
}
];
// cesium时钟时间格式化函数
function CesiumTimeFormatter(datetime: any, viewModel: any) {
const julianDT = new Cesium.JulianDate();
Cesium.JulianDate.addHours(datetime, 8, julianDT);
const gregorianDT = Cesium.JulianDate.toGregorianDate(julianDT);
const hour = `${gregorianDT.hour}`;
const minute = `${gregorianDT.minute}`;
const second = `${gregorianDT.second}`;
return `${hour.padStart(2, '0')}:${minute.padStart(2, '0')}:${second.padStart(2, '0')}`;
}
// cesium时钟日期格式化函数
function CesiumDateFormatter(datetime, viewModel, ignoredate) {
const julianDT = new Cesium.JulianDate();
Cesium.JulianDate.addHours(datetime, 8, julianDT);
const gregorianDT = Cesium.JulianDate.toGregorianDate(julianDT);
return `${gregorianDT.year}年${gregorianDT.month}月${gregorianDT.day}日`;
}
// cesium时间轴格式化函数
function CesiumDateTimeFormatter(datetime, viewModel, ignoredate) {
const julianDT = new Cesium.JulianDate();
Cesium.JulianDate.addHours(datetime, 8, julianDT);
const gregorianDT = Cesium.JulianDate.toGregorianDate(julianDT);
const hour = `${gregorianDT.hour}`;
const minute = `${gregorianDT.minute}`;
return `${gregorianDT.day}日${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`;
}
const initMap = () => {
return new Promise((resolve, _) => {
if (!viewer) {
Cesium.Ion.defaultAccessToken =
'------------------申请的token-----------------';
// viewer是所有Api的开始
viewer = new Cesium.Viewer('track-map-container', {
animation: true, // 是否显示动画控件
shouldAnimate: true,
// credit: false, //是否显示版权信息
creditContainer: 'credit',
baseLayerPicker: false, // 是否显示图层选择控件
geocoder: false, // 是否显示地名查找控件
timeline: true, // 是否显示时间线控件
sceneModePicker: false, // 是否显示投影方式控件
navigationHelpButton: false, // 是否显示帮助信息控件
fullscreenButton: false, // 是否显示全屏按钮
infoBox: false, // 是否显示点击要素之后显示的信息
homeButton: false // 是否显示Home按钮
// terrainProvider: Cesium.createWorldTerrain(), // 是否显示地形
// imageryProvider: new Cesium.UrlTemplateImageryProvider({
// url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
// })
});
addTMap(viewer, 'img');
addTMap(viewer, 'cia');
// addAMapImagery(viewer, 'img'); // 加载高德影像底图
// addAMapImagery(viewer, 'marker'); // 加载高德标注图层
// 添加错误处理和状态检查
console.log('开始加载地形...');
const loadTerrain = async () => {
// 添加超时处理函数
const timeoutPromise = (promise: Promise<any>, timeout: number) => {
return Promise.race([
promise,
new Promise((_, reject) => {
setTimeout(() => reject(new Error('加载超时')), timeout);
})
]);
};
try {
const terrainProviderLocal = new Cesium.CesiumTerrainProvider({
// url: 'http://localhost:8090'
// url: 'http://localhost:8091'
url: '/terrain'
// url: import.meta.env.VITE_TERRAIN_URL
});
// 先尝试加载本地地形
await timeoutPromise(terrainProviderLocal.readyPromise, 3000);
if (terrainProviderLocal.ready) {
viewer.scene.terrainProvider = terrainProviderLocal;
console.log('本地地形加载成功terrainProvider', terrainProviderLocal);
} else {
throw new Error('本地地形未就绪');
}
} catch (localError) {
console.warn('本地地形加载失败:', localError);
} finally {
console.log('地形加载流程完成');
}
};
viewer.scene.globe.depthTestAgainstTerrain = true;
loadBoundary();
loadTerrain();
Cesium.Timeline.prototype.makeLabel = CesiumDateTimeFormatter;
viewer.animation.viewModel.dateFormatter = CesiumDateFormatter;
viewer.animation.viewModel.timeFormatter = CesiumTimeFormatter;
resolve('初始化完成');
} else {
resolve('已经初始化');
}
});
};
// 绘制边界
function drawBoundary(data: any) {
// 要绘制的区域经纬度数据
const checkLatlng = data.features[0].geometry.coordinates[0][0];
removeBoundary();
// 中国边界
const extent = { xmin: 73.0, xmax: 136.0, ymin: 3.0, ymax: 59.0 };
const geojson = {
type: 'Feature',
geometry: {
type: 'MultiPolygon',
coordinates: [
[
[
[extent.xmin, extent.ymin],
[extent.xmax, extent.ymin],
[extent.xmax, extent.ymax],
[extent.xmin, extent.ymax],
[extent.xmin, extent.ymin]
],
checkLatlng
]
]
}
};
const geoPromise = Cesium.GeoJsonDataSource.load(geojson, {
stroke: Cesium.Color.fromCssColorString('#39E09B').withAlpha(0.8),
fill: Cesium.Color.fromCssColorString('rgb(2,26,79)').withAlpha(0.6),
strokeWidth: 10,
clampToGround: true
});
geoPromise.then(dataSource => {
// 添加geojson
viewer.dataSources.add(dataSource);
// 给geojson命名
dataSource.name = 'geojsonBoundary';
// 视角跳转至geojson
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(106.547514, 29.578797, 50000),
duration: 0
});
});
}
// 清除绘制
function removeBoundary() {
viewer.dataSources.remove(viewer.dataSources.getByName('geojsonBoundary')[0]);
}
// 获取geojson数据
function loadBoundary() {
drawBoundary(ChongQingCity);
}
function loadTrackData(jobId: string) {
console.log('加载轨迹数据');
getPosition(jobId).then((res: any) => {
console.log('%c[🚀🚀🚀 table-action-modal.vue - line 538]res', 'color: red;font-size:x-large', res);
nextTick(() => {
// 更改polylineList的dynamicTime,每个为当前时间多1000000*i
polylineList.forEach((item: any, index: number) => {
item.dynamicTime = new Date().getTime() + index * 1000000;
});
// newAddPolyline(res);
newAddPolyline(polylineList);
addClockTickListener();
});
});
}
/**
* 添加轨迹线
* @param trackList 坐标数组
*/
function newAddPolyline(trackList: any) {
const startTime = new Date(Number(trackList[0].dynamicTime));
const endTime = new Date(Number(trackList[trackList.length - 1].dynamicTime));
const start = Cesium.JulianDate.fromDate(startTime);
const stop = Cesium.JulianDate.fromDate(endTime);
// console.log('开始时间1:', startTime);
// console.log('结束时间1:', endTime);
// console.log('开始时间2:', start);
// console.log('结束时间2:', stop);
const positionProperty = new Cesium.SampledPositionProperty();
positionProperty.setInterpolationOptions({
interpolationDegree: 1,
interpolationAlgorithm: Cesium.LagrangePolynomialApproximation
});
for (let i = 0; i < trackList.length; i++) {
const { lng, lat, height, dynamicTime } = trackList[i];
const position = Cesium.Cartesian3.fromDegrees(Number(lng), Number(lat), Number(height));
const time = new Date(Number(dynamicTime));
const csTime = Cesium.JulianDate.fromDate(time);
positionProperty.addSample(csTime, position);
}
animateEntity.value = viewer.entities.add({
name: 'DroneM30',
availability: new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({ start, stop })]),
position: positionProperty,
orientation: new Cesium.VelocityOrientationProperty(positionProperty),
// 轨迹路径
path: {
resolution: 1,
width: 2,
material: Cesium.Color.RED
},
// path: {
// leadTime: 1,
// resolution: 1,
// material: new Cesium.PolylineGlowMaterialProperty({
// glowPower: 0.1,
// color: Cesium.Color.GREEN
// }),
// width: 10
// },
// path: {
// leadTime: 1,
// resolution: 1,
// material: new Cesium.PolylineGlowMaterialProperty({
// glowPower: 0.1,
// color: viewer.trackedEntity === animateEntity.value ? Cesium.Color.GREEN : Cesium.Color.RED // 根据条件设置颜色
// }),
// width: 10
// },
// 使用模型
model: {
show: true,
uri: '/dji_m300_drone_animated.glb', // 使用 require 来加载模型文件
minimumPixelSize: 164,
maximumScale: 20000
}
// ,
// label: {
// text: '无人机M30',
// font: '500 24px Helvetica',
// scale: 1,
// style: Cesium.LabelStyle.FILL,
// fillColor: Cesium.Color.RED,
// pixelOffset: new Cesium.Cartesian2(0, -40),
// showBackground: false
// }
});
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // Loop at the end
viewer.clock.multiplier = 100;
viewer.timeline.zoomTo(start, stop);
// 在动画开始前取消对实体的追踪
viewer.trackedEntity = undefined;
viewer.trackedEntity = animateEntity.value;
viewer.timeline.shouldAnimate = true;
viewer.clock.shouldAnimate = true;
const { lng, lat, height } = trackList[0];
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(Number(lng), Number(lat), Number(height) + 10000),
duration: 0
});
}
function setEntityPosition() {
// 获取当前时间
const currentTime = viewer.clock.currentTime;
// 获取实体的位置
const position = animateEntity.value.position.getValue(currentTime);
// position 是一个 Cartesian3 对象,你可以使用 Cesium.Cartographic.fromCartesian 来将其转换为经纬度
const cartographic = Cesium.Cartographic.fromCartesian(position);
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
const height = Cesium.Math.toDegrees(cartographic.height);
// console.log(`实体的当前位置是:经度 ${longitude},纬度 ${latitude}`);
// 创建一个HeadingPitchRange实例,设置视角为垂直向下
const hpr = new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 1000);
// 设置相机高度
const cameraHeight = 200000; // 你可以修改这个值来设置相机高度
// 获取trackEntity的position
// const trackEntityPosition = animateEntity.value.position.getValue(currentTime);
// 更改trackEntityPosition的高度
// trackEntityPosition.z += 15000;
// 创建一个新的Cartesian3实例,设置z坐标为相机高度
// const newPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, cameraHeight);
const newPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);
// console.log('%c[🚀🚀🚀 trackMap.vue - line 278]height', 'color: red;font-size:x-large', height);
// 调整相机视角,改为非锁定视角
// viewer.camera.lookAt(newPosition, hpr);
// viewer.camera.flyTo({
// destination: newPosition,
// orientation: {
// heading: 0,
// pitch: -90,
// roll: 0
// }
// });
}
function addClockTickListener() {
clockTickListener.value = (clock: { shouldAnimate: any }) => {
if (clock.shouldAnimate) {
// 动画正在播放
// viewer.trackedEntity = animateEntity.value;
// console.log('动画正在播放');
viewer.trackedEntity = undefined; // 确保不锁定相机
// setEntityPosition();
} else {
// 动画已暂停
viewer.trackedEntity = undefined;
// console.log('动画已暂停');
}
};
viewer.clock.onTick.addEventListener(clockTickListener.value);
}
function removeClockTickListener() {
if (clockTickListener.value) {
viewer.clock.onTick.removeEventListener(clockTickListener.value);
clockTickListener.value = null;
}
}
function closeModal() {
removeClockTickListener();
// 停止动画实体
viewer.entities.remove(animateEntity.value);
// 停止时间线
viewer.timeline.stop();
// 停止时间线动画
viewer.timeline.shouldAnimate = false;
// 停止动画实体
viewer.trackedEntity = undefined;
modalVisible.value = false;
}
function openModal(jobId: string) {
modalVisible.value = true;
nextTick(() => {
initMap().then(res => {
console.log(res);
loadTrackData(jobId);
});
});
}
defineExpose({
openModal
});
</script>
<style lang="scss">
.track-map-box {
width: 100%;
height: 100%;
}
#track-map-container {
.cesium-viewer-animationContainer {
display: none;
}
.cesium-viewer-timelineContainer {
display: none;
}
}
</style>