Bootstrap

Cesium 卫星波束实现及跟随

有三种方式可以实现,效果如图:

1、原生方法实现,存在局限性,不支持棱柱波形

// 使用entity创建model和cylinder,通过new Cesium.CallbackProperty()方法来动态返回圆锥位置,实现波束跟随

const moveRedCone = viewer.entities.add({
  model: {
    uri: 'xxx.glb',
    minimumPixelSize: 120,
    maximumScale: 4800,
  },
  name: "Red cone",
  position: new Cesium.CallbackProperty((time) => {
    return Cesium.Cartesian3.fromDegrees(-105.0, 40.0, 200000.0);
  }),
  cylinder: {
    length: 400000.0,
    topRadius: 0.0,
    bottomRadius: 200000.0,
    material: Cesium.Color.RED,
  },
});

2、引用外部插件绘制

kaktus40/cesium-sensors 、AnalyticalGraphicsInc/cesium-sensorsjlouns/cesium-sensor-volumesFlowm/cesium-sensor-volumes

 例如:Flowm/cesium-sensor-volumes/examples/api.html

CesiumSensorVolumes是全局构造函数,主要通过CesiumSensorVolumes.modelMatrix绑定模型,使波束与模型关联

<!DOCTYPE html>
<html lang="en">
<head>
	<!-- Use correct character set. -->
	<meta charset="utf-8">
	<!-- Tell IE to use the latest, best version (or Chrome Frame if pre-IE11). -->
	<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
	<!-- Make the application on mobile take up the full browser screen and disable user scaling. -->
	<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
	<title>Cesium Sensor Volumes Example</title>

	<link rel="stylesheet" href="../node_modules/cesium/Build/Cesium/Widgets/widgets.css" />
	<script src="../node_modules/cesium/Build/Cesium/Cesium.js"></script>
	<script src="../dist/cesium-sensor-volumes.js"></script>

	<style>
		#cesiumContainer {
			position: absolute;
			top: 0;
			left: 0;
			height: 100%;
			width: 100%;
			margin: 0;
			overflow: hidden;
			padding: 0;
			font-family: sans-serif;
		}

		html {
			height: 100%;
		}

		body {
			padding: 0;
			margin: 0;
			overflow: hidden;
			height: 100%;
		}

		#toolbar {
			margin: 5px;
			padding: 2px 5px;
			position: absolute;
		}
	</style>
</head>
<body>
<div id="cesiumContainer"></div>
<div id="toolbar"></div>
<script>
	var viewer = new Cesium.Viewer('cesiumContainer', {
		sceneModePicker : false
	});

	var longitude = Cesium.Math.toRadians(-90.0);
	var latitude = Cesium.Math.toRadians(30.0);
	var altitude = 3000000.0;
	var clock = 0.0;
	var cone = Cesium.Math.toRadians(15.0);
	var twist = 0.0;

	function getModelMatrix() {
		var ellipsoid = viewer.scene.globe.ellipsoid;
		var location = ellipsoid.cartographicToCartesian(new Cesium.Cartographic(longitude, latitude, altitude));
		var modelMatrix = Cesium.Transforms.northEastDownToFixedFrame(location);
		var orientation = Cesium.Matrix3.multiply(
			Cesium.Matrix3.multiply(Cesium.Matrix3.fromRotationZ(clock), Cesium.Matrix3.fromRotationY(cone), new Cesium.Matrix3()),
			Cesium.Matrix3.fromRotationX(twist), new Cesium.Matrix3()
		);
		return Cesium.Matrix4.multiply(modelMatrix, Cesium.Matrix4.fromRotationTranslation(orientation, Cesium.Cartesian3.ZERO), new Cesium.Matrix4());
	}

	function addRectangularSensor() {
		viewer.scene.primitives.removeAll();
		var rectangularPyramidSensor = new CesiumSensorVolumes.RectangularPyramidSensorVolume();

		rectangularPyramidSensor.modelMatrix = getModelMatrix();
		rectangularPyramidSensor.radius = 20000000.0;
		rectangularPyramidSensor.xHalfAngle = Cesium.Math.toRadians(40.0);
		rectangularPyramidSensor.yHalfAngle = Cesium.Math.toRadians(20.0);

		rectangularPyramidSensor.lateralSurfaceMaterial = Cesium.Material.fromType('Color');
		rectangularPyramidSensor.lateralSurfaceMaterial.uniforms.color = new Cesium.Color(0.0, 1.0, 1.0, 0.5);
		viewer.scene.primitives.add(rectangularPyramidSensor);
	}

	function addCustomSensor() {
		viewer.scene.primitives.removeAll();
		var customSensor = new CesiumSensorVolumes.CustomSensorVolume();

		var directions = [];
		for (var i = 0; i < 8; ++i) {
			var clock = Cesium.Math.toRadians(45.0 * i);
			var cone = Cesium.Math.toRadians(25.0);
			directions.push(new Cesium.Spherical(clock, cone));
		}

		customSensor.modelMatrix = getModelMatrix();
		customSensor.radius = 20000000.0;
		customSensor.directions = directions;
		viewer.scene.primitives.add(customSensor);
	}

	function addToolbarButton(text, onclick) {
		var button = document.createElement('button');
		button.type = 'button';
		button.className = 'cesium-button';
		button.onclick = onclick;
		button.textContent = text;
		document.getElementById('toolbar').appendChild(button);
	}

	addToolbarButton('Rectangular', addRectangularSensor);
	addToolbarButton('Custom', addCustomSensor);

	addRectangularSensor();
</script>
</body>
</html>

稍微改写一下,适用于项目

function addSensor(sate_id) {
  const color = "#00ffff";
  let customSensor = new CesiumSensorVolumes.RectangularPyramidSensorVolume();
  const sate = tasks.value.find((item) => item.satId === sate_id);

  // radius 是指波束的长度
  customSensor.radius = 40000000.0;
  customSensor.id = `${sate_id}_sensor`;
  customSensor.intersectionWidth = 1;

  customSensor.xHalfAngle = Cesium.Math.toRadians(1);
  customSensor.yHalfAngle = Cesium.Math.toRadians(1);
  customSensor.lateralSurfaceMaterial = Cesium.Material.fromType("Color");
  customSensor.lateralSurfaceMaterial.uniforms.color =
    new Cesium.Color.fromCssColorString(color).withAlpha(0.3);
  // 默认矩阵
  customSensor.modelMatrix = new Cesium.Matrix4();
  // 使用preRender 监听卫星每帧运动
  viewer.scene.preRender.addEventListener((scene, time) => {
    customSensor.show = false;
    // 判断数据源中所有实体是否准备就绪,viewer.dataSourceDisplay.dataSources = viewer.dataSources
    if (viewer.dataSourceDisplay.ready) {
      const satellite = viewer.dataSources
        .getByName("simDemon")[0]
        .entities.getById(sate_id);
      // 根据时间获取卫星实时笛卡尔位置
      let position = Cesium.Property.getValueOrUndefined(
        satellite.position,
        time,
        new Cesium.Cartesian3()
      );
      // 根据卫星位置和朝向转换为矩阵信息,此处使用的是后台计算的四元数,实际一般情况应该使用Cesium.Transforms+position转换
      let m = Cesium.Matrix4.fromRotationTranslation(
        Cesium.Matrix3.fromQuaternion(
          satellite.orientation_c.getValue(time),
          new Cesium.Matrix3()
        ),
        position,
        new Cesium.Matrix4()
      );
      customSensor.modelMatrix = m;
      customSensor.show = true;
    }
  });
  viewer.scene.primitives.add(customSensor);
}

关于四元数前端也可以通过 地固坐标计算得到,跟stk模拟的位置有时会有偏差,这是因为精度丢失问题,参考链接:精度丢失导致扭曲与抖动

demo: sample看上一篇文章Cesium 利用path实现卫星轨道

// w,x,y,z是四元数四个参数,是后台计算数据
function addSate({ sate_name, sate_id, sample, a, tle2 }) {
  // console.log(sample, a, tle2);
  // 默认均为惯性系
  let samp_icrf = new Cesium.SampledPositionProperty(
    Cesium.ReferenceFrame.INERTIAL
  );
  samp_icrf.setInterpolationOptions({
    interpolationDegree: 5,
    interpolationAlgorithm: Cesium.LagrangePolynomialApproximation,
  });

  let samp_q = new Cesium.SampledProperty(Cesium.Quaternion);
  samp_q.setInterpolationOptions({
    interpolationDegree: 1,
    interpolationAlgorithm: Cesium.LinearApproximation,
  });

  sample.forEach(
    (
      {
        epoch,
        x_ICRF,
        y_ICRF,
        z_ICRF,
        x_ECF,
        y_ECF,
        z_ECF,
        vx_ECF,
        vy_ECF,
        vz_ECF,
        w,
        x,
        y,
        z,
      },
      index
    ) => {
      const time = Cesium.JulianDate.fromDate(new Date(epoch));
      samp_icrf.addSample(time, new Cesium.Cartesian3(x_ICRF, y_ICRF, z_ICRF));

      // samp_ecf.addSample(time, new Cesium.Cartesian3(x_ECF, y_ECF, z_ECF));
      // samp_ecf_v.addSample(time, new Cesium.Cartesian3(vx_ECF, vy_ECF, vz_ECF));

      let hpr = getQuaternion(
          new Cesium.Cartesian3(x_ECF, y_ECF, z_ECF),
          new Cesium.Cartesian3(vx_ECF, vy_ECF, vz_ECF)
      );
      // 经过测试,需要使用惯性系转换成为四元数,不是文章中的地固系坐标
      const q = Cesium.Transforms.headingPitchRollQuaternion(
          samp_icrf.getValue(time),
          hpr
      );
      samp_q.addSample(time, new Cesium.Quaternion(q.x, q.y, q.z, q.w));
    }
  );
  let options = {
    id: sate_id,
    model: {
      uri: "model/wide-simp.glb",
      minimumPixelSize: 120,
      maximumScale: 4800,
      skipLevelOfDetail: true,
      immediatelyLoadDesiredLevelOfDetail: true,
      runAnimations: false,
      incrementallyLoadTextures: false,
    },
    label: {
      text: sate_name,
      font: "normal 30px 楷体",
      fillColor: Cesium.Color.fromCssColorString("#ffffff"),
      outlineColor: Cesium.Color.fromCssColorString("#000000"),
      outlineWidth: 3,
      pixelOffset: new Cesium.Cartesian2(0, -20), // 偏移量
      showBackground: true,
      backgroundColor:
        Cesium.Color.fromCssColorString("#000000").withAlpha(0.5),
      scaleByDistance: new Cesium.NearFarScalar(100000, 1.0, 10000000, 0.4),
      backgroundPadding: new Cesium.Cartesian2(10, 10), //指定以像素为单位的水平和垂直背景填充padding
      pixelOffsetScaleByDistance: new Cesium.NearFarScalar(
        100000,
        3.5,
        30000000,
        1
      ),
    },
    position: samp_icrf,
    orientation: new Cesium.CallbackProperty((time) => {
      const position = samp_icrf.getValue(time);
      const hpr = new Cesium.HeadingPitchRoll(
        Cesium.Math.toRadians(80),
        Cesium.Math.toRadians(-90),
        0
      );
      return Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
    }, false),
    orientation_c: samp_q,
    path: {
      resolution: 60,
      material: Cesium.Color.GREEN,
      leadTime: a ? calcT(a) : calcTByTle2(tle2),
      trailTime: a ? calcT(a) : calcTByTle2(tle2),
      width: 1,
    },
  };
  viewer.entities.add(options);
}


/**
 * 计算朝向四元数
 * 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(positionEcf, velocityEcf) {
  // 1、计算站心到模型坐标系的旋转平移矩阵
  // 速度归一化
  let normal = Cesium.Cartesian3.normalize(
    velocityEcf,
    new Cesium.Cartesian3()
  );
  // 计算模型坐标系的旋转矩阵
  let satRotationMatrix = Cesium.Transforms.rotationMatrixFromPositionVelocity(
    positionEcf,
    normal,
    Cesium.Ellipsoid.WGS84
  );
  // 模型坐标系到地固坐标系旋转平移矩阵
  let m = Cesium.Matrix4.fromRotationTranslation(
    satRotationMatrix,
    positionEcf
  );
  // 站心坐标系(东北天坐标系)到地固坐标系旋转平移矩阵
  var m1 = Cesium.Transforms.eastNorthUpToFixedFrame(
    positionEcf,
    Cesium.Ellipsoid.WGS84,
    new Cesium.Matrix4()
  );
  // 站心到模型坐标系的旋转平移矩阵
  let m3 = Cesium.Matrix4.multiply(
    Cesium.Matrix4.inverse(m1, new Cesium.Matrix4()),
    m,
    new Cesium.Matrix4()
  );

  // 2、模型姿态旋转矩阵(根据实际需要)
  let h1 = 0,
    p1 = 3.33992,
    r1 = -11.8216;
  let postureHpr = new Cesium.HeadingPitchRoll(
    Cesium.Math.toRadians(h1),
    Cesium.Math.toRadians(p1),
    Cesium.Math.toRadians(r1)
  );
  let postureMatrix = Cesium.Matrix3.fromHeadingPitchRoll(postureHpr);

  // 3、模型朝向旋转矩阵
  let h2 = 0,
    p2 = -180,
    r2 = 0;
  let sHpr = new Cesium.HeadingPitchRoll(
    Cesium.Math.toRadians(h2),
    Cesium.Math.toRadians(p2),
    Cesium.Math.toRadians(r2)
  );
  let sMatrix = Cesium.Matrix3.fromHeadingPitchRoll(sHpr);

  // 4、最终的旋转矩阵
  let mat3 = Cesium.Matrix4.getMatrix3(m3, new Cesium.Matrix3());
  let finalMatrix = Cesium.Matrix3.multiply(
    mat3,
    postureMatrix,
    new Cesium.Matrix3()
  );
  let finalMatrix1 = Cesium.Matrix3.multiply(
    finalMatrix,
    sMatrix,
    new Cesium.Matrix3()
  );

  let quaternion1 = Cesium.Quaternion.fromRotationMatrix(finalMatrix1);
  let hpr = Cesium.HeadingPitchRoll.fromQuaternion(quaternion1);
  return hpr;
  // let q2 = Cesium.Transforms.headingPitchRollQuaternion(positionEcf, hpr);
  // return q2;
}

参考: 【Cesium】计算模型的朝向四元数,实现模型运动中调整朝向

3、使用cesium-ion 自带的ConicSensor、RectangularSensor构造函数

用法是一样的,只是CesiumSensorVolumes对象换成了Cesium对象

总结: 波束这块比较难的原因是涉及到调资,包括卫星调资,载荷调资,轨道倾角,这些合起来都会影响到波束,而计算也不是简单相加相减,需要进行矩阵合并计算,会涉及到向量,同时,需要与stk保持一致的话,需要跟stk使用同一个参考系,反正是挺多的 STK中的姿态设置与应用icon-default.png?t=N7T8https://www.renrendoc.com/paper/271017959.html

上面三种方法的话,插件的话,只有 AnalyticalGraphicsInc/cesium-sensors对二维支持的比较好,其它的切换到二维都是没有波束了,Cesium-ion的话是不能在商用版本使用的,对二维支持的比较好,可以按照情况按需选择。

;