站内搜索

输入关键词开始搜索

Cesium 区域等高线:地形采样与 Marching Squares

文章目录

区域等高线案例把用户绘制的多边形转成一张规则高程网格,再用 Marching Squares 提取指定等高距的线段。截图中的区域完成了 100×100 采样,得到 47 层等高线。

山地区域生成的彩色等高线、高程渐变与统计结果

实现思路导图

flowchart TD
  ROOT[区域等高线]
  ROOT --> INPUT[分析输入]
  INPUT --> POLYGON[绘制多边形]
  INPUT --> GRID[规则采样格]
  ROOT --> TERRAIN[地形高度场]
  ROOT --> EXTRACT[线条提取]
  EXTRACT --> MS[Marching Squares]
  EXTRACT --> JOIN[线段拼接与裁剪]
  ROOT --> RENDER[贴地或立体渲染]
  RENDER --> LABEL[色带与高程标注]

绘制阶段只确定边界

CesiumDrawTool 用左键追加点、右键结束。绘制中的面和闭合辅助线由 CallbackProperty 更新,结束后再固化为静态几何。

const dynamicHierarchy = new Cesium.CallbackProperty(
  () => new Cesium.PolygonHierarchy(activeShapePoints),
  false,
);

这一阶段不采样地形。把高成本计算延后到边界确认后,可以避免鼠标移动时反复发请求。

在包围盒内建立采样网格

程序按多边形包围盒生成规则经纬网,过滤边界外的点,然后调用地形采样接口。采样密度为 100 时,横纵方向各有约 100 个位置,实际请求量还要看边界形状。

规则网格是 Marching Squares 的前提。每四个相邻高程点形成一个单元,某个等高值与四条边的交点决定该单元内的线段形态。

提取与连接线段

对最低到最高高程按等高距循环,每个高度都遍历网格单元:

for (let level = minLevel; level <= maxLevel; level += interval) {
  const segments = marchingSquares(heightGrid, level);
  const lines = connectSegments(segments);
  renderContourLines(lines, level);
}

connectSegments 需要容差。地形坐标经过插值后存在浮点误差,只按完全相等比较端点会产生大量断线。

颜色、平滑与三维抬升

高程渐变色根据当前等高值在最小值和最大值之间的比例取色。平滑曲线只改变折线外观,不增加采样精度。3D 立体模式把线放到对应海拔,普通模式则更接近贴地显示。

坡度着色来自高程网格的局部梯度,它与等高线提取共用同一批采样数据,无需再次请求地形。

交互重新生成

修改等高距、线宽或显示选项时,可以复用高程网格;修改采样密度或区域边界才需要重新采样。区分这两类更新,能让面板调节保持即时响应。

截图中的 47 条等高线怎么得到

截图选区最低高程 2606.2 米,最高高程 4977.7 米,等高距为 50 米。程序把首个等高值向上对齐到 50 的整数倍,再逐层计算,因此层数约为:

floor(4977.7 / 50) - ceil(2606.2 / 50) + 1 = 47

彩色面是同一份高程网格生成的高程渐变,不是另一套影像。等高线、坡度着色、统计和悬停查询共享采样快照,这也是该案例的主要设计思路。

Marching Squares 如何决定线段

每个网格单元有四个角。将角点高程与当前等高值比较,可得到 4 位状态码。绝大多数状态只对应一条线段;鞍点状态会有两种连接方式,需要用单元中心高程或渐近判别决定连接方向。

交点位于边上的比例使用线性插值:

t = (level - h0) / (h1 - h0)
p = p0 + t × (p1 - p0)

若 h0 与 h1 极接近,需要设置 epsilon,避免除零和交点抖动。

线段怎样拼成长线

Marching Squares 输出的是每个单元内的短线段。程序把端点量化到容差网格,用哈希表寻找首尾相接的线段,再合并为折线。

容差过小会留下断口,过大会把邻近但不同的等高线错误连接。合并后再执行平滑,顺序不能反过来;对短线段先平滑会让端点失去可连接性。

多边形边界的处理

包围盒内的规则采样点不一定都在用户多边形中。边界外点标记为无效,Marching Squares 遇到无效角点时跳过或在边界处裁断。

如果只对最终线做多边形裁剪,而采样阶段保留外部山峰,边界附近的插值仍会受到区域外高程影响。因此有效掩膜应与高程网格一起参与单元计算。

3D 立体模式和贴地模式

贴地模式依赖地形深度,适合地图浏览。立体模式把每条线放到自身等高值,并可增加轻微抬升,能看出层级关系,但远处容易被地形遮挡。

标注不应该每条线每隔固定顶点创建一个 Label。应按屏幕距离、折线长度和最大数量抽样,否则高密度等高线会让标签数量超过线本身。

这个案例的主线是:绘制多边形 → 规则网格采样 → Marching Squares 提取 → 线段连接和平滑 → Primitive 与标注渲染。

从零复现:区域等高线教程

案例源码位于 src/demos/cesiumdemos/contour-lines/。建议先用矩形和 32×32 采样格验证算法,再切换到任意多边形和更高质量。

第 1 步:绘制并规范分析区域

收集用户点击的经纬度,结束时移除重复末点并统一顶点方向。计算包围矩形,同时保留原始多边形用于最后裁剪。

在区域中心建立 ENU 坐标。规则采样和折线连接在二维米制平面中完成,渲染前再转换回经纬度,避免直接用经纬度计算距离和插值。

第 2 步:生成规则高度格

按 rows × columns 遍历包围矩形,为每个格点生成 Cartographic。调用 sampleTerrainMostDetailed 批量采高,并把结果写入固定顺序的 Float32Array。

const index = (row, column) => row * columns + column;
heights[index(row, column)] = samples[index(row, column)].height;

完成标志:打印最小高程、最大高程和有效样本率;范围应与当前山地相符,数组中不应出现整行 0 或 NaN。

第 3 步:生成等高值序列

给定间距 interval,从 ceil(min/interval) * interval 开始,到最大高度结束。不要从最小高度本身开始,否则标签会出现难读的小数。

for (let level = first; level <= maxHeight; level += interval) {
  levels.push(level);
}

截图中的 47 级就是这一步根据采样范围和间距计算出来的,不是预先写死的线条数量。

第 4 步:执行 Marching Squares

每个采样小格有四个角。比较四角高度与当前等高值,得到 4 位 case。查表确定需要连接的边,并在线性插值后得到精确交点。

t = (level - hA) / (hB - hA)
point = A + t · (B - A)

遇到 5 和 10 这类鞍点 case 时,用格心平均高度决定连接方向。若固定选一条对角线,等高线会在鞍部错误交叉。

第 5 步:把短线段拼成长折线

Marching Squares 输出大量无序短线段。把端点按空间容差量化成 key,建立“端点到线段”索引,再从任意未访问线段向两端追踪。

容差应小于半个采样格。过小会因为浮点误差断线,过大会把相邻等高线粘在一起。拼接后可以使用 Chaikin 或 Catmull-Rom 做一次轻度平滑,但不能让平滑曲线越出原多边形。

第 6 步:裁剪并渲染

检查折线点或线段中点是否位于原多边形内,裁掉包围矩形外多余部分。贴地模式使用 GroundPolyline;立体模式把等高值写成 Cartesian 高度,再用 Primitive 批量提交。

颜色按 (level-min)/(max-min) 查渐变色。标签按折线屏幕长度稀疏抽样,文本使用真实等高值。

第 7 步:处理参数重算

采样分辨率变化需要重新采高;等高距变化只需重新提取;颜色和线宽只需更新渲染层。将三类参数拆开,拖动颜色时就不会重新请求地形。

验收与排错

  • 等高线呈锯齿:提高采样分辨率或轻度平滑,不要只增加渲染顶点。
  • 鞍部出现 X 形交叉:补上 5/10 case 的判别。
  • 多边形外仍有线:提取后必须按原区域裁剪。
  • 标签挤成一团:按屏幕距离和全局上限抽样。

相关文章

从 Cesium 地形采样生成局部等高线

使用规则网格、地形高程采样和 Marching Squares,在用户绘制区域内生成可交互的局部等高线

Cesium 三维风场:用 WebGL 2 GPU 粒子描绘全球气流

拆解案例中的风场数据、GPU 粒子更新、轨迹渲染、颜色映射和 Vue 参数控制