当前位置: 首页 > java >正文

Cesium Entity动态更新

Cesium Entity动态更新技术全解析

🔄 Entity属性动态更新流程图

Entity属性更新方式
直接修改属性
使用CallbackProperty
使用SampledPositionProperty
实时修改单个值
定时器批量更新
基于时间的回调
基于其他参数的回调
关键帧动画
轨迹回放

📊 动态更新Entity的三大方法

1️⃣ 直接修改属性值

最简单的方式是直接修改Entity的属性值:

// 创建一个点实体
const point = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(116.39, 39.9),point: {pixelSize: 10,color: Cesium.Color.RED}
});// ⭐直接修改属性
function updatePoint() {// 修改位置point.position = Cesium.Cartesian3.fromDegrees(116.39 + Math.random() * 0.1,  // 随机经度变化39.9 + Math.random() * 0.1,    // 随机纬度变化Math.random() * 500            // 随机高度);// 修改颜色point.point.color = Cesium.Color.fromRandom({alpha: 1.0});// 修改大小point.point.pixelSize = 5 + Math.random() * 15;
}// 每秒更新一次
setInterval(updatePoint, 1000);

适用场景

  • 简单的数值变化
  • 低频率更新
  • 基于外部数据源(如API)的更新

2️⃣ CallbackProperty动态计算

CallbackProperty允许通过回调函数动态计算属性值:

// ⭐使用CallbackProperty创建动态属性
const movingPoint = viewer.entities.add({// 动态位置 - 圆周运动position: new Cesium.CallbackProperty(function(time) {// time参数是当前Cesium时间const seconds = Cesium.JulianDate.secondsDifference(time, viewer.clock.startTime);const radiusMeters = 1000;  // 运动半径(米)// 计算圆周位置const longitude = 116.39 + (radiusMeters / 111000) * Math.cos(seconds * 0.1);const latitude = 39.9 + (radiusMeters / 111000) * Math.sin(seconds * 0.1);return Cesium.Cartesian3.fromDegrees(longitude, latitude, 100);}, false),  // false表示不缓存值// 动态颜色 - 彩虹色变化point: {pixelSize: 15,color: new Cesium.CallbackProperty(function(time) {const seconds = Cesium.JulianDate.secondsDifference(time, viewer.clock.startTime);// HSL颜色循环return Cesium.Color.fromHsl(seconds * 0.1 % 1.0, 1.0, 0.5, 1.0);}, false)}
});

动态大小示例

// ⭐使用CallbackProperty创建呼吸效果
const pulsingPoint = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(116.39, 39.9),point: {pixelSize: new Cesium.CallbackProperty(function(time) {const seconds = Cesium.JulianDate.secondsDifference(time, viewer.clock.startTime);// 大小在8-20之间脉动return 14 + 6 * Math.sin(seconds * 2.0);}, false),color: Cesium.Color.RED}
});

动态材质示例

// ⭐动态线材质 - 流动效果
const flowingLine = viewer.entities.add({polyline: {positions: Cesium.Cartesian3.fromDegreesArrayHeights([116.38, 39.9, 0,116.39, 39.91, 100,116.40, 39.9, 0]),width: 10,material: new Cesium.PolylineGlowMaterialProperty({glowPower: new Cesium.CallbackProperty(function(time) {const seconds = Cesium.JulianDate.secondsDifference(time, viewer.clock.startTime);// 发光强度变化return 0.1 + 0.4 * Math.abs(Math.sin(seconds));}, false),color: Cesium.Color.BLUE})}
});

适用场景

  • 基于时间的连续动画
  • 复杂的数学计算生成的属性
  • 需要与Cesium时钟同步的效果

3️⃣ SampledPositionProperty关键帧动画

SampledPositionProperty用于创建基于关键帧的位置动画:

// ⭐创建采样位置属性
const positionProperty = new Cesium.SampledPositionProperty();// 获取当前时间作为起点
const start = Cesium.JulianDate.fromDate(new Date());
// 设置结束时间为60秒后
const stop = Cesium.JulianDate.addSeconds(start, 60, new Cesium.JulianDate());// 设置viewer时钟范围
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // 循环播放
viewer.clock.multiplier = 1; // 播放速度// ⭐添加位置关键帧 - 创建路径
for (let i = 0; i <= 60; i++) {const time = Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate());// 创建一条正弦波形路径const longitude = 116.39 + (i * 0.001);const latitude = 39.9 + 0.01 * Math.sin(i * 0.1);const height = 100 + 50 * Math.cos(i * 0.1);const position = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);// 添加时间点和位置positionProperty.addSample(time, position);
}// 创建路径实体
const pathEntity = viewer.entities.add({position: positionProperty,// 自动沿路径方向旋转orientation: new Cesium.VelocityOrientationProperty(positionProperty),// 视觉表示 - 3D模型model: {uri: './models/aircraft.glb',minimumPixelSize: 64,maximumScale: 20000},// 路径可视化path: {resolution: 1,material: new Cesium.PolylineGlowMaterialProperty({glowPower: 0.1,color: Cesium.Color.YELLOW}),width: 10}
});// 启动动画
viewer.timeline.zoomTo(start, stop);

基于CSV/JSON数据的轨迹回放

// ⭐加载GPS轨迹数据并创建动画
async function loadAndAnimateTrack(dataUrl) {try {// 获取数据const response = await fetch(dataUrl);const trackData = await response.json();// 创建采样位置属性const positionProperty = new Cesium.SampledPositionProperty();// 处理时间范围const startTime = Cesium.JulianDate.fromIso8601(trackData[0].timestamp);const stopTime = Cesium.JulianDate.fromIso8601(trackData[trackData.length - 1].timestamp);// 添加采样点trackData.forEach(point => {const time = Cesium.JulianDate.fromIso8601(point.timestamp);const position = Cesium.Cartesian3.fromDegrees(point.longitude,point.latitude,point.altitude || 0);positionProperty.addSample(time, position);});// 设置插值选项 - 平滑曲线positionProperty.setInterpolationOptions({interpolationDegree: 3,interpolationAlgorithm: Cesium.LagrangePolynomialApproximation});// 创建实体const entity = viewer.entities.add({availability: new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({start: startTime,stop: stopTime})]),position: positionProperty,orientation: new Cesium.VelocityOrientationProperty(positionProperty),model: {uri: './models/vehicle.glb',minimumPixelSize: 64},path: {resolution: 1,material: new Cesium.PolylineGlowMaterialProperty({glowPower: 0.1,color: Cesium.Color.YELLOW}),width: 10}});// 设置时钟viewer.clock.startTime = startTime.clone();viewer.clock.stopTime = stopTime.clone();viewer.clock.currentTime = startTime.clone();viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;viewer.clock.multiplier = 5;// 聚焦到实体viewer.trackedEntity = entity;return entity;} catch (error) {console.error('加载轨迹失败:', error);}
}// 使用示例
loadAndAnimateTrack('./data/vehicle_track.json');

适用场景

  • 预定义轨迹动画
  • GPS轨迹回放
  • 飞行路径可视化
  • 时间序列数据可视化

🎭 高级动态效果技巧

1. 动态颜色渐变效果

// ⭐创建颜色渐变效果
function createColorTransition(entity, fromColor, toColor, duration) {const startTime = Cesium.JulianDate.fromDate(new Date());const endTime = Cesium.JulianDate.addSeconds(startTime, duration, new Cesium.JulianDate());entity.point.color = new Cesium.CallbackProperty(function(time) {// 计算当前时间进度 (0.0-1.0)const t = Cesium.JulianDate.secondsDifference(time, startTime) / duration;// 限制在0-1范围内const normalizedT = Math.min(1.0, Math.max(0.0, t));// 线性插值颜色return Cesium.Color.lerp(fromColor, toColor, normalizedT, new Cesium.Color());}, false);// 到达终点后设置为最终颜色setTimeout(() => {entity.point.color = toColor;}, duration * 1000);
}// 使用示例
const point = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(116.39, 39.9),point: {pixelSize: 20,color: Cesium.Color.BLUE}
});// 颜色从蓝色渐变到红色,持续3秒
createColorTransition(point, Cesium.Color.BLUE, Cesium.Color.RED, 3.0);

2. 属性动画管理器

// ⭐创建属性动画管理器
class EntityAnimator {constructor(viewer) {this.viewer = viewer;this.animations = new Map();this.nextId = 1;}// 添加动画animateProperty(entity, propertyPath, startValue, endValue, duration, easingFunction) {const startTime = Cesium.JulianDate.fromDate(new Date());const endTime = Cesium.JulianDate.addSeconds(startTime, duration, new Cesium.JulianDate());// 解析属性路径 (例如: "point.pixelSize")const parts = propertyPath.split('.');let targetObj = entity;for (let i = 0; i < parts.length - 1; i++) {targetObj = targetObj[parts[i]];}const propertyName = parts[parts.length - 1];// 保存原始值const originalValue = targetObj[propertyName];// 创建动画IDconst animationId = this.nextId++;// 创建回调属性const callbackProperty = new Cesium.CallbackProperty((time) => {// 检查动画是否已被移除if (!this.animations.has(animationId)) {return endValue;}// 计算进度const t = Cesium.JulianDate.secondsDifference(time, startTime) / duration;const normalizedT = Math.min(1.0, Math.max(0.0, t));// 应用缓动函数(如果提供)const easedT = easingFunction ? easingFunction(normalizedT) : normalizedT;// 值插值if (startValue instanceof Cesium.Cartesian3) {return Cesium.Cartesian3.lerp(startValue, endValue, easedT, new Cesium.Cartesian3());} else if (startValue instanceof Cesium.Color) {return Cesium.Color.lerp(startValue, endValue, easedT, new Cesium.Color());} else {// 数值插值return startValue + (endValue - startValue) * easedT;}}, false);// 保存动画信息this.animations.set(animationId, {entity,propertyPath,originalValue,callbackProperty,endTime});// 设置回调属性targetObj[propertyName] = callbackProperty;// 设置自动清理setTimeout(() => {this.stopAnimation(animationId);}, duration * 1000);return animationId;}// 停止动画stopAnimation(animationId) {if (this.animations.has(animationId)) {const animation = this.animations.get(animationId);// 解析属性路径const parts = animation.propertyPath.split('.');let targetObj = animation.entity;for (let i = 0; i < parts.length - 1; i++) {targetObj = targetObj[parts[i]];}const propertyName = parts[parts.length - 1];// 设置最终值if (animation.callbackProperty) {const finalValue = animation.callbackProperty.getValue(this.viewer.clock.currentTime);targetObj[propertyName] = finalValue;}// 移除动画this.animations.delete(animationId);}}// 缓动函数static easing = {linear: t => t,easeInQuad: t => t * t,easeOutQuad: t => t * (2 - t),easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,easeInCubic: t => t * t * t,easeOutCubic: t => (--t) * t * t + 1,easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1}
}// 使用示例
const animator = new EntityAnimator(viewer);const point = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(116.39, 39.9),point: {pixelSize: 10,color: Cesium.Color.BLUE}
});// 大小动画 - 使用缓出效果
animator.animateProperty(point,'point.pixelSize',10,30,2.0,EntityAnimator.easing.easeOutQuad
);// 颜色动画 - 使用缓入缓出效果
animator.animateProperty(point,'point.color',Cesium.Color.BLUE,Cesium.Color.RED,3.0,EntityAnimator.easing.easeInOutCubic
);// 位置动画
const startPosition = Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0);
const endPosition = Cesium.Cartesian3.fromDegrees(116.40, 39.95, 500);
animator.animateProperty(point,'position',startPosition,endPosition,5.0,EntityAnimator.easing.easeInOutQuad
);

3. 动态绑定多个实体

// ⭐创建互相绑定的实体
function createLinkedEntities() {// 主实体 - 可拖动点const mainPoint = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 100),point: {pixelSize: 15,color: Cesium.Color.RED}});// 从属实体 - 跟随主实体的椭圆const circle = viewer.entities.add({position: new Cesium.CallbackProperty(() => {// 获取主实体当前位置return mainPoint.position.getValue(viewer.clock.currentTime);}, false),ellipse: {semiMajorAxis: 300,semiMinorAxis: 300,material: Cesium.Color.RED.withAlpha(0.3),height: new Cesium.CallbackProperty(() => {// 获取主实体当前高度const position = mainPoint.position.getValue(viewer.clock.currentTime);const cartographic = Cesium.Cartographic.fromCartesian(position);return cartographic.height;}, false)}});// 连接线const connectorLine = viewer.entities.add({polyline: {positions: new Cesium.CallbackProperty(() => {const mainPosition = mainPoint.position.getValue(viewer.clock.currentTime);// 获取下方地面点const cartographic = Cesium.Cartographic.fromCartesian(mainPosition);const groundPosition = Cesium.Cartesian3.fromRadians(cartographic.longitude,cartographic.latitude,0);return [groundPosition, mainPosition];}, false),width: 2,material: Cesium.Color.WHITE}});// 地面标记const groundMarker = viewer.entities.add({position: new Cesium.CallbackProperty(() => {const mainPosition = mainPoint.position.getValue(viewer.clock.currentTime);const cartographic = Cesium.Cartographic.fromCartesian(mainPosition);return Cesium.Cartesian3.fromRadians(cartographic.longitude,cartographic.latitude,0);}, false),ellipse: {semiMajorAxis: 300,semiMinorAxis: 300,material: Cesium.Color.RED.withAlpha(0.3),heightReference: Cesium.HeightReference.CLAMP_TO_GROUND}});return {mainPoint,circle,connectorLine,groundMarker};
}// 创建并绑定交互
const linkedEntities = createLinkedEntities();

4. 使用场景渲染事件

// ⭐使用场景渲染事件更新属性
function animateUsingRenderEvent() {const point = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0),point: {pixelSize: 15,color: Cesium.Color.YELLOW}});// 使用preRender事件viewer.scene.preRender.addEventListener((scene, time) => {// 获取当前时间(秒)const seconds = Cesium.JulianDate.secondsDifference(time, viewer.clock.startTime);// 更新位置 - 螺旋上升const angle = seconds * 0.3;const radius = 0.001 * (1 + seconds * 0.01);const height = seconds * 10;const longitude = 116.39 + radius * Math.cos(angle);const latitude = 39.9 + radius * Math.sin(angle);point.position = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);// 更新颜色 - 彩虹循环point.point.color = Cesium.Color.fromHsl((seconds * 0.1) % 1.0,  // 色相循环1.0,                    // 饱和度0.5,                    // 亮度1.0                     // 透明度);});return point;
}// 创建动画
const animatedPoint = animateUsingRenderEvent();

5. 属性插值与过渡

// ⭐创建SampledProperty属性插值
function createInterpolatedProperty() {// 创建采样属性 - 比如半径const radiusProperty = new Cesium.SampledProperty(Number);// 设置插值选项radiusProperty.setInterpolationOptions({interpolationDegree: 3,  // 三次多项式插值interpolationAlgorithm: Cesium.HermitePolynomialApproximation  // 埃尔米特插值});// 获取起始时间const start = Cesium.JulianDate.fromDate(new Date());// 添加关键帧for (let i = 0; i <= 20; i++) {const time = Cesium.JulianDate.addSeconds(start, i * 3, new Cesium.JulianDate());// 随机半径值(但确保有起伏变化)let radius;if (i % 2 === 0) {radius = 300 + Math.random() * 200;} else {radius = 700 + Math.random() * 300;}radiusProperty.addSample(time, radius);}// 使用该属性创建圆const circle = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(116.39, 39.9),ellipse: {semiMajorAxis: radiusProperty,semiMinorAxis: radiusProperty,material: Cesium.Color.BLUE.withAlpha(0.5),outline: true,outlineColor: Cesium.Color.WHITE}});// 设置时钟const endTime = Cesium.JulianDate.addSeconds(start, 60, new Cesium.JulianDate());viewer.clock.startTime = start.clone();viewer.clock.stopTime = endTime.clone();viewer.clock.currentTime = start.clone();viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;return circle;
}// 创建插值动画
const interpolatedCircle = createInterpolatedProperty();

6. 动态材质更新

// ⭐创建动态自定义材质
function createDynamicMaterial() {// 自定义材质 - 扩散波const customMaterial = new Cesium.Material({fabric: {type: 'DynamicWave',uniforms: {color: new Cesium.Color(0.0, 0.5, 1.0, 1.0),time: 0,count: 3,speed: 3.0,gradient: 0.3},source: `czm_material czm_getMaterial(czm_materialInput materialInput) {czm_material material = czm_getDefaultMaterial(materialInput);float distance = length(materialInput.st - 0.5);float waves = czm_pi * count * 2.0;float phase = fract(time * speed * 0.1);float ring = abs(sin(distance * waves - phase * 6.28));ring = pow(ring, gradient);material.diffuse = color.rgb;material.alpha = ring * color.a * (1.0 - smoothstep(0.4, 0.5, distance));return material;}`},translucent: function() {return true;}});// 创建使用该材质的圆形const waveCircle = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(116.39, 39.9),ellipse: {semiMajorAxis: 1000,semiMinorAxis: 1000,material: customMaterial,heightReference: Cesium.HeightReference.CLAMP_TO_GROUND}});// 更新材质时间参数viewer.scene.preRender.addEventListener((scene, time) => {const seconds = Cesium.JulianDate.secondsDifference(time, viewer.clock.startTime);customMaterial.uniforms.time = seconds;});return waveCircle;
}// 创建动态材质
const waveMaterial = createDynamicMaterial();

🧪 实际应用案例

1. 实时数据更新 - 气象站

// ⭐实时气象站数据更新
function createWeatherStations(stationData) {const stations = {};// 为每个站点创建实体stationData.forEach(station => {// 创建站点实体const entity = viewer.entities.add({id: `station-${station.id}`,position: Cesium.Cartesian3.fromDegrees(station.longitude, station.latitude),billboard: {image: './images/weather-station.png',scale: 0.5,verticalOrigin: Cesium.VerticalOrigin.BOTTOM},label: {text: station.name,font: '12px sans-serif',style: Cesium.LabelStyle.FILL_AND_OUTLINE,outlineWidth: 2,verticalOrigin: Cesium.VerticalOrigin.BOTTOM,pixelOffset: new Cesium.Cartesian2(0, -36)},// 自定义属性properties: {stationId: station.id,temperature: 0,humidity: 0,windSpeed: 0,precipitation: 0,lastUpdate: ''}});// 添加温度可视化const tempCircle = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(station.longitude, station.latitude),ellipse: {semiMajorAxis: 500,semiMinorAxis: 500,material: new Cesium.ColorMaterialProperty(new Cesium.CallbackProperty(() => {// 基于温度变化颜色const temp = entity.properties.temperature.getValue();if (temp < 0) {// 蓝色 - 寒冷return Cesium.Color.fromCssColorString('rgba(0, 50, 255, 0.3)');} else if (temp < 15) {// 青色 - 凉爽return Cesium.Color.fromCssColorString('rgba(0, 255, 255, 0.3)');} else if (temp < 25) {// 绿色 - 温和return Cesium.Color.fromCssColorString('rgba(0, 255, 50, 0.3)');} else if (temp < 35) {// 黄色 - 温暖return Cesium.Color.fromCssColorString('rgba(255, 255, 0, 0.3)');} else {// 红色 - 炎热return Cesium.Color.fromCssColorString('rgba(255, 50, 0, 0.3)');}}, false)),heightReference: Cesium.HeightReference.CLAMP_TO_GROUND}});stations[station.id] = { entity, tempCircle };});// 模拟数据更新函数function updateWeatherData() {// 在实际应用中,这里会从API获取实时数据// 这里使用随机数据进行模拟Object.keys(stations).forEach(stationId => {const station = stations[stationId];// 随机温度变化 (-10 到 40)const currentTemp = station.entity.properties.temperature.getValue() || 15;const tempChange = (Math.random() - 0.5) * 2; // -1 到 1 度变化const newTemp = Math.min(40, Math.max(-10, currentTemp + tempChange));// 更新属性station.entity.properties.temperature = newTemp;station.entity.properties.humidity = Math.random() * 100;station.entity.properties.windSpeed = Math.random() * 30;station.entity.properties.precipitation = Math.random() * 10;station.entity.properties.lastUpdate = new Date().toISOString();// 更新标签station.entity.label.text = `${station.entity.properties.stationId.getValue()}: ${newTemp.toFixed(1)}°C`;});}// 每10秒更新一次数据setInterval(updateWeatherData, 10000);updateWeatherData(); // 初始更新return stations;
}// 站点数据
const weatherStations = [{ id: 'BJ01', name: '北京站', longitude: 116.39, latitude: 39.9 },{ id: 'SH01', name: '上海站', longitude: 121.47, latitude: 31.23 },{ id: 'GZ01', name: '广州站', longitude: 113.26, latitude: 23.13 }
];// 创建气象站网络
const stations = createWeatherStations(weatherStations);

2. 运动轨迹回放与预测

// ⭐创建车辆轨迹回放与预测系统
async function createVehicleTrackingSystem() {// 模拟车辆实时位置数据class VehicleTracker {constructor(vehicleId, startPosition, speed) {this.vehicleId = vehicleId;this.currentPosition = startPosition;this.speed = speed; // 米/秒this.heading = Math.random() * 360; // 初始方向this.positionHistory = []; // 历史位置this.historyLimit = 100; // 保留的历史位置数量// 添加时间戳和位置this.positionHistory.push({time: Cesium.JulianDate.fromDate(new Date()),position: Cesium.Cartesian3.fromDegrees(startPosition[0], startPosition[1], startPosition[2] || 0)});}// 更新位置update(deltaSeconds) {// 计算新位置const distance = this.speed * deltaSeconds; // 米const headingRadians = Cesium.Math.toRadians(this.heading);// 随机小幅度改变方向this.heading += (Math.random() - 0.5) * 10;// 将距离转换为经纬度变化 (粗略计算)const latChange = distance * Math.cos(headingRadians) / 111000;const lonChange = distance * Math.sin(headingRadians) / (111000 * Math.cos(Cesium.Math.toRadians(this.currentPosition[1])));// 更新位置this.currentPosition = [this.currentPosition[0] + lonChange,this.currentPosition[1] + latChange,this.currentPosition[2] || 0];// 添加到历史记录this.positionHistory.push({time: Cesium.JulianDate.fromDate(new Date()),position: Cesium.Cartesian3.fromDegrees(this.currentPosition[0], this.currentPosition[1], this.currentPosition[2])});// 限制历史记录长度if (this.positionHistory.length > this.historyLimit) {this.positionHistory.shift();}return this.currentPosition;}// 获取历史位置getPositionHistory() {return this.positionHistory;}// 预测未来位置predictFuturePositions(seconds, interval) {const predictions = [];let predictPosition = [...this.currentPosition];const predictHeading = this.heading;for (let i = 0; i < seconds / interval; i++) {// 计算未来位置const distance = this.speed * interval;const headingRadians = Cesium.Math.toRadians(predictHeading);const latChange = distance * Math.cos(headingRadians) / 111000;const lonChange = distance * Math.sin(headingRadians) / (111000 * Math.cos(Cesium.Math.toRadians(predictPosition[1])));predictPosition = [predictPosition[0] + lonChange,predictPosition[1] + latChange,predictPosition[2] || 0];const futureTime = Cesium.JulianDate.addSeconds(Cesium.JulianDate.fromDate(new Date()),interval * (i + 1),new Cesium.JulianDate());predictions.push({time: futureTime,position: Cesium.Cartesian3.fromDegrees(predictPosition[0],predictPosition[1],predictPosition[2])});}return predictions;}}// 创建实体可视化function createVehicleEntity(vehicleTracker) {// 历史路径采样属性const positionProperty = new Cesium.SampledPositionProperty();// 预测路径采样属性const predictionProperty = new Cesium.SampledPositionProperty();// 创建车辆实体const vehicleEntity = viewer.entities.add({id: `vehicle-${vehicleTracker.vehicleId}`,// 动态位置position: new Cesium.CallbackProperty(function(time) {return vehicleTracker.positionHistory[vehicleTracker.positionHistory.length - 1].position;}, false),// 动态方向 - 朝向行进方向orientation: new Cesium.CallbackProperty(function(time) {return Cesium.Transforms.headingPitchRollQuaternion(vehicleTracker.positionHistory[vehicleTracker.positionHistory.length - 1].position,new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(vehicleTracker.heading),0,0));}, false),model: {uri: './models/vehicle.glb',minimumPixelSize: 32,maximumScale: 10000},// 信息标签label: {text: vehicleTracker.vehicleId,font: '14px sans-serif',style: Cesium.LabelStyle.FILL_AND_OUTLINE,outlineWidth: 2,verticalOrigin: Cesium.VerticalOrigin.TOP,pixelOffset: new Cesium.Cartesian2(0, 20)},// 历史轨迹路径path: {resolution: 1,material: new Cesium.PolylineGlowMaterialProperty({glowPower: 0.1,color: Cesium.Color.BLUE}),width: 10,leadTime: 0,trailTime: 60 // 显示60秒的历史轨迹},// 自定义属性properties: {vehicleId: vehicleTracker.vehicleId,speed: vehicleTracker.speed,updateTime: new Date().toISOString()}});// 创建预测轨迹const predictionPath = viewer.entities.add({id: `prediction-${vehicleTracker.vehicleId}`,polyline: {// 动态生成预测点positions: new Cesium.CallbackProperty(function(time) {const predictions = vehicleTracker.predictFuturePositions(30, 1);return predictions.map(p => p.position);}, false),width: 3,material: new Cesium.PolylineDashMaterialProperty({color: Cesium.Color.YELLOW,dashLength: 8.0})}});return {vehicleEntity,predictionPath,update: function() {// 更新实体的属性vehicleEntity.properties.speed = vehicleTracker.speed;vehicleEntity.properties.updateTime = new Date().toISOString();}};}// 创建多个车辆const vehicles = [];const vehicleEntities = [];// 初始化几个随机位置的车辆for (let i = 0; i < 5; i++) {// 北京市区范围内随机位置const startPosition = [116.39 + (Math.random() - 0.5) * 0.1,39.9 + (Math.random() - 0.5) * 0.1,0];// 速度 10-30 米/秒const speed = 10 + Math.random() * 20;// 创建车辆跟踪器const vehicleTracker = new VehicleTracker(`V${i+1}`, startPosition, speed);vehicles.push(vehicleTracker);// 创建可视化实体const vehicleEntity = createVehicleEntity(vehicleTracker);vehicleEntities.push(vehicleEntity);}// 更新循环viewer.scene.preRender.addEventListener((scene, time) => {// 计算自上次更新的时间差(秒)static let lastUpdateTime = Date.now();const now = Date.now();const deltaSeconds = (now - lastUpdateTime) / 1000;lastUpdateTime = now;// 更新每个车辆for (let i = 0; i < vehicles.length; i++) {// 更新位置vehicles[i].update(deltaSeconds);// 更新实体vehicleEntities[i].update();}});return {vehicles,vehicleEntities};
}// 启动车辆跟踪系统
const trackingSystem = createVehicleTrackingSystem();

3. 三维数据可视化 - 温度柱状图

// ⭐创建三维温度柱状图
function createTemperatureColumns(data) {const entities = [];// 基本设置const spacing = 0.01; // 经纬度间距const baseHeight = 10; // 柱子基础高度(米)const heightScale = 10; // 高度缩放因子// 创建网格for (let i = 0; i < data.length; i++) {for (let j = 0; j < data[i].length; j++) {const temperature = data[i][j]; // 温度值// 计算位置const longitude = 116.30 + j * spacing;const latitude = 39.80 + i * spacing;// 计算柱高const columnHeight = baseHeight + temperature * heightScale;// 基于温度计算颜色let color;if (temperature < 0) {// 蓝色到青色的渐变(-10°C到0°C)const t = (temperature + 10) / 10; // 0到1color = Cesium.Color.fromCssColorString(`rgba(0, ${Math.floor(255 * t)}, 255, 0.7)`);} else if (temperature < 15) {// 青色到绿色的渐变(0°C到15°C)const t = temperature / 15; // 0到1color = Cesium.Color.fromCssColorString(`rgba(0, 255, ${Math.floor(255 * (1-t))}, 0.7)`);} else if (temperature < 30) {// 绿色到黄色的渐变(15°C到30°C)const t = (temperature - 15) / 15; // 0到1color = Cesium.Color.fromCssColorString(`rgba(${Math.floor(255 * t)}, 255, 0, 0.7)`);} else {// 黄色到红色的渐变(30°C到40°C)const t = Math.min(1, (temperature - 30) / 10); // 0到1color = Cesium.Color.fromCssColorString(`rgba(255, ${Math.floor(255 * (1-t))}, 0, 0.7)`);}// 创建柱体const entity = viewer.entities.add({position: Cesium.Cartesian3.fromDegrees(longitude, latitude, columnHeight / 2),box: {dimensions: new Cesium.Cartesian3(spacing * 111000 * 0.8, // 宽度(米)spacing * 111000 * 0.8, // 深度(米)columnHeight            // 高度(米)),material: color,outline: true,outlineColor: Cesium.Color.BLACK.withAlpha(0.2)},// 添加温度标签label: {text: temperature.toFixed(1) + '°C',font: '10px sans-serif',style: Cesium.LabelStyle.FILL_AND_OUTLINE,outlineWidth: 2,verticalOrigin: Cesium.VerticalOrigin.TOP,pixelOffset: new Cesium.Cartesian2(0, 5),// 只在近距离显示标签distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 15000)}});entities.push(entity);}}// 动态更新函数 - 模拟温度变化function updateTemperatures() {for (let i = 0; i < data.length; i++) {for (let j = 0; j < data[i].length; j++) {// 随机温度变化(-0.5°C到+0.5°C)data[i][j] += (Math.random() - 0.5);// 确保温度在合理范围data[i][j] = Math.max(-10, Math.min(40, data[i][j]));const index = i * data[i].length + j;const entity = entities[index];// 更新柱高const columnHeight = baseHeight + data[i][j] * heightScale;entity.position = Cesium.Cartesian3.fromDegrees(116.30 + j * spacing,39.80 + i * spacing,columnHeight / 2);entity.box.dimensions = new Cesium.Cartesian3(spacing * 111000 * 0.8,spacing * 111000 * 0.8,columnHeight);// 更新标签entity.label.text = data[i][j].toFixed(1) + '°C';// 更新颜色let color;const temperature = data[i][j];if (temperature < 0) {const t = (temperature + 10) / 10;color = Cesium.Color.fromCssColorString(`rgba(0, ${Math.floor(255 * t)}, 255, 0.7)`);} else if (temperature < 15) {const t = temperature / 15;color = Cesium.Color.fromCssColorString(`rgba(0, 255, ${Math.floor(255 * (1-t))}, 0.7)`);} else if (temperature < 30) {const t = (temperature - 15) / 15;color = Cesium.Color.fromCssColorString(`rgba(${Math.floor(255 * t)}, 255, 0, 0.7)`);} else {const t = Math.min(1, (temperature - 30) / 10);color = Cesium.Color.fromCssColorString(`rgba(255, ${Math.floor(255 * (1-t))}, 0, 0.7)`);}entity.box.material = color;}}}// 每10秒更新一次数据setInterval(updateTemperatures, 10000);return {entities,data,updateTemperatures};
}// 创建初始温度数据 (10x10网格)
const temperatureData = [];
for (let i = 0; i < 10; i++) {const row = [];for (let j = 0; j < 10; j++) {// 生成-5°C到35°C的随机温度row.push(Math.random() * 40 - 5);}temperatureData.push(row);
}// 创建温度可视化
const temperatureVis = createTemperatureColumns(temperatureData);

🏆 动态Entity实战技巧总结

  1. 选择正确的更新方法:

    • 低频更新 → 直接修改属性
    • 基于时间的动画 → CallbackProperty
    • 关键帧动画 → SampledPositionProperty
  2. 性能优化:

    • 避免在每一帧都创建新对象
    • 使用回调缓存值(CallbackProperty的第二个参数为true)
    • 减少不必要的实体,考虑使用Entity clustering
    • 远距离减少细节(使用distanceDisplayCondition)
  3. 动画平滑度:

    • 使用插值算法,如LagrangePolynomialApproximationHermitePolynomialApproximation
    • 选择合适的插值度数(interpolationDegree)
    • 使用缓动函数创建更自然的动画
  4. 创建复杂动画:

    • 组合多种动画类型
    • 使用预渲染事件获取精确的时间
    • 考虑使用动画管理器处理多个动画
  5. 数据驱动可视化:

    • 将数据变化映射到视觉属性
    • 使用颜色映射表示数据变化
    • 考虑多维数据的不同表现形式

🧩 记忆助手

动态属性三板斧: 直接修改、回调函数、采样属性
动画平滑四字诀: 插值、缓动、关键帧、时钟同步
性能优化要领: 对象复用、批量更新、条件显示、远近细节

http://www.xdnf.cn/news/3410.html

相关文章:

  • 嵌入式AI还是一片蓝海
  • Day107 | 147.对链表进行插入排序 | 简单选择、冒泡、直接插入
  • 【专题五】位运算(2)
  • AXI中的out of order和interleaving的定义和两者的差别?
  • OSPF的路由
  • Go-web开发之社区功能
  • Java 中那些奇怪的空指针报错场景及解决方案NullPointerException
  • 【计算机视觉】语义分割:MMSegmentation:OpenMMLab开源语义分割框架实战指南
  • MySQL数据同步之Canal讲解
  • 2025年- H16-Lc124-169.多数元素(技巧)---java版
  • 7.0/Q1,GBD数据库最新文章解读
  • ClackyAI:下一代智能云开发环境的技术革新与实践价值
  • WPF使用依赖注入框架AutoMapper
  • 仿腾讯会议——服务器结构讲解
  • Matlab/Simulink - BLDC直流无刷电机仿真基础教程(四) - PWM调制模拟
  • 后端工程师需要掌握哪些基础技能
  • 机器人--底盘
  • 人才答辩ppt优化技巧_杰青_优青_万人计划青年拔尖人才_青年长江学者ppt制作案例
  • 2025五一杯A题五一杯数学建模思路代码文章教学:支路车流量推测问题
  • 部署.NET6.0 Web API项目到Docker
  • 实现了一个基于寄存器操作STM32F103C8t6的工程, 并实现对PA1,PA2接LED正极的点灯操作
  • npm宿主依赖、宿主环境依赖(peerDependencies)(指由宿主环境提供的依赖)
  • 网络安全防火墙技术有哪些?网络防火墙的主要作用
  • 在ASP.NET MVC中使用Repeater指南
  • 【浅尝Java】Java简介第一个Java程序(含JDK、JRE与JVM关系、javcdoc的使用)
  • Seata服务端回滚事务核心源码解析
  • springboot中异步接口实现所有方式_20250501
  • 内存 “舞台” 上,进程如何 “翩翩起舞”?(转)
  • idea安装
  • 【Unity】 组件库分类详解