cesium已知起点、终点的经纬度,绘制向上的贝塞尔曲线
时间: 2025-08-03 18:23:14 浏览: 15
<think>我们已知起点和终点的经纬度,需要在Cesium中绘制一条向上的贝塞尔曲线(即拱形曲线)。由于Cesium本身没有直接提供绘制贝塞尔曲线的函数,我们需要自己计算贝塞尔曲线的控制点,然后使用折线(或实体)来绘制。贝塞尔曲线通常使用多个控制点。对于一条三次贝塞尔曲线,需要4个点:起点、两个控制点、终点。为了形成向上的曲线,我们需要在起点和终点之间的中点上方添加控制点。具体步骤:1.将起点和终点的经纬度转换为笛卡尔坐标(Cartesian3)。2.计算起点和终点之间的中点(在笛卡尔坐标系下),然后将该点沿法线方向(即地球表面向上的方向)升高一定高度,作为中间的控制点。实际上,对于三次贝塞尔曲线,我们需要两个控制点。常见的做法是:将起点到终点的方向向量,然后分别从起点和终点沿该方向的一定比例处升高作为控制点。但是,我们也可以使用二次贝塞尔曲线(三个点:起点、一个控制点、终点),这样更简单。不过Cesium的折线不支持直接绘制贝塞尔曲线,所以我们需要通过采样点来模拟曲线。这里我们选择三次贝塞尔曲线,因为它更灵活。我们可以这样设置控制点:控制点1:起点沿着起点到终点的方向移动1/3的距离,然后向上提升一定高度。控制点2:起点沿着起点到终点的方向移动2/3的距离,然后向上提升一定高度。另一种方法是:取起点和终点的中点,然后将其提升到一定高度作为中间的控制点(二次贝塞尔),然后采样点。由于Cesium中绘制曲线需要一系列点,因此我们将曲线离散成多个点,然后用这些点连成折线。算法:1.将起点(lon1,lat1)和终点(lon2,lat2)转换为Cartesian3:startCartesian,endCartesian。2.计算中点,并将其向上抬升作为控制点(对于二次贝塞尔曲线):-先求起点和终点的笛卡尔坐标的中点:midCartesian=(startCartesian+endCartesian)/2-然后,将中点沿法线方向(即归一化后的midCartesian方向)提升一定高度,得到控制点controlCartesian。但是,对于三次贝塞尔曲线,我们需要两个控制点。我们可以这样:-将起点到终点的向量分为三段,在1/3和2/3处设置控制点,并抬高。实际上,我们可以这样:controlPoint1=从起点沿起点到终点方向移动1/3距离的点,然后抬高。controlPoint2=从起点沿起点到终点方向移动2/3距离的点,然后抬高。然而,在三维空间中,我们需要保证控制点在地球上方。另一种思路是在起点和终点的连线上的两个点(比如1/3和2/3处)抬高作为控制点。具体步骤(使用三次贝塞尔曲线):1.将起点和终点转换为笛卡尔坐标。2.计算起点到终点的向量:direction=endCartesian-startCartesian。3.计算两个控制点:controlPoint1=startCartesian+direction*(1/3)+normalVector*height1;controlPoint2=startCartesian+direction*(2/3)+normalVector*height2;其中,normalVector可以是地球表面在该点的法向量(即归一化的Cartesian3向量),但注意:在起点和终点处的法向量可能不同。我们可以用起点、终点和中间点的法向量?为了简单,我们可以使用中点的法向量,即归一化的(startCartesian+endCartesian)向量,然后乘以一个高度。但是,这样计算出的点可能不在球面上。我们可以在笛卡尔坐标系中做插值,然后抬高,这里抬高就是沿着该点的法线方向(即该点的归一化向量)乘以一个高度增量。4.使用三次贝塞尔曲线公式,在0到1之间取多个t值,计算曲线上的点:B(t)=(1-t)^3*P0+3*(1-t)^2*t*P1+3*(1-t)*t^2*P2+t^3*P3其中P0是起点,P3是终点,P1和P2是控制点。5.将计算出的点(笛卡尔坐标数组)添加到Cesium的折线或实体中。注意:我们抬高控制点的时候,抬高的高度应该是相对于地球表面的高度。但是起点和终点在地球表面,所以控制点的高度应该是一个相对高度(例如比起点终点高1000米)。步骤详细:步骤1:转换起点和终点startCartographic=Cesium.Cartographic.fromDegrees(lon1,lat1)startCartesian=Cesium.Cartesian3.fromRadians(startCartographic.longitude,startCartographic.latitude,startCartographic.height)同理,终点转换。步骤2:计算中点(在笛卡尔坐标系下)并计算中点的法线方向midCartesian=Cesium.Cartesian3.lerp(startCartesian,endCartesian,0.5,newCesium.Cartesian3());midNormal=Cesium.Cartesian3.normalize(midCartesian,newCesium.Cartesian3());步骤3:计算两个控制点(这里我们使用三次贝塞尔,需要两个控制点)我们选择在1/3和2/3处抬高://1/3处的点temp1=Cesium.Cartesian3.lerp(startCartesian,endCartesian,1/3,newCesium.Cartesian3());//然后沿着该点的法线方向(其实这里我们近似使用midNormal)抬高,因为严格来说每个点都有自己的法线,但为了简单,使用同一个法线方向。controlPoint1=Cesium.Cartesian3.add(temp1,Cesium.Cartesian3.multiplyByScalar(midNormal,height,newCesium.Cartesian3()),newCesium.Cartesian3());//2/3处的点temp2=Cesium.Cartesian3.lerp(startCartesian,endCartesian,2/3,newCesium.Cartesian3());controlPoint2=Cesium.Cartesian3.add(temp2,Cesium.Cartesian3.multiplyByScalar(midNormal,height,newCesium.Cartesian3()),newCesium.Cartesian3());注意:height是抬高的高度(单位:米)。但这里temp1和temp2的坐标是笛卡尔坐标,而height是相对于地球表面的高度?实际上,temp1和temp2的位置在地球表面(高度0),所以我们直接加上height*midNormal,因为midNormal是单位向量,乘以height就是抬高height米。步骤4:采样贝塞尔曲线点t从0到1,步长0.01,计算贝塞尔曲线的点。步骤5:创建折线viewer.entities.add({polyline:{positions:positions,//步骤4计算的笛卡尔坐标数组width:2.0,material:Cesium.Color.RED}});但是,这里有一个问题:我们使用笛卡尔坐标的线性插值(lerp)得到的1/3和2/3处的点,是否是在地球表面?实际上,我们之前将起点和终点转换到笛卡尔坐标时,高度为0(即在地球表面)。而lerp是在两个笛卡尔坐标之间线性插值,得到的点也在地球表面上(因为笛卡尔坐标系中,从球面一点到另一点的直线段并不在地球表面,而是穿越地球内部?)。所以,我们实际上需要的是沿地球表面的曲线?但是用户要求向上的拱形,所以实际上我们不需要沿着地表,而是直线段的上方拱起。我们当前的方法是在直线段(连接起点和终点的直线)的上方抬高,这样形成的曲线是在一个平面内的拱形。但是,由于地球是球体,如果起点和终点距离很远,直线段会穿越地球内部,而我们希望拱形是在地球外部,所以我们应该沿大圆路径(测地线)上方拱起?这样更符合实际情况。然而,计算测地线上的点然后抬高,会复杂一些。由于用户要求不高,我们可以先使用直线段上方拱起。如果距离不远,效果可以接受。另外,如果我们希望曲线完全在地球外部,我们可以这样做:1.计算起点和终点之间的测地线(大圆)路径,并采样多个点(这些点在地球表面)。2.将这些采样点抬高到某个高度(比如10000米),形成一条高空的大圆路径。3.然后在这条高空路径上应用贝塞尔曲线?或者直接使用这些点作为路径?但是题目要求是贝塞尔曲线,所以我们可以将起点、终点和中间的采样点作为控制点,构建更高阶的贝塞尔曲线?这可能会很复杂。这里为了简单,我们还是采用在直线段上方拱起的方法。如果距离较远(比如超过几百公里),可能会看起来不太自然(因为直线穿越地球内部,而拱形曲线也会穿越地球内部)。所以我们还需要将拱形曲线抬高到一定高度,以避免穿越地球内部。一种改进方法是:我们首先将起点和终点都抬高到一定高度(例如10000米),然后在这两个点之间构造贝塞尔曲线,这样整个曲线都在高空,不会穿越地球内部。步骤调整(改进):1.将起点和终点都抬高到同一个高度(比如10000米)。startCartographic.height=10000;startCartesian=Cesium.Cartesian3.fromRadians(startCartographic.longitude,startCartographic.latitude,startCartographic.height);//同理终点2.然后按照之前的步骤计算两个控制点(也在10000米高度上方再抬高,比如15000米?)注意:这样控制点的高度就比起点终点高了5000米。//计算起点终点之间的1/3和2/3处的点(在笛卡尔坐标系中直线插值)//然后将这些点再向上抬高(相对于10000米表面再抬高5000米?)注意:这里我们抬高的参考面并不是地球表面,而是起点终点所在的高度的平面。但是为了简单,我们还是沿法线方向抬高。3.计算贝塞尔曲线的点。这样整个曲线都在10000米以上,不会穿越地球内部。但是,在球面上,沿着法线方向抬高后,实际是在球心到该点连线的延长线上。所以起点、终点、控制点都是在一个球面上(10000米高度球面)或者再高一点。具体代码步骤:步骤1:定义起点和终点的经纬度,并设置一个基础高度(比如10000米)。varstartCartographic=Cesium.Cartographic.fromDegrees(lon1,lat1,10000);varendCartographic=Cesium.Cartographic.fromDegrees(lon2,lat2,10000);varstart=Cesium.Cartesian3.fromRadians(startCartographic.longitude,startCartographic.latitude,startCartographic.height);varend=Cesium.Cartesian3.fromRadians(endCartographic.longitude,endCartographic.latitude,endCartographic.height);步骤2:计算中点(在起点和终点的笛卡尔坐标之间线性插值),然后计算该点的法向量(即归一化的中点向量)varmid=Cesium.Cartesian3.lerp(start,end,0.5,newCesium.Cartesian3());varmidNormal=Cesium.Cartesian3.normalize(mid,newCesium.Cartesian3());步骤3:计算两个控制点(在起点和终点之间的1/3和2/3处,然后沿着midNormal方向再抬高5000米):varcontrolHeight=15000;//因为起点终点在10000米,再抬高5000米达到15000米//计算1/3处的点vartemp1=Cesium.Cartesian3.lerp(start,end,1/3,newCesium.Cartesian3());//计算该点处的实际高度(相对于球心)?我们不需要计算,直接加上抬高向量//但是注意:temp1的位置是在起点和终点连线上,高度并不精确是10000米(因为线性插值在笛卡尔坐标系中,高度并不是恒定的10000米)。所以我们应该先将其投影到10000米高度的球面上?或者,我们可以用另一种方法:将temp1归一化,然后乘以(地球半径+10000米)?这样会保证在10000米高度上。但我们这里为了简单,不再做投影,直接用线性插值的结果,然后加上抬高向量(这样高度会有所变化,但差别不大)。//所以,控制点1:将temp1投影到10000米的球面上?或者,我们直接加一个向量(midNormal*5000)?因为temp1已经在10000米高度附近,我们再加5000米的抬高量,应该达到15000米高度。//然而,这样做并不精确。更精确的做法:将temp1归一化,然后乘以(地球半径+15000米)。但Cesium中,地球半径可以取6378137米。//这里我们采用归一化后乘以半径+高度的方法。varearthRadius=6378137.0;//椭球长半轴,Cesium中默认的椭球是WGS84vartemp1Normal=Cesium.Cartesian3.normalize(temp1,newCesium.Cartesian3());control1=Cesium.Cartesian3.multiplyByScalar(temp1Normal,earthRadius+controlHeight,newCesium.Cartesian3());//同理计算2/3处的点vartemp2=Cesium.Cartesian3.lerp(start,end,2/3,newCesium.Cartesian3());vartemp2Normal=Cesium.Cartesian3.normalize(temp2,newCesium.Cartesian3());control2=Cesium.Cartesian3.multiplyByScalar(temp2Normal,earthRadius+controlHeight,newCesium.Cartesian3());但是,我们注意到:起点和终点我们也是通过同样方式构造的(即用高度乘以法向量),所以起点和终点是精确的。那么我们的控制点应该也是用同样的方式构造。所以,我们不应该使用线性插值得到的temp1,而是应该计算从球心到temp1方向的单位向量,然后乘以(地球半径+目标高度)。然而,我们之前已经将起点和终点转换成了笛卡尔坐标(从球心到表面点的向量乘以半径+高度),所以起点和终点之间的线性插值temp1并不在球面上。因此,我们需要重新计算起点和终点连线上的点在球面上的对应点(即沿着球心到该点的方向与球面相交的点)?这没有必要,因为我们的目标是构建一条平滑的曲线,而不是严格的球面曲线。因此,我们可以简化处理,直接使用线性插值得到的点作为控制点的基础位置,然后归一化并乘以(地球半径+控制点高度)。这样控制点就在球面上方15000米处,但位置与线性插值点在同一方向。步骤4:现在,起点、终点和控制点都在不同高度(起点终点10000米,控制点15000米)。然后我们采样三次贝塞尔曲线。注意:贝塞尔曲线公式是在笛卡尔坐标空间中进行的,所以直接代入公式即可。步骤5:将采样点用于绘制折线。但是,这样绘制出的曲线会穿越地球吗?因为起点和终点在10000米,控制点在15000米,所以曲线整体应该都在10000米以上。接下来,我们编写代码:我们使用Cesium.Entity的折线(polyline)来绘制。折线是由一系列点组成的。代码实现:注意:以下代码仅为示例,可能需要在Cesium环境中运行。```javascriptfunctionaddBezierCurve(viewer,lon1,lat1,lon2,lat2){varearthRadius=6378137.0;//地球椭球长半轴,单位米varbaseHeight=10000;//基础高度(米)varcontrolHeight=15000;//控制点高度(米)//创建起点和终点的Cartographic(弧度制)varstartCartographic=Cesium.Cartographic.fromDegrees(lon1,lat1);varendCartographic=Cesium.Cartographic.fromDegrees(lon2,lat2);//转换为笛卡尔坐标(在baseHeight高度)varstart=Cesium.Cartesian3.fromRadians(startCartographic.longitude,startCartographic.latitude,baseHeight);varend=Cesium.Cartesian3.fromRadians(endCartographic.longitude,endCartographic.latitude,baseHeight);//计算1/3和2/3处的点(在笛卡尔坐标系中线性插值)vartemp1=Cesium.Cartesian3.lerp(start,end,1/3,newCesium.Cartesian3());vartemp2=Cesium.Cartesian3.lerp(start,end,2/3,newCesium.Cartesian3());//归一化并乘以半径+控制点高度,得到控制点varcontrol1=scaleToHeight(temp1,earthRadius+controlHeight);varcontrol2=scaleToHeight(temp2,earthRadius+controlHeight);//采样贝塞尔曲线(三次)varpositions=[];for(vart=0;t<=1;t+=0.01){//三次贝塞尔曲线公式varB=bezier3(t,start,control1,control2,end);positions.push(B);}//添加实体viewer.entities.add({polyline:{positions:positions,width:5,material:Cesium.Color.RED}});}//将一个笛卡尔坐标归一化后乘以指定高度(半径+高度)functionscaleToHeight(cartesian,height){varnormal=Cesium.Cartesian3.normalize(cartesian,newCesium.Cartesian3());returnCesium.Cartesian3.multiplyByScalar(normal,height,newCesium.Cartesian3());}//三次贝塞尔曲线:t在[0,1],P0,P1,P2,P3functionbezier3(t,P0,P1,P2,P3){//B(t)=(1-t)^3*P0+3*(1-t)^2*t*P1+3*(1-t)*t^2*P2+t^3*P3vart1=1-t;vart1_2=t1*t1;vart1_3=t1_2*t1;vart2=t*t;vart3=t2*t;//分别计算四项varpart0=Cesium.Cartesian3.multiplyByScalar(P0,t1_3,newCesium.Cartesian3());varpart1=Cesium.Cartesian3.multiplyByScalar(P1,3*t1_2*t,newCesium.Cartesian3());varpart2=Cesium.Cartesian3.multiplyByScalar(P2,3*t1*t2,newCesium.Cartesian3());varpart3=Cesium.Cartesian3.multiplyByScalar(P3,t3,newCesium.Cartesian3());//相加varresult=Cesium.Cartesian3.add(part0,part1,newCesium.Cartesian3());result=Cesium.Cartesian3.add(result,part2,result);result=Cesium.Cartesian3.add(result,part3,result);returnresult;}```注意:这里我们使用线性插值后归一化再乘以高度的方法得到控制点。这样控制点就会在距离球心(earthRadius+controlHeight)的位置。这样绘制的曲线是一个拱形,但是可能不够平滑(如果起点和终点距离很远,1/3和2/3处的控制点会使得曲线有一个尖峰?)。我们可以尝试调整控制点的高度(比如控制点高度更高,拱形更明显)或者调整控制点的位置(比如使用其他比例的中间点)来改变曲线形状。另外,我们还可以使用二阶贝塞尔曲线(一个控制点):控制点在中点上方贝塞尔公式:B(t)=(1-t)^2*P0+2*(1-t)*t*P1+t^2*P2修改:控制点取中点:mid=(start+end)/2,然后归一化乘以控制点高度(15000米)采样点公式改为二阶代码类似,这里不再重复。以上是一种实现方式。需要注意的是,由于我们使用的是直线插值,所以当起点和终点距离很远时,中间点会在地球内部(但实际上我们使用基础高度10000米,所以不会在地表以下),控制点也在15000米高度,所以整个曲线都在空中。希望这个方案对你有帮助。</think>
在 Cesium 中根据经纬度起点和终点绘制向上拱起的贝塞尔曲线,可以通过以下步骤实现:
### 解决思路
1. **计算中间控制点**:在起点和终点之间选择抬升点(如中点),并沿地球法线方向抬升到指定高度
2. **构建贝塞尔曲线**:使用三次贝塞尔曲线公式进行采样
3. **坐标转换**:使用 Cartographic 和 Cartesian3 进行经纬度与笛卡尔坐标转换
4. **创建实体**:用采样点生成 Polyline 实体
### 代码实现
```javascript
function createBezierArc(startLon, startLat, endLon, endLat, height) {
// 1. 坐标转换
const earthRadius = 6378137.0; // 地球半径 (WGS84)
const startCartographic = Cesium.Cartographic.fromDegrees(startLon, startLat);
const endCartographic = Cesium.Cartographic.fromDegrees(endLon, endLat);
// 2. 转换到笛卡尔坐标
const start = Cesium.Cartesian3.fromRadians(
startCartographic.longitude,
startCartographic.latitude,
earthRadius
);
const end = Cesium.Cartesian3.fromRadians(
endCartographic.longitude,
endCartographic.latitude,
earthRadius
);
// 3. 计算抬升点(中点)
const midPoint = new Cesium.Cartesian3();
Cesium.Cartesian3.lerp(start, end, 0.5, midPoint);
const midNormal = Cesium.Cartesian3.normalize(midPoint, new Cesium.Cartesian3());
const controlPoint = Cesium.Cartesian3.add(
midPoint,
Cesium.Cartesian3.multiplyByScalar(midNormal, height, new Cesium.Cartesian3()),
new Cesium.Cartesian3()
);
// 4. 贝塞尔曲线采样(二次贝塞尔)
const positions = [];
for (let t = 0; t <= 1; t += 0.01) {
// 贝塞尔公式: B(t) = (1-t)²P0 + 2(1-t)tP1 + t²P2
const t1 = 1 - t;
const term0 = Cesium.Cartesian3.multiplyByScalar(start, t1 * t1, new Cesium.Cartesian3());
const term1 = Cesium.Cartesian3.multiplyByScalar(controlPoint, 2 * t1 * t, new Cesium.Cartesian3());
const term2 = Cesium.Cartesian3.multiplyByScalar(end, t * t, new Cesium.Cartesian3());
const point = Cesium.Cartesian3.add(term0, term1, new Cesium.Cartesian3());
Cesium.Cartesian3.add(point, term2, point);
positions.push(point);
}
// 5. 创建曲线实体
viewer.entities.add({
polyline: {
positions: positions,
width: 5,
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.2,
color: Cesium.Color.CYAN
}),
arcType: Cesium.ArcType.NONE
}
});
}
// 调用示例(起点北京,终点上海,抬升高度15km)
createBezierArc(116.4, 39.9, 121.47, 31.23, 15000);
```
### 关键说明
1. **曲线类型**:使用二次贝塞尔曲线(1个控制点)
2. **高度控制**:
- `height` 参数控制拱起高度(单位:米)
- 在法线方向抬升形成自然拱形
3. **采样精度**:
- `t += 0.01` 控制曲线平滑度
- 可根据性能需求调整步长
### 可视化效果
生成的曲线会呈现以下特征:
1. 起点和终点固定在地表
2. 中点在指定高度形成拱形顶点
3. 曲线保持在地球外部空间
4. 可通过材质参数调整颜色和发光效果
### 进阶优化
1. **动态高度**:根据距离自动计算合适高度
```javascript
const distance = Cesium.Cartesian3.distance(start, end);
const autoHeight = distance * 0.2; // 高度为距离的20%
```
2. **三次贝塞尔曲线**:如需更多控制点可扩展为:
```javascript
// 新增控制点
const controlPoint2 = ...
// 三次贝塞尔公式
const t1 = 1 - t;
const B = P0*t1³ + 3*P1*t*t1² + 3*P2*t²*t1 + P3*t³;
```
[^1]: 参考坐标转换原理
阅读全文
相关推荐













