有三种方式可以实现,效果如图:
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-sensors、jlouns/cesium-sensor-volumes、Flowm/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中的姿态设置与应用https://www.renrendoc.com/paper/271017959.html
上面三种方法的话,插件的话,只有 AnalyticalGraphicsInc/cesium-sensors对二维支持的比较好,其它的切换到二维都是没有波束了,Cesium-ion的话是不能在商用版本使用的,对二维支持的比较好,可以按照情况按需选择。