Cesium 时间线 及 坐标转换

文章目录

    • Cesium 基础理解(二)
      • TimeLine & Clock 应用场景
      • 核心代码实例及解释
      • 代码解释
    • Cesium 之 实体动画
      • 构建实体动画的技巧
        • 1. 利用时间属性
        • 2. 组合动画效果
        • 3. 使用动画曲线
      • 优化点
        • 1. 减少属性更新频率
        • 2. 优化实体数量
        • 3. 合理使用材质和纹理
      • 注意事项
        • 1. 时间同步
        • 2. 内存管理
        • 3. 兼容性
    • 坐标系转换
      • 1. WGS84 与 Web 墨卡托投影坐标系转换
        • 1.1 WGS84 转 Web 墨卡托
        • 1.2 Web 墨卡托转 WGS84
      • 2. 二维屏幕坐标与 WGS84 坐标转换
        • 2.1 二维屏幕坐标转 WGS84 坐标
        • 2.2 WGS84 坐标转二维屏幕坐标
      • 3. 二维平面坐标与 WGS84 坐标转换
        • 3.1 二维平面坐标(局部坐标系)转 WGS84 坐标
        • 3.2 WGS84 坐标转二维平面坐标(局部坐标系)
    • 技术名词解释(局部坐标系)
      • 定义与概念
      • 为什么需要局部坐标系
      • 局部坐标系的构成要素
      • 在 Cesium 中的应用示例解释
      • 定义局部二维平面坐标(x, y 偏移量)的原因
      • 是否可以随便改值

Cesium 基础理解(二)

在 Cesium 里,TimeLine(时间轴)与 Clock(时钟)是用来管理和展示时间相关数据与动画的重要组件。Clock 负责管理时间的推进,而 TimeLine 则提供了一个可视化的界面,让用户能够与时间进行交互。下面为你详细介绍它们的应用,并给出核心代码及解释。

TimeLine & Clock 应用场景

  • 动态数据可视化:像气象数据、交通流量数据这类随时间变化的数据,可借助时间轴与时钟来展示不同时间点的数据。
  • 动画演示:在模拟飞行、天体运动等场景时,能够利用时间轴和时钟控制动画的播放。
  • 历史数据回放:对历史事件进行回放,用户可通过时间轴选择特定的时间点查看相应的数据。

核心代码实例及解释

下面的代码展示了如何在 Cesium 中使用 TimeLineClock 来创建一个简单的动画:

// 初始化 Cesium Viewer
var viewer = new Cesium.Viewer('cesiumContainer', {
    timeline: true,  // 显示时间轴
    animation: true  // 显示动画控制器
});

// 设置时钟的起始时间和结束时间
var start = Cesium.JulianDate.fromIso8601('2025-04-03T00:00:00Z');
var stop = Cesium.JulianDate.addDays(start, 1, new Cesium.JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();

// 设置时间乘数和时钟范围
viewer.clock.multiplier = 3600; // 每帧前进一小时
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // 到达结束时间后循环播放
viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER;

// 创建一个实体,用于演示动画
var entity = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
    point: {
        pixelSize: 10,
        color: Cesium.Color.RED
    }
});

// 动画回调函数
viewer.clock.onTick.addEventListener(function (clock) {
    // 在每次时钟更新时,更新实体的颜色
    var currentTime = clock.currentTime;
    var fraction = Cesium.JulianDate.secondsDifference(currentTime, start) / Cesium.JulianDate.secondsDifference(stop, start);
    entity.point.color = Cesium.Color.fromHsl(fraction, 1.0, 0.5);
});    

代码解释

  1. 初始化 Cesium Viewer
var viewer = new Cesium.Viewer('cesiumContainer', {
    timeline: true,  // 显示时间轴
    animation: true  // 显示动画控制器
});

创建一个 Cesium Viewer 实例,并且启用时间轴和动画控制器。

  1. 设置时钟参数
var start = Cesium.JulianDate.fromIso8601('2025-04-03T00:00:00Z');
var stop = Cesium.JulianDate.addDays(start, 1, new Cesium.JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();

定义时钟的起始时间和结束时间,并且将当前时间设置为起始时间。

  1. 设置时间乘数和时钟范围
viewer.clock.multiplier = 3600; // 每帧前进一小时
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // 到达结束时间后循环播放
viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER;
  • multiplier:设置时间推进的速度,这里表示每帧前进一小时。
  • clockRange:设置时钟到达结束时间后的行为,LOOP_STOP 表示到达结束时间后循环播放。
  • clockStep:设置时钟推进的方式,SYSTEM_CLOCK_MULTIPLIER 表示根据系统时钟和 multiplier 推进。
  1. 创建实体
var entity = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
    point: {
        pixelSize: 10,
        color: Cesium.Color.RED
    }
});

创建一个红色的点实体,用于演示动画。

  1. 动画回调函数
viewer.clock.onTick.addEventListener(function (clock) {
    // 在每次时钟更新时,更新实体的颜色
    var currentTime = clock.currentTime;
    var fraction = Cesium.JulianDate.secondsDifference(currentTime, start) / Cesium.JulianDate.secondsDifference(stop, start);
    entity.point.color = Cesium.Color.fromHsl(fraction, 1.0, 0.5);
});

监听 viewer.clock.onTick 事件,在每次时钟更新时,根据当前时间计算一个颜色值,并更新实体的颜色。这样就实现了一个简单的动画效果。

Cesium 之 实体动画

构建实体动画的技巧

1. 利用时间属性
  • 插值动画:借助 SampledPositionPropertyConstantProperty 等属性类,为实体的位置、姿态等属性设置随时间变化的值。例如,你可以使用 SampledPositionProperty 按时间顺序添加多个位置点,Cesium 会自动在这些点之间进行插值,从而实现平滑的移动动画。
var positionProperty = new Cesium.SampledPositionProperty();
positionProperty.addSample(Cesium.JulianDate.fromIso8601('2025-04-03T00:00:00Z'), Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883));
positionProperty.addSample(Cesium.JulianDate.fromIso8601('2025-04-03T01:00:00Z'), Cesium.Cartesian3.fromDegrees(-76.59777, 41.03883));
var entity = viewer.entities.add({
    position: positionProperty,
    point: {
        pixelSize: 10,
        color: Cesium.Color.RED
    }
});
  • 时间范围控制:合理设置 ClockstartTimestopTimecurrentTime,以精确控制动画的开始、结束和当前播放位置。
2. 组合动画效果
  • 多属性动画:同时对实体的多个属性(如位置、颜色、大小)进行动画设置,从而创建更丰富的动画效果。例如,在实体移动的同时改变其颜色。
viewer.clock.onTick.addEventListener(function (clock) {
    var currentTime = clock.currentTime;
    var fraction = Cesium.JulianDate.secondsDifference(currentTime, start) / Cesium.JulianDate.secondsDifference(stop, start);
    entity.point.color = Cesium.Color.fromHsl(fraction, 1.0, 0.5);
    entity.point.pixelSize = 10 + 10 * Math.sin(fraction * Math.PI * 2);
});
  • 序列动画:按顺序依次播放不同的动画,实现复杂的动画流程。你可以通过在不同的时间区间设置不同的属性值来达成这一目的。
3. 使用动画曲线
  • 自定义插值函数:除了默认的线性插值,你还可以自定义插值函数,以实现更自然的动画效果。例如,使用缓动函数(如 easeInOut)让动画的开始和结束更加平滑。
function easeInOut(t) {
    return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
viewer.clock.onTick.addEventListener(function (clock) {
    var currentTime = clock.currentTime;
    var fraction = Cesium.JulianDate.secondsDifference(currentTime, start) / Cesium.JulianDate.secondsDifference(stop, start);
    var easedFraction = easeInOut(fraction);
    entity.point.color = Cesium.Color.fromHsl(easedFraction, 1.0, 0.5);
});

优化点

1. 减少属性更新频率
  • 批量更新:避免在每一帧都对实体的属性进行更新,可在必要时批量更新属性。例如,只有当时间间隔达到一定值时才更新实体的颜色。
var updateInterval = 0.1; // 每 0.1 秒更新一次
var lastUpdateTime = -Infinity;
viewer.clock.onTick.addEventListener(function (clock) {
    var currentTime = clock.currentTime;
    var elapsedSeconds = Cesium.JulianDate.secondsDifference(currentTime, start);
    if (elapsedSeconds - lastUpdateTime >= updateInterval) {
        var fraction = elapsedSeconds / Cesium.JulianDate.secondsDifference(stop, start);
        entity.point.color = Cesium.Color.fromHsl(fraction, 1.0, 0.5);
        lastUpdateTime = elapsedSeconds;
    }
});
  • 缓存计算结果:对于一些复杂的计算结果,如三角函数值、插值系数等,进行缓存,避免重复计算。
2. 优化实体数量
  • 按需加载:仅在需要时加载和显示实体,避免一次性加载过多实体导致性能下降。例如,根据相机的位置和视野范围,动态加载和卸载实体。
  • 合并实体:如果多个实体具有相似的属性和动画效果,可以考虑将它们合并为一个实体,减少渲染开销。
3. 合理使用材质和纹理
  • 简单材质:尽量使用简单的材质和纹理,避免使用过于复杂的纹理和特效,以减少 GPU 负担。
  • 纹理压缩:对纹理进行压缩,降低纹理的内存占用。

注意事项

1. 时间同步
  • 时钟设置:确保所有实体的动画都使用相同的 Clock 对象,以保证动画的时间同步。
  • 跨帧一致性:在动画回调函数中,要确保每次更新的结果在不同帧之间具有一致性,避免出现跳跃或闪烁的现象。
2. 内存管理
  • 实体销毁:当动画结束或不再需要某个实体时,及时销毁该实体,释放内存。
viewer.entities.remove(entity);
  • 属性清理:对于不再使用的属性对象,及时清理,避免内存泄漏。
3. 兼容性
  • 不同浏览器和设备:在不同的浏览器和设备上测试动画效果,确保动画在各种环境下都能正常显示和运行。
  • 版本兼容性:注意 Cesium 库的版本兼容性,不同版本的 API 可能会有所不同。

坐标系转换

在 Cesium 里,坐标系转化是常见的操作,特别是在处理 WGS84(地理坐标系)、Web 墨卡托投影坐标系以及二维坐标与平面坐标转换时。下面为你详细介绍这些坐标系转化的方法,并给出对应的代码示例。

1. WGS84 与 Web 墨卡托投影坐标系转换

1.1 WGS84 转 Web 墨卡托

WGS84 是一种地理坐标系,使用经度、纬度和高度来表示位置;而 Web 墨卡托投影坐标系是一种平面坐标系,常用于地图显示。在 Cesium 中,可以使用 Cesium.CartographicCesium.WebMercatorProjection 来实现转换。

// 定义 WGS84 坐标(经度、纬度、高度)
var longitude = Cesium.Math.toRadians(-75.59777);
var latitude = Cesium.Math.toRadians(40.03883);
var height = 0;

// 创建 Cartographic 对象
var cartographic = new Cesium.Cartographic(longitude, latitude, height);

// 创建 Web 墨卡托投影对象
var projection = new Cesium.WebMercatorProjection();

// 转换为 Web 墨卡托坐标
var webMercator = projection.project(cartographic);
1.2 Web 墨卡托转 WGS84
// 定义 Web 墨卡托坐标
var x = webMercator.x;
var y = webMercator.y;

// 反投影到 WGS84
var cartographicBack = projection.unproject(webMercator);

// 获取经度、纬度和高度
var longitudeBack = Cesium.Math.toDegrees(cartographicBack.longitude);
var latitudeBack = Cesium.Math.toDegrees(cartographicBack.latitude);
var heightBack = cartographicBack.height;

2. 二维屏幕坐标与 WGS84 坐标转换

2.1 二维屏幕坐标转 WGS84 坐标

当用户在屏幕上点击某个位置时,需要将屏幕坐标转换为 WGS84 坐标。可以使用 viewer.scene.pickPosition 方法来实现。

// 监听鼠标点击事件
var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
    // 获取屏幕坐标
    var cartesian = viewer.camera.pickEllipsoid(movement.position, viewer.scene.globe.ellipsoid);
    if (cartesian) {
        // 将笛卡尔坐标转换为 WGS84 坐标
        var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
        var longitude = Cesium.Math.toDegrees(cartographic.longitude);
        var latitude = Cesium.Math.toDegrees(cartographic.latitude);
        var height = cartographic.height;
        console.log('经度:', longitude, '纬度:', latitude, '高度:', height);
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
2.2 WGS84 坐标转二维屏幕坐标
// 定义 WGS84 坐标
var longitude = Cesium.Math.toRadians(-75.59777);
var latitude = Cesium.Math.toRadians(40.03883);
var height = 0;

// 创建 Cartographic 对象
var cartographic = new Cesium.Cartographic(longitude, latitude, height);

// 将 WGS84 坐标转换为笛卡尔坐标
var cartesian = Cesium.Ellipsoid.WGS84.cartographicToCartesian(cartographic);

// 将笛卡尔坐标转换为屏幕坐标
var canvasPosition = Cesium.SceneTransforms.wgs84ToWindowCoordinates(viewer.scene, cartesian);

3. 二维平面坐标与 WGS84 坐标转换

3.1 二维平面坐标(局部坐标系)转 WGS84 坐标

如果有一个局部的二维平面坐标系,需要将其转换为 WGS84 坐标,可以先定义一个原点的 WGS84 坐标,然后根据平面坐标的偏移量进行转换。

// 定义原点的 WGS84 坐标
var originLongitude = Cesium.Math.toRadians(-75.59777);
var originLatitude = Cesium.Math.toRadians(40.03883);
var originHeight = 0;
var originCartographic = new Cesium.Cartographic(originLongitude, originLatitude, originHeight);
var originCartesian = Cesium.Ellipsoid.WGS84.cartographicToCartesian(originCartographic);

// 定义局部二维平面坐标(x, y 偏移量)
var localX = 100;
var localY = 200;

// 假设局部坐标系的单位为米,计算偏移后的笛卡尔坐标
var offsetCartesian = new Cesium.Cartesian3(
    originCartesian.x + localX,
    originCartesian.y + localY,
    originCartesian.z
);

// 将笛卡尔坐标转换为 WGS84 坐标
var cartographic = Cesium.Cartographic.fromCartesian(offsetCartesian);
var longitude = Cesium.Math.toDegrees(cartographic.longitude);
var latitude = Cesium.Math.toDegrees(cartographic.latitude);
var height = cartographic.height;
3.2 WGS84 坐标转二维平面坐标(局部坐标系)
// 定义目标 WGS84 坐标
var targetLongitude = Cesium.Math.toRadians(-75.59778);
var targetLatitude = Cesium.Math.toRadians(40.03884);
var targetHeight = 0;
var targetCartographic = new Cesium.Cartographic(targetLongitude, targetLatitude, targetHeight);
var targetCartesian = Cesium.Ellipsoid.WGS84.cartographicToCartesian(targetCartographic);

// 计算相对于原点的偏移量
var offset = Cesium.Cartesian3.subtract(targetCartesian, originCartesian, new Cesium.Cartesian3());
var localX = offset.x;
var localY = offset.y;

通过上述代码示例,你可以在 Cesium 中实现不同坐标系之间的转换。在实际应用中,需要根据具体需求选择合适的转换方法。

技术名词解释(局部坐标系)

在地理信息系统(GIS)和三维可视化场景(如使用 Cesium 构建的场景)中,局部坐标系是一种非常有用的概念,下面为你详细解释。

定义与概念

局部坐标系是相对于全局坐标系(如 WGS84 地理坐标系)而言的,它是为了方便在特定区域内进行精确的位置描述和计算而建立的一种相对坐标系。在局部坐标系里,我们可以自由定义原点、坐标轴方向和单位长度,以此简化在该特定区域内的几何计算和数据处理。

为什么需要局部坐标系

  • 简化计算:在处理小范围的地理数据或者三维模型时,使用全局的 WGS84 坐标系可能会因为数值过大或者地球曲率的影响,导致计算复杂且容易出现精度问题。而局部坐标系可以将坐标值缩小到一个相对较小的范围内,减少计算误差,提高计算效率。
  • 方便建模:在创建三维模型或者进行场景设计时,使用局部坐标系可以更直观地描述物体的位置和方向。例如,在设计一个建筑物的三维模型时,我们可以将建筑物的某个角点作为局部坐标系的原点,然后根据建筑物的实际尺寸和方向来定义坐标轴,这样就可以更方便地确定建筑物各个部分的位置。

局部坐标系的构成要素

  • 原点:局部坐标系的原点是一个在全局坐标系中已知位置的点。在地理场景中,这个原点可以是某个特定的地理坐标点,比如一个城市的中心、一个建筑物的某个角点等。
  • 坐标轴方向:局部坐标系的坐标轴方向通常是根据具体的应用需求来定义的。在二维平面中,常见的做法是将一个坐标轴定义为水平方向(通常是向东),另一个坐标轴定义为垂直方向(通常是向北)。在三维空间中,还需要定义一个垂直于平面的坐标轴(通常是向上)。
  • 单位长度:局部坐标系的单位长度可以根据实际情况进行定义。在地理场景中,常用的单位长度是米。

在 Cesium 中的应用示例解释

在之前提供的 Cesium 代码示例中,我们构建了一个简单的局部二维平面坐标系并进行了坐标转换:

// 定义原点的 WGS84 坐标
var originLongitude = Cesium.Math.toRadians(-75.59777);
var originLatitude = Cesium.Math.toRadians(40.03883);
var originHeight = 0;
var originCartographic = new Cesium.Cartographic(originLongitude, originLatitude, originHeight);
var originCartesian = Cesium.Ellipsoid.WGS84.cartographicToCartesian(originCartographic);

// 定义局部二维平面坐标(x, y 偏移量)
var localX = 100;
var localY = 200;

// 假设局部坐标系的单位为米,计算偏移后的笛卡尔坐标
var offsetCartesian = new Cesium.Cartesian3(
    originCartesian.x + localX,
    originCartesian.y + localY,
    originCartesian.z
);

// 将笛卡尔坐标转换为 WGS84 坐标
var cartographic = Cesium.Cartographic.fromCartesian(offsetCartesian);
var longitude = Cesium.Math.toDegrees(cartographic.longitude);
var latitude = Cesium.Math.toDegrees(cartographic.latitude);
var height = cartographic.height;
  • 原点定义:代码中首先定义了一个原点的 WGS84 坐标,将其转换为笛卡尔坐标 originCartesian,这个点就是局部坐标系的原点。
  • 局部坐标表示localXlocalY 表示在局部坐标系中的偏移量,单位为米。通过将这些偏移量加到原点的笛卡尔坐标上,就可以得到在全局坐标系中的新位置。
  • 坐标转换:最后,将得到的笛卡尔坐标转换回 WGS84 坐标,这样就完成了从局部坐标系到全局坐标系的转换。

通过这种方式,我们可以在局部坐标系中方便地进行位置计算和处理,然后再将结果转换回全局坐标系进行展示和分析。

定义局部二维平面坐标(x, y 偏移量)的原因

在构建局部坐标系并进行坐标转换时,定义 localXlocalY 这两个偏移量是为了描述在局部坐标系中某个点相对于原点的位置。

在实际应用场景里,我们常常需要在特定的小范围内进行精确的位置计算和表示。以地理信息系统为例,当你要描述一个建筑物内部各个房间的位置,或者一个小型园区内各个设施的位置时,直接使用全局的地理坐标(如 WGS84 坐标)会比较复杂,而且在小范围内使用地理坐标进行计算还可能会因为地球曲率等因素产生精度问题。

这时,我们就可以建立一个局部坐标系,将这个小范围的某个特征点(如建筑物的某个墙角、园区的大门位置等)作为局部坐标系的原点。然后,使用 localXlocalY 来表示其他点相对于原点在水平和垂直方向上的偏移距离,这样就能更方便、更精确地描述这些点的位置。

是否可以随便改值

可以随意修改 localXlocalY 的值,这取决于你具体的需求:

  • 描述不同位置:修改 localXlocalY 的值可以表示局部坐标系中不同的点。例如,如果你想描述局部坐标系中另一个位置的点,就可以通过调整这两个偏移量来实现。比如,将 localX 改为 300,localY 改为 400,就表示该点相对于原点在水平方向上偏移 300 个单位(这里假设单位是米),在垂直方向上偏移 400 个单位。
  • 模拟移动或变化:在动画或者动态场景中,你可以动态地改变 localXlocalY 的值,来模拟物体在局部坐标系中的移动。例如,在一个模拟车辆在园区内行驶的场景中,随着时间的推移,不断更新 localXlocalY 的值,就可以让车辆在局部坐标系中移动,然后再将其转换到全局坐标系中进行展示。

不过,在修改这两个值时,需要注意以下几点:

  • 单位一致性:要确保 localXlocalY 的单位与你所设定的局部坐标系的单位一致。如果单位是米,那么所有的偏移量都应该以米为单位进行计算。
  • 范围合理性:要考虑局部坐标系的范围和实际应用场景的限制。如果偏移量过大,可能会超出局部坐标系所能合理表示的范围,或者导致转换后的全局坐标出现不合理的结果。

以下是一个简单的示例代码,展示了如何动态改变 localXlocalY 的值来模拟物体的移动:

// 定义原点的 WGS84 坐标
var originLongitude = Cesium.Math.toRadians(-75.59777);
var originLatitude = Cesium.Math.toRadians(40.03883);
var originHeight = 0;
var originCartographic = new Cesium.Cartographic(originLongitude, originLatitude, originHeight);
var originCartesian = Cesium.Ellipsoid.WGS84.cartographicToCartesian(originCartographic);

// 初始局部二维平面坐标(x, y 偏移量)
var localX = 0;
var localY = 0;

// 模拟物体移动,每 1 秒更新一次位置
setInterval(function () {
    localX += 10;
    localY += 10;

    // 假设局部坐标系的单位为米,计算偏移后的笛卡尔坐标
    var offsetCartesian = new Cesium.Cartesian3(
        originCartesian.x + localX,
        originCartesian.y + localY,
        originCartesian.z
    );

    // 将笛卡尔坐标转换为 WGS84 坐标
    var cartographic = Cesium.Cartographic.fromCartesian(offsetCartesian);
    var longitude = Cesium.Math.toDegrees(cartographic.longitude);
    var latitude = Cesium.Math.toDegrees(cartographic.latitude);
    var height = cartographic.height;

    console.log('当前位置:经度', longitude, '纬度', latitude, '高度', height);
}, 1000);

在这个示例中,每 1 秒 localXlocalY 都会增加 10,从而模拟物体在局部坐标系中的移动,然后将其转换为全局的 WGS84 坐标并输出。

你可能感兴趣的:(webgl,cesium,坐标转换,cesium之时间应用)