• 可以通过czml-writer来生成CZML,这个程序维护在Github上

  • CZML 是一种 JSON 格式的字符串,用于描述与时间有关的动画场景,CZML 包含点、线、地标、模型、和其他的一些图形元素,并指明了这些元素如何随时间而变化。

  • 格式 cankaoopen in new window

  • 第一个packet代表了cesium场景(cesium时间轴的范围,当前时刻,倍速等信息)

  • 之外,其他的packet都可以理解为描述某一时间范围内的entity的行为。

  • 一个czml文件当中至少有一个packet,也即第一个描述场景的packet,

  • 每一个id不能相同,否则只显示相同id中最后一个entity,但是对于 不同czml文件中可以使用相同id。


let czml=[
     //packet1,id一定为document,否则会报错,这里定义的是整个显示场景的信息
    {
        "id": "document",
        "clock": {
            "interval": "2022-01-01T10:10:10+0800/2022-01-05T10:10:10+0800",
            "currentTime": "2022-01-02T02:10:10",
            "step": "SYSTEM_CLOCK_MULTIPLIER",
            "range": "LOOP_STOP",
            "multiplier": 5
        },
        "version": "1.0"
    },
    //packet two
    {
		"id":"GroundControlStation"
		"position":{"cartographicDegrees":[-75.5,40.0,0.0]},
		"point":{
		"color":{"rgba":[0,0,255,255]},
		}
	},
	// packet three
	{
		"id":"PredatorUAV",
		// ...
	}
]


    "path":{
      "show":[
        {
          "interval":"2012-03-15T10:00:00Z/2012-03-16T10:00:00Z",
          "boolean":true
        }
      ],
      "width":1,
      "material":{
        "solidColor":{
          "color":{
            "rgba":[
              0,255,0,255
            ]
          }
        }
      },
      "resolution":120,
    //   提前量
      "leadTime":[
        {
          "interval":"2012-03-15T10:00:00Z/2012-03-15T10:39:30.5752243210009Z",
          "epoch":"2012-03-15T10:00:00Z",
          "number":[
            0,5903.376977238004,
            5903.376977238004,0
          ]
        },
          
        {
          "interval":"2012-03-16T08:21:36.5644517090113Z/2012-03-16T10:00:00Z",
          "epoch":"2012-03-16T08:21:36.5644517090113Z",
          "number":[
            0,5903.435548290989,
            5903.435548290989,0
          ]
        }
      ],

    //   后尾部
      "trailTime":[
        {
          "interval":"2012-03-15T10:00:00Z/2012-03-15T10:39:30.5752243210009Z",
          "epoch":"2012-03-15T10:00:00Z",
          "number":[
            0,0,
            5903.376977238004,5903.376977238004
          ]
        },
        {
          "interval":"2012-03-15T10:39:30.5752243210009Z/2012-03-15T12:17:53.9522015590046Z",
          "epoch":"2012-03-15T10:39:30.5752243210009Z",
          "number":[
            0,0,
            5903.376977238004,5903.376977238004
          ]
        } 
         
      ]

CZML文件加载

let czmldata = new Cesium.CzmlDataSource(id).load(czml);
//id 为 CzmlDataSource对象 id
//或者直接写做 let czmldata = Cesium.CzmlDataSource.load(czml);
let temp;
cesium.viewer.dataSources.add(czmldata).then(function (ds) {
     temp = ds;
});
//或者 cesium.viewer.dataSources.add(czmldata)
  • 最终czml文件最终成为了CzmlDataSource对象,被加载到viewer的datasources中。
  • ds是一个CzmlDataSource对象,通过ds.entities.getById(id),可以获取到czml文件当中定义的entity,此时可以方便的对entity进行一些操作,比如viewer.trackEntity = ds.entities.getById("model_id"),又或者可以将CzmlDataSource对象保存下来,以便后续进行一些操作。 ds一定要赋值给已经定义好的对象,故先声明对象,而后进行赋值。 注意:当加载多个czml文件时,场景信息会以最后一个czml文件定义的为准。

CZML文件移除

也即CzmlDataSource对象的移除,写做:

cesium.viewer.dataSources.remove(temp,isDestroy)

temp也即刚才保存的CzmlDataSource对象,这样可以将刚才添加的场景信息、entity信息都移除掉,isDestroy是一个布尔对象,代表是否要销毁CzmlDataSource对象。如果只移除某一个entity的信息,可以写做:

let entity = temp.entities.getById("GroundControlStation");
if(entity){
   temp.entities.remove(entity);
  }

如果删除viewer中的全部dataSources对象,可以写做:

cesium.viewer.dataSources.removeAll(true)

true代表销毁对象

模型的加载

  • 在CZML文件中想要加载模型,需要先将其他格式的模型文件转换成gltf文件,然后通过
            "model": {
                "show": true,
                "gltf": "./111.gltf",
                "minimumPixelSize": 99,
            },

进行加载,minimumPixelSize表示显示的最小像素点,有了这个参数的保证,可以在缩小地球时也保证模型能够看清楚。

轨迹的显示

在场景中加载卫星一般情况下无法满足我们的使用要求,还需要卫星运动起来,并且显示其运行轨迹,在CZML文件中,一般可以用如下格式的文件来表示卫星和其轨迹的显示:

 
  {
      "id": "document",
      "clock": {
          "interval": "2022-03-22T16:08:00+08:00/2022-03-23T16:08:00+08:00",
          "currentTime": "2022-03-22T16:08:00+08:00",
          "step": "SYSTEM_CLOCK_MULTIPLIER",
          "range": "LOOP_STOP",
          "multiplier": 60
      },
      "version": "1.0"
  },
  {
      "label": {
          "outlineColor": {
              "rgba": [
                  0,
                  0,
                  0,
                  255
              ]
          },
          "horizontalOrigin": "LEFT",
          "text": "ppCOSMOS 2426 (717)",
          "outlineWidth": 2,
          "
          ": {
              "cartesian2": [
                  12,
                  0
              ]
          },
          "fillColor": {
              "rgba": [
                  "213",
                  "255",
                  "0",
                  255
              ]
          },
          "font": "11pt Lucida Console",
          "show": true
      },
      "id": "Satellite/ppCOSMOS 2426 (717)",
      "description": "Orbit of Satellite:  ppCOSMOS 2426 (717)",
      "position": {
          "interpolationAlgorithm": "LAGRANGE",
          "referenceFrame": "INERTIAL",
          "interpolationDegree": 5,

    // "interpolationAlgorithm": "LAGRANGE",  //插值算法为LAGRANGE,还有HERMITE,GEODESIC

//         "interpolationDegree": 5 //1为线性插值,2为平方插值

           "epoch": "2012-04-30T12:00Z", //表示时间起点为2012-04-30T12:00:00 
           
        "cartesian": [  
            0.0, 1.0, 2.0, 3.0,  //从起点开始,第0秒时坐标为(1,2,3)
            60.0, 4.0, 5.0, 6.0, //从起点开始,第60秒时坐标为(4,5,6) 
            120.0, 7.0, 8.0, 9.0 //从起点开始,第120秒时坐标为(7,8,9) 
        ]  
        ,
          "cartesian": [

            
              "2012-04-30T12:00Z", 1.0, 2.0, 3.0, //表示当时间为2012-04-30T12:00Z,坐标为(1,2,3)
            "2012-04-30T12:01Z", 4.0, 5.0, 6.0, //表示当时间为2012-04-30T12:01Z,坐标为(4,5,6)
            "2012-04-30T12:02Z", 7.0, 8.0, 9.0  //表示当时间为2012-04-30T12:02Z,坐标为(7,8,9),
              0,
              10246472.183615023,
              23315473.78200593,
              -99942.77560130549,
              300,
              9768930.236227807,
              23491323.358455077,
              -1173388.669547688,
              600,
              9270175.988391416,
              23616164.516070485,
              -2244286.2659865934,
             
          ],
          "epoch": "2022-03-22T16:08:00+08:00"
      },
      "billboard": {
          "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADJSURBVDhPnZHRDcMgEEMZjVEYpaNklIzSEfLfD4qNnXAJSFWfhO7w2Zc0Tf9QG2rXrEzSUeZLOGm47WoH95x3Hl3jEgilvDgsOQUTqsNl68ezEwn1vae6lceSEEYvvWNT/Rxc4CXQNGadho1NXoJ+9iaqc2xi2xbt23PJCDIB6TQjOC6Bho/sDy3fBQT8PrVhibU7yBFcEPaRxOoeTwbwByCOYf9VGp1BYI1BA+EeHhmfzKbBoJEQwn1yzUZtyspIQUha85MpkNIXB7GizqDEECsAAAAASUVORK5CYII=",
          "scale": 1.5,
          "show": true
      },
      "availability": "2022-03-22T16:08:00+08:00/2022-03-23T16:08:00+08:00",
      "path": {
          "leadTime": [
              {
                  "interval": "2022-03-22T16:08:00+08:00/2022-03-22T17:35:38.857645+08:00",
                  "number": [
                      0,
                      40570.57117734112,
                      40570.57117734112,
                      0
                  ],
                  "epoch": "2022-03-22T16:08:00+08:00"
              },
              {
                  "interval": "2022-03-22T17:35:38.857645+08:00/2022-03-23T04:51:49.428822+08:00",
                  "number": [
                      0,
                      40570.57117734112,
                      40570.57117734112,
                      0
                  ],
                  "epoch": "2022-03-22T17:35:38.857645+08:00"
              },
              {
                  "interval": "2022-03-23T04:51:49.428822+08:00/2022-03-23T16:07:59.999999+08:00",
                  "number": [
                      0,
                      40570.57117734112,
                      40570.57117734112,
                      0
                  ],
                  "epoch": "2022-03-23T04:51:49.428822+08:00"
              }
          ],
          "width": 1,
          "trailTime": [
              {
                  "interval": "2022-03-22T16:08:00+08:00/2022-03-22T17:35:38.857645+08:00",
                  "number": [
                      0,
                      0,
                      40570.57117734112,
                      40570.57117734112
                  ],
                  "epoch": "2022-03-22T16:08:00+08:00"
              },
              {
                  "interval": "2022-03-22T17:35:38.857645+08:00/2022-03-23T04:51:49.428822+08:00",
                  "number": [
                      0,
                      0,
                      40570.57117734112,
                      40570.57117734112
                  ],
                  "epoch": "2022-03-22T17:35:38.857645+08:00"
              },
              {
                  "interval": "2022-03-23T04:51:49.428822+08:00/2022-03-23T16:07:59.999999+08:00",
                  "number": [
                      0,
                      0,
                      40570.57117734112,
                      40570.57117734112
                  ],
                  "epoch": "2022-03-23T04:51:49.428822+08:00"
              }
          ],
          "material": {
              "solidColor": {
                  "color": {
                      "rgba": [
                          "213",
                          "255",
                          "0",
                          255
                      ]
                  }
              }
          },
          "resolution": 120,
          "show": [
              {
                  "interval": "2022-03-22T16:08:00+08:00/2022-03-23T16:08:00+08:00",
                  "boolean": true
              }
          ]
      }
  }


czml_vue


<template>
  <div class="ctx">
    <div id="cesiumContainer" ref="cesiumContainer"></div>
  </div>
</template>
  <script setup>
import * as Cesium from "cesium";
import { onMounted, ref } from "vue";
let viewer;
const cesiumContainer = ref("cesiumContainer");

onMounted(async () => {
  viewer = new Cesium.Viewer("cesiumContainer", {
    // animation: false, // 是否开启动画
    // timeline: false, // 是否显示时间轴
    // imageryProvider: new Cesium.UrlTemplateImageryProvider({
    //   url: "https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
    // }),
  });

  let { czml_team } = getdata();

  let entityB2, onTickEvent;

  let cdata = await Cesium.CzmlDataSource.load(czml_team);
  // 加载数据
  viewer.dataSources.add(cdata);

  entityB2 = cdata.entities.getById("flying_follow_team");
  viewer.clock.shouldAnimate = true;

  viewer.trackedEntity = entityB2;

  // 获取模型对象

  // 获取当前模型方向和位置
  const orientation = entityB2.orientation;
  const position = entityB2.position;

  console.log(position, orientation);
  
  console.log(entityB2);
  entityB2.model.runAnimations = true;
  console.log(entityB2.model);
  (entityB2.model.silhouetteColor = getColor("Red", 1.0)), //> 模型轮廓颜色
    (entityB2.model.silhouetteSize = parseFloat(2));
  // 实时调整位置

  // entityB2.orientation  = new Cesium.VelocityOrientationProperty(entityB2.position);

  /*
      // 添加属性:速度向量
        entityB2.velocityVector = new Cesium.VelocityVectorProperty(entityB2.position, true);

        // 当前时刻速度向量、位置
        let curVelocityVector = entityB2.velocityVector.getValue(viewer.clock.currentTime, new Cesium.Cartesian3());
        let curPosition = entityB2.position.getValue(viewer.clock.currentTime, new Cesium.Cartesian3());
        // 计算朝向四元数
        var quaternion = getQuaternion(curPosition, curVelocityVector);
        // 设置实体朝向,验证是否指向速度矢量方向
        entityB2.orientation = quaternion;   
*/
  function adjust() {
    if (viewer.clock.shouldAnimate === true) {
      let ori = orientation.getValue(viewer.clock.currentTime); // 获取偏向角
      let center = position.getValue(viewer.clock.currentTime); // 获取位置
      console.log(ori, center);

      /*
                   // 1、由四元数计算三维旋转矩阵
                  var mtx3 = Cesium.Matrix3.fromQuaternion(ori);

                  // 2、计算四维转换矩阵:
                  var mtx4 = Cesium.Matrix4.fromRotationTranslation(mtx3, center);

                  // 3、计算角度:
                  var hpr = Cesium.Transforms.fixedFrameToHeadingPitchRoll(mtx4);

                  // 获取角度(弧度)
                    const headingTemp = hpr.heading;
                    const pitchTemp = hpr.pitch;

                    // 调整角度为第一人称
                    const heading = Cesium.Math.toRadians(
                      Cesium.Math.toDegrees(headingTemp) + 90
                    );
                    const pitch = Cesium.Math.toRadians(
                      Cesium.Math.toDegrees(pitchTemp) - 12
                    );
                    // 视角高度,根据模型大小调整
                    const range = 140.0;

                    // 动态改变模型视角

                    viewer.camera.lookAt(
                      center,
                      new Cesium.HeadingPitchRange(heading, pitch, range)
                    );
*/

      entityB2.velocityVector = new Cesium.VelocityVectorProperty(
        entityB2.position,
        true
      );
      // 当前时刻速度向量、位置
      let curVelocityVector = entityB2.velocityVector.getValue(
        viewer.clock.currentTime,
        new Cesium.Cartesian3()
      );
      let curPosition = entityB2.position.getValue(
        viewer.clock.currentTime,
        new Cesium.Cartesian3()
      );
      // 计算朝向四元数
      var quaternion = getQuaternion(curPosition, curVelocityVector);
      // 设置实体朝向,验证是否指向速度矢量方向
      entityB2.orientation = quaternion;

      // entityB2.orientation = new Cesium.VelocityOrientationProperty(
      //   entityB2.position
      // );
    }
  }
  onTickEvent = viewer.clock.onTick.addEventListener(adjust);
});

/**
 * 计算朝向四元数
 * X轴正向指向运动方向;Y轴在水平面内垂直于X轴,正向指向右侧;Z轴通过右手法则确定
 * @param {Cartesian3} position 位置
 * @param {Cartesian3} velocity 速度向量
 * @param {*} rotateX 绕X轴旋转的角度(roll)
 * @param {*} rotateY 绕Y轴旋转的角度(pitch)
 * @param {*} rotateZ 绕Z轴旋转的角度(heading)
 * @returns
 */
function getQuaternion(position, velocity, rotateX, rotateY, rotateZ) {
  // 1、计算站心到模型坐标系的旋转平移矩阵
  // 速度归一化
  let normal = Cesium.Cartesian3.normalize(velocity, new Cesium.Cartesian3());
  // 计算模型坐标系的旋转矩阵
  let satRotationMatrix = Cesium.Transforms.rotationMatrixFromPositionVelocity(
    position,
    normal,
    Cesium.Ellipsoid.WGS84
  );
  // 模型坐标系到地固坐标系旋转平移矩阵
  let m = Cesium.Matrix4.fromRotationTranslation(satRotationMatrix, position);
  // 站心坐标系(东北天坐标系)到地固坐标系旋转平移矩阵
  var m1 = Cesium.Transforms.eastNorthUpToFixedFrame(
    position,
    Cesium.Ellipsoid.WGS84,
    new Cesium.Matrix4()
  );
  // 站心到模型坐标系的旋转平移矩阵
  let m3 = Cesium.Matrix4.multiply(
    Cesium.Matrix4.inverse(m1, new Cesium.Matrix4()),
    m,
    new Cesium.Matrix4()
  );

  // 2、模型姿态旋转矩阵
  rotateX = rotateX || 0;
  rotateY = rotateY || 0;
  rotateZ = rotateZ || -90;
  let heading = rotateZ,
    pitch = rotateY,
    roll = rotateX;

  let postureHpr = new Cesium.HeadingPitchRoll(
    Cesium.Math.toRadians(heading),
    Cesium.Math.toRadians(pitch),
    Cesium.Math.toRadians(roll)
  );
  let postureMatrix = Cesium.Matrix3.fromHeadingPitchRoll(postureHpr);

  // 3、最终的旋转矩阵
  let mat3 = Cesium.Matrix4.getMatrix3(m3, new Cesium.Matrix3());
  let finalMatrix = Cesium.Matrix3.multiply(
    mat3,
    postureMatrix,
    new Cesium.Matrix3()
  );
  let quaternion1 = Cesium.Quaternion.fromRotationMatrix(finalMatrix);
  let hpr = Cesium.HeadingPitchRoll.fromQuaternion(quaternion1);
  let q2 = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
  return q2;
}
function getdata() {
  const czml_team = [
    {
      id: "document",
      name: "flying_follow_team",
      version: "1.0",
      clock: {
        interval: "2023-03-08T10:00:00Z/2023-03-08T12:00:00Z",
        currentTime: "2023-03-08T10:00:00Z",
        multiplier: 10,
      },
    },
    {
      id: "flying_follow_team",
      name: "path with GPS flight data",
      description: "测试第一人称视角。",
      // 可用性
      availability: "2023-03-08T10:00:00Z/2023-03-08T12:00:00Z",
      path: {
        material: {
          polylineGlow: {
            color: {
              rgba: [0, 0, 255, 200],
            },
            glowPower: 0.1,
            taperPower: 0.1,
          },
        },
        width: 20,
        // 向前路线
        leadTime: 10,
        trailTime: 1000,
        resolution: 0.5,
        show: true,
      },
      model: {
        // 模型参数
        gltf: "/b22.glb",
        minimumPixelSize: 1000,
        maximumScale: 20,
      },

      orientation: {
        // 自动计算方向
        velocityReference: "#position",
      },
      position: {
        // 插值算法
        interpolationAlgorithm: "LAGRANGE",
        interpolationDegree: 5,
        epoch: "2023-03-08T10:00:00Z",
        // 坐标组
        cartographicDegrees: [
          0, 118.93830177292894, 25.488280583435404, 0,

          300, 119.14034602637892, 25.32388938213355, 2000,

          800, 119.43064375816327, 25.230148210056235, 5000, 1500,
          120.93105921868252, 24.769194048014963, 12000, 2500,
          121.5592902752412, 24.658964292017885, 12000, 3500,
          121.56445881860067, 25.16649023047563, 5000,

          4500, 119.94263373897657, 25.49632056739945, 12000,

          5400, 119.30910179629008, 25.559938450361965, 5000,

          6300, 118.96295053426707, 25.571485127594467, 0,

          7200, 114, 26, 300,
        ],
      },
      // orientation : new Cesium.VelocityOrientationProperty(position),
    },
  ];

  return {
    czml_team,
  };
}

//-----------------------下面是获取颜色方法------------------
//> 获取模型颜色与透明度
function getColor(colorName, alpha) {
  const color = Cesium.Color[colorName.toUpperCase()];
  return Cesium.Color.fromAlpha(color, parseFloat(alpha));
}
//> 获取目标颜色和图元的源颜色之间混合的不同模式  HIGHLIGHT将源颜色乘以目标颜色 REPLACE将源颜色替换为目标颜色 MIX将源颜色和目标颜色混合在一起
function getColorBlendMode(colorBlendMode) {
  return Cesium.ColorBlendMode[colorBlendMode.toUpperCase()];
}
</script>
  
  <style scoped>
.ctx {
  box-sizing: border-box;
  width: 100%;
  height: 100%;
}
</style>
   
<!-- 


  let geo = await Cesium.GeoJsonDataSource.load(
    "/src/assets/hbeiprovince.json",
    {
      // fill: Cesium.Color.PINK, //填充色
      stroke: Cesium.Color.HOTPINK, //轮廓颜色
      strokeWidth: 5, //轮廓宽度
    }
  );

  geo.entities.values.forEach((enetity) => {
     enetity.polygon.outlineColor = Cesium.Color.RED;
    enetity.polygon.material = Cesium.Color.BLUE;
    enetity.polygon.height = 1000;
    enetity.polygon.extrudedHeight = 2000;
  });

  viewer.dataSources.add(geo);

  let { czml_team } = getdata();


 -->