第26篇:空间测量-三角量测——点两下量出的直角三角形,内角和永远是 180°,可地球不是平的

发布时间:2026/10/1 10:27:54
第26篇:空间测量-三角量测——点两下量出的直角三角形,内角和永远是 180°,可地球不是平的 上回书说到我们拿 Cesium 量了方向量了高度量了长度上一站还围了一圈量了面积。回头一看——点有了、线有了、面也有了空间测量这条线就差最后一道收口把这些零散的量装进一个三角形里。还记得第 25 篇结尾本猿立的那个 flag 吗下一站是#26 三角量测面积会拐弯了该收口了——三条边、三个内角、一个周长和一个面积把前面四站的东西在一个三角形里合起来验一遍而那时候你会发现今天那个从哪儿起算的问题会以另一种形式再出现一次。今天——我们来兑现它。回顾一下进度空间测量这条线一共五站#22 方位角 → #23 高度 → #24 距离 → #25 面积 → #26 三角前四站分别解决了朝哪个方向“高多少”“有多远”“这一块地有多大”今天收官把这三件事合到一个三角形里。大家好我是 Cesium 酱也可以叫我本猿一名在 WebGIS 领域摸爬滚打多年的前端开发者。其实四站走下来本猿最大的体会就是一句话任何量测先问量的是哪条路。到了三角形这个问题会再升一级——因为三角形同时牵扯三条边各自量哪条路和三个角在不在同一个平面上。而 Cesium 的measure-lib/geometry.ts里正好躺着两个函数一个管你点两下就能看到的直角三角形一个管任意三点那个更狠的三角形。本文目标用 Cesium 量出一个三角形把 rightTriangleInfodemo 真跑的直角三角形和 triangleInfo库里更一般的任意三角形拆清楚给出实测数据告诉你它们各自的180°和角超是怎么回事并揪出那条本猿认为这条线收官时最该提醒你的一句话——你量出的三角形内角和永远是 180.000000°可地球明明是弯的。 本文涉及的全部代码均已开源完整的仓库地址见文末可自行取用、随意魔改。一、为什么一个三角形是这条线最好的收官 背景现状方位角、高度、距离、面积——前四站拆得很细但它们都有一个共同的收口形状三角形。你量方位角本质是量一条线相对正北的夹角你量高度本质是量一条竖直线的两端你量距离本质是量一条边的长短你量面积本质是把一堆三角形拼起来求和所以最后一站与其再开一个新形状不如把前面四站装进一个三角形里让三条边、三个角、一个周长、一个面积一次性把这趟旅程合订掉。 传统痛点办法问题拿三个点的笛卡尔距离用海伦公式求面积那是三维弦围出来的平面三角形。地球是弯的弦会从地下穿过去和 #24、#25 同一类坑用测地线长度当边、球面三角算角方向对了但 Cesium 的triangleInfo根本不走测地线它直接在 ECEF 里当平面三角算以为直角三角形就真直demo 那个直角只在水平投影里成立真到了 3D曲率会把那个直角掰歪一点拿三点共线当退化保护ECEF 里三点几乎从不完全共线Heron 公式照样给你一个不为零的数 本文方案1️⃣拾取继续走三级降级pickPosition→globe.pick→pickEllipsoid跟第 14、22、23、24、25 篇同一套2️⃣两个函数两副面孔rightTriangleInfodemo 真跑两下点出一个直角三角形/triangleInfo库里更一般任意三点给三边三内角 周长 面积3️⃣把四站归位和180° vs 角超这两个最反直觉的点讲清楚并给出能落地的改法✨ 一句话总结Cesium 给你的三角形是平的——它把三条边当 ECEF 里的直线段在平坦空间里算出内角和永远 180°。地球那个弯被它悄悄塞进了边长里。二、主线两个函数一个直角三角形和一个任意三角形整个核心还是measure-lib/geometry.ts。这一站它并排摆了两个函数// ① rightTriangleInfodemo 真跑的——两下点出一个直角三角形exportfunctionrightTriangleInfo(a:Cartesian3,b:Cartesian3):RightTriangleInfo{constgaCartographic.fromCartesian(a,Ellipsoid.WGS84)constgbCartographic.fromCartesian(b,Ellipsoid.WGS84)constverticalMath.abs(gb.height-ga.height)// 垂直腿 高差的绝对值consthorizontalprojectedDistance(a,b)// 水平腿 贴椭球面的测地线constslopespatialDistance(a,b)// 斜边 三维弦constangleCesiumMath.toDegrees(Math.atan2(vertical,horizontal))// 夹角 坡角// apex抬到较高那一端的海拔让直角出现在水平投影里constapexgb.heightga.height?Cartesian3.fromDegrees(aLon,aLat,gb.height):Cartesian3.fromDegrees(bLon,bLat,ga.height)return{horizontal,vertical,slope,angle,apex}}// ② triangleInfo库里更一般的——任意三点三边三内角 周长 面积exportfunctiontriangleInfo(a:Cartesian3,b:Cartesian3,c:Cartesian3):TriangleInfo{constabspatialDistance(a,b)constbcspatialDistance(b,c)constcaspatialDistance(c,a)constangleAcomputeAngle(bc,ab,ca)// 余弦定理constangleBcomputeAngle(ca,ab,bc)constangleCcomputeAngle(ab,bc,ca)return{sides:[ab,bc,ca],angles:[angleA,angleB,angleC],perimeter:abbcca,area:triangleArea3D(a,b,c)// 海伦公式三维弦}}// 余弦定理已知三边反算夹角functioncomputeAngle(opposite:number,s1:number,s2:number):number{constv(s1*s1s2*s2-opposite*opposite)/(2*s1*s2)returnCesiumMath.toDegrees(Math.acos(Math.max(-1,Math.min(1,v))))}注意一个源码事实rightTriangleInfo里的horizontal调的是projectedDistance而projectedDistance内部会把两端高度强制清零再求EllipsoidGeodesic.surfaceDistance和 #24 发现的投影距离与高度完全无关是同一个机制。所以水平腿报的永远是贴椭球面的测地线长度跟这两点实际离地多高没关系。而triangleInfo更老实——三条边全是spatialDistance纯三维欧氏弦三个角全用余弦定理在平坦的 ECEF 空间里反算。这意味着demo 界面上你点两下看到的是直角三角形rightTriangleInfo但triangleInfo这个能把前四站真正合起来验的更狠的那个demo 压根没把它接上界面。三、这个直角三角形里藏着前四站demo 跑出来的那个直角三角形表面上是水平 / 垂直 / 斜边 / 夹角四个数其实它把前四站一股脑打包了水平腿projectedDistance 第 24 篇的投影距离贴椭球面的测地线。一条边复用了整篇距离量测的结论。垂直腿|Δh| 第 23 篇的高度量测而且是签名里连scene都不带的那个纯椭球高差。斜边spatialDistance 第 24 篇的空间距离纯三维弦。夹角atan2(Δh, 水平) 一个坡角是第 22 篇方位角“两条线差几度”的近亲——#22 量的是这条线朝北偏几度这里量的是这条斜边相对水平面翘几度。面积 ½·水平·垂直 第 25 篇投影面积在直角三角形这种特殊多边形上的解析解#25 的projectedArea对任意多边形跑鞋带直角三角形退化成 ½·底·高。所以你点两下以为在量一个直角三角形实际上是在把方向、高度、长度、面积一次性打包。这恰恰是第 25 篇那个 flag 说的——“把前面四站的东西在’一个三角形’里合起来验一遍”。四、实测把珠峰大本营 → 峰顶摆进去光说结论不过瘾本猿用一组真实坐标跑了一遍珠峰大本营28.14°N, 86.85°E, 5200 m→ 峰顶27.988°N, 86.925°E, 8848 m。实测出来四个数量值说明水平距离测地线18387.554 mprojectedDistance贴 h0 椭球面垂直距离高差3648.000 m8848 − 5200纯椭球高差斜边三维弦18765.863 mspatialDistance夹角坡角11.2215°atan2(3648, 18387.554)第一个反常它不满足勾股定理。按报出来的水平腿算√(3648² 18387.554²) 18745.934 m可斜边实测是 18765.863 m差 19.93 m。不是本猿算错是水平腿报的是测地线、画的是弦——这两根不是同一根线见图 2。珠峰这段跨度小差 19 米等你量几十公里这个差会涨到几十米甚至上百米见第五节坑 2。第二个反常那个直角其实不是 90°。把 A / apex / B 三个点喂给triangleInfo用余弦定理反算apex 处的角是89.917109°不是 90°。因为 apex 构造只保证水平投影里是直角真到了 3Dapex→B 那根弦并不严格垂直于 apex→A 那根半径方向——曲率把它掰歪了 0.083°。跨度越大掰得越狠。第三个反常画出来的水平腿比报的长。apex 和 B 都在 8848 m 高度它们之间那段水平腿实际是 18413.149 m 的三维弦可面板报的是 18387.554 m 的测地线差 25.59 m。所以 demo 里你看到的直角三角形三条边里有两条斜边、水平腿是弦只有水平腿的标签是测地线——它内部不自洽只是平时跨度小看不出来。五、实测出来的坑本猿把这套逻辑逐行复刻成 Node 探针跑了十几组下面这十条是实打实测出来的坑 1勾股定理不严格成立而且往上量和往下量斜边还不一样长。slope²和horizontal² vertical²永远差一截残差量级在 0.007%–0.076% 之间高度差越大、跨度越大差得越多。本猿把残差精确拆开发现它等于2·Δh·sag——sag是较低那端相对较高端切平面的下垂距离。更绝的是同样的高差往上量终点更高和往下量终点更低斜边长度能差 0.0075%–0.078%比如 1° 跨度、高差 1000 m往上 96497.794 m、往下 96482.680 m。因为弦的走向不同。坑 2水平边报的数和画出来的线不是一根。apex 与 B 不在同一高度时面板报的是测地线画的是弦。实测 1° 跨度、高差 3000 m报 96485.974 m画的弦 96530.400 m差 44.43 m0.046%。你以为你在量一条水平线其实你画的是一条微微拱起的弦。坑 3三角形内角和triangleInfo 永远 180.000000°可地表三角形明明有角超。本猿用triangleInfo算了从 0.01° 到 60° 各种尺度的三角形三个内角加起来每一位都精确到 180.000000°。但同一组点如果按地表测地三角形算角超spherical excess尺度一大就离谱Δ1° 超 0.0065°、Δ10° 超 0.65°、Δ30° 超5.79°、Δ60° 超23.30°。也就是说——曲率没有消失它只是被 Cesium 塞进了边长里没放在角里。你拿triangleInfo的角去当球面三角形的内角小尺度还行大尺度会差出二十多度。坑 4你说三点共线ECEF 不这么认为Heron 照样给你一个数。同纬度三个点A、B、C 经度递增在人类眼里是一条线但在 ECEF 里它们躺在一条圆弧上根本不共线。triangleInfo用 Heron 公式算出来面积8.12×10⁷ m²约 81 km²不是 0。所以共线就面积为零的直觉在弦三角形里不成立。坑 5面积用 Heron弦≈ 投影面积但它是 3D 弦面积不是贴地面积。本猿拿 Heron弦和 ENU 切平面鞋带投影比Δ5° 只差 0.000444%、Δ10° 差 0.001632%——所以 Heron 在合理尺度下够准。但它是三维弦围出来的平面面积弦会从地下穿过不是 #25 那种贴地表面积。和 #25 同一句告诫要脚下的真实表面得走细分贴地形那条路。坑 6rightTriangleInfo 的夹角比真值偏大一点点。它用atan2(Δh, 测地线水平)算夹角可实际上那根水平腿是弦真值应该用atan2(Δh, 真实弦水平)。本猿比过0.01° 跨度、高差 1000 m报 46.024498°、真值 46.020014°偏大 0.0045°跨度 1°、高差 8848 m偏大 0.0072°。平时无所谓做精密坡角测量要自己换。坑 7退化情形不兜底直接给你 NaN。两点重合AA三边 0 / 96485 / 96485内角 NaN / 面积 0三点全重合三边全 0、内角全 NaN。代码里没有任何拦截NaN 会直接灌进面板和后续计算。量测系统里这种点重复是最容易触发又最容易被忽略的崩盘点。坑 8夹角的边界值会骗人。同一点 → 0°同经纬度、只有高差 →90°纯竖直同高度、水平 1 km → 0°水平 1 km 高差 1 km → 46.02°注意不是 45°因为水平腿是 964.86 m 的测地线不是 1000 mB 比 A 低时夹角和B 比 A 高数值一样——atan2只吃绝对值不区分上下坡。这些边界值单独看都合理但凑在一起你很难一眼判断用户到底量了个啥。坑 9两个函数的夹角根本不在同一个顶点上。rightTriangleInfo只报一个角angle atan2(Δh, 水平)——它其实是斜边与水平腿的夹角那个顶点的角实测 46.024498°。而triangleInfo报三个内角angles[0]是起点 A处的角实测 43.982074°。你把两个函数的输出摆一起对比会看到 2.04° 的差第一反应是哪个算错了——其实谁都没错只是它们量的不是同一个顶点的角。要对照得让rightTriangleInfo.angle去对triangleInfo在另一个顶点的角实测 46.022256°只差 0.002°。坑 10formatLength的单位切换会在 1000 m 附近跳档。判据是meters 1000所以 999.95 m 仍按米显示.toFixed(1)进位成“1000.0 m”而 1000.0 m 直接切千米显示“1.000 km”——相差 0.05 m 的两个数一个写1000.0 m、一个写1.000 km肉眼看像差了三个数量级。同理 1000.4 m 被格式化回“1.000 km”那 0.4 m 直接没了。做审计口径的报表时要当心。顺带说个好消息数学链路本身不是瓶颈。Cartesian3.fromDegrees/fromRadians往返误差实测在 1e-9 m 量级亚微米垂直腿|Δh| 与 ECEF 直线距离两条路算出来差不到 1e-10 m。所以上面这些坑没有一个是因为浮点精度不够——全是量的是哪条路的选择问题。六、怎么改才准如果你要把三角量测用进项目本猿给几条落地建议要真直角面积直接用 ½·水平·垂直它等于投影面积在直角三角形上的解析解足够准和 #25 的projectedArea一致到 4 位有效数字。要贴地表面积三角形也能像 #25 那样细分——对每条边interpolateUsingFraction取中点、用globe.getHeight贴地、把三角形一分为四递归再求 Heron。比多边形那条surfaceArea还好控制。要大尺度下的真实内角 / 角超别用triangleInfo的余弦定理角——它给的是平角。要么在 ENU 切平面里用球面三角L’Huilier 公式算角超要么直接用三边测地线长度反推球面角。要水平腿和画的线一致自己用Transforms.eastNorthUpToFixedFrame建 ENU 切平面把 apex 和 B 投影进去求弦水平别再拿测地线当水平腿。给退化情形加兜底两点重合、三点全重合、共线都应该在finishMeasurement之前拦下来至少报个点重复请重采别让 NaN 进面板。七、跑起来看看gitclone https://github.com/jianlei-wang/CESIUM-TREASURE-BOXcdCESIUM-TREASURE-BOXpnpminstallpnpmdev打开空间测量 / 三角量测单击选起点移动鼠标会实时拉出绿/蓝/橙三色直角三角形并预览四个数再次单击完成。八、写在最后Cesium 是头优雅的大象。五站量下来我们量了方向、高度、长度、面积今天把这三件事收进一个三角形里。代码依然短——两个函数加起来不到 40 行。但这一站让本猿最想留下的一句话不是任何一个公式而是Cesium 给你的三角形是平的。它把三条边当 ECEF 里的直线段在平坦空间里算出内角和永远 180.000000°。地球那个弯它没有丢掉只是悄悄塞进了边长里——于是triangleInfo的角永远平角超全藏在spatialDistance比测地线多出来的那一截里。你要的是曲面上的三角形就得自己把角超算回来。这其实和 #25 那条从哪儿起算是同一个毛病的两种长相量测系统里凡是把曲强行压成平的步骤曲率都不会消失只会搬家——要么搬进边长要么搬进面积要么搬进你以为已经对齐的那根水平腿。现在Cesium 不但能量方向、量高度、量长度、量面积还能把这三件事装进一个三角形里一次端出来了。挑数的办法还是那个——先问自己我要的是平面上那个三角形还是地球上那个三角形要平面投影三角坡角、投影面积→rightTriangleInfotriangleInfo都够用记得水平腿是测地线要地球表面三角真实内角、贴地面积→ 自己建 ENU 切平面或细分贴地形别信那个 180°要避免 NaN→ 给重合点、共线加兜底这一量依然粗demo 只暴露了rightTriangleInfo那个能把四站真正合起来验的triangleInfo还躺在库里没界面退化不兜底水平腿内部测地线与弦不自洽。但它是这条线第一次让我们看清——当一个形状同时牵扯方向、高度、长度和面积时量的是哪条路这个问题会从一条边、一个面收成一个自带闭环的三角形。如果你有更好的想法或者在实际使用中遇到问题欢迎来 GitHub 提 Issue这个系列的空间测量线今天正式收官#22→#23→#24→#25→#26。下一站本猿打算往两条岔路里选一条接着挖要么把这套量测扩成折线 / 多边形上的逐段方位角与转向角把 #22 的方位角从一条线拉成一条路要么给高度量测补上海拔改正外接 EGM2008 / CNGG2011 格网把椭球高 h 换算成大家真正要的海拔 H h − ζ。你更想看哪个来评论区拍砖。 项目已开源欢迎Star你的 Star ⭐ 就是对我最大的支持开源地址https://github.com/jianlei-wang/CESIUM-TREASURE-BOX仓库公开无需回复口令直接到 GitHub 取即可。本篇的rightTriangleInfo()/triangleInfo()在src/cases/measure-lib/geometry.tsdemo 在src/cases/triangle-measure/TriangleMeasureDemo.vue拿去魔改。Cesium酱の百宝箱 · 第 26 篇三边承四站平角掩崎岖二〇二六年秋与你共量这颗蓝星的弧度