Cesium 空间分析:把 “坡向” 计算搬到浏览器

📅 2026/7/29 11:33:39
Cesium 空间分析:把 “坡向” 计算搬到浏览器
前端做 GIS 空间分析不是新鲜事但把坡向计算搬到浏览器里跑、还要能交互式选范围中间藏了多少细节这篇文章把完整实现拆开给你看。开篇CesiumJS 提供了sampleTerrainAPI 可以采样地形高度但没有内置坡向分析这个功能。你得自己把整个计算流水线搭起来从 DEM 采样到梯度计算从色带映射到 Canvas 叠图最后再盖一层方向箭头。每一步都有坑。这篇文章就是我的实测笔记——我会逐段拆解aspect_analysis.html的核心实现环境搭建和底图加载一笔带过重点放在 计算链路 和 可视化策略 上。核心思路从地形到坡向的四步流水线整个工具的逻辑其实不复杂——就四步地形采样 —— 在用户绘制的矩形范围内按网格密度采样地形高度梯度计算 —— 用中心差分法算出每个格网的东西向和南北向坡度坡向转换 —— 把梯度分量转为 0°~360° 的方位角0°北顺时针可视化呈现 —— 用 Canvas 生成色带叠加层 箭头矢量表示方向这四步串起来就是一条流水线每一步的输出是下一步的输入。贴个简化版流程矩形范围→ sampleTerrain → 高度矩阵 → 梯度计算 → 坡度坡向 → Canvas色带叠图 → 箭头渲染看代码的时候你会注意到整条流水线全部塞在analyzeCurrentRectangle()这一个方法里。这么做不是偷懒——是为了保证每一步的数据都是最新的避免参数改了但结果没刷新这种前端常见的状态不同步问题。下面我一步步拆。地形采样sampleTerrain的几个坑坑一采样点数组不能随便构造采样是整个链条的第一步也是最容易翻车的一步。Cesium 的sampleTerrain接受一个Cartographic数组和一个采样层级返回带高度的Cartographic数组。关键点在构造这个数组letpositions[];for(letj0;jrows;j){letlatsouth(latSpan*j)/(rows-1);for(leti0;icols;i){letlonwest(lonSpan*i)/(cols-1);positions.push(newCesium.Cartographic(lon,lat,0));}}letsampledawaitCesium.sampleTerrain(this.viewer.terrainProvider,this.params.terrainLevel,positions,);注意这里terrainLevel是可调的8-15并且这里精度其实没设置。两个采样点的间距直接由矩形范围的经纬度跨度和网格数决定。这点和桌面 GIS 不太一样——桌面软件通常让你指定像元大小而这里是纯数学网格每个网格点就是一个采样点。坑二采样层级是个性能杠杆terrainLevel的值越大采样精度越高但耗时也越长。我默认设了 12——经过实测100×100 的网格在 level 12 时大概 2-3 秒level 15 直接上 6-8 秒。这就是为什么我在面板里加了autoAnalyze开关——关掉它你可以先调好所有参数再点开始分析省得每改一个参数就触发一次 3 秒的重新计算。坑三地形返回的高度可能 undefinedDEM 数据不是全地球都有。采样点落在 DEM 覆盖范围之外时sampleTerrain返回的height就是undefined。代码里必须兜底letheightsnewFloat64Array(cols*rows);letvalidnewUint8Array(cols*rows);for(letk0;ksampled.length;k){letheightsampled[k].height;if(heightundefined||Number.isNaN(height))continue;heights[k]height;valid[k]1;}用valid数组标记有效点后续梯度计算时遇到无效点就直接跳过。这个细节不处理后面梯度计算会给你各种 NaN 污染。梯度和坡向这部分纯数学但很容易算错方向拿到高度矩阵之后就可以计算每个格网的坡度和坡向了。梯度中心差分法代码用的是最简单的中心差分法——每个格网的东西向梯度用左右两个邻点的高度差除以两倍格网间距南北向同理。这是标准做法不稀奇。但这里有两个细节值得说一下leteastMeterPerRadearthRadius*Math.cos(lat);第一经纬度换算成米。东西向 1 度的实际距离随纬度变化——赤道最大极点趋近于零。所以每次迭代都要乘以Math.cos(lat)。你直接用(nextLon - prevLon) * earthRadius算东西向梯度的话高纬度地区会严重失真。第二坡向角的定义。GIS 里坡向是坡面面向的方向坡面法向量在水平面上的投影不是坡面下降的方向。所以代码里传到getAspectAzimuthDeg时梯度分量都取了反号letaspectDegthis.getAspectAzimuthDeg(-dhdEast,-dhdNorth);dhdEast和dhdNorth是高度沿东西、南北方向的变化率。坡面上升最快的方向是(dhdEast, dhdNorth)但坡向要输出的是坡面朝哪个方向所以取反——下降最快的方向和坡面朝向一致。坡向角到色带算出来的 0°~360° 方位角要映射到色带颜色上。代码支持 4 种内置色带色带视觉风格适合场景八方向8 色离散区分度高快速识别 N/NE/E/SE/S/SW/W/NW冷暖方位蓝→白→红渐变南北坡对比明显彩虹方位多色渐变精细方位辨识灰度方位黑白渐变叠加在影像上不抢视觉色带切换是onChange→ 直接触发reanalyzeIfReady()全程实时重算不用等、不用手动点刷新。可视化Canvas 叠图 箭头两个图层各有门道Canvas 叠图给每个格网画一个像素这部分是整个工具最手工的地方。计算完所有格网的坡向后不是用 Cesium 的 Entity 或 Primitive 来渲染——而是直接画到 Canvas 上再把 Canvas 转成SingleTileImageryProvider贴到地球上。letcanvasdocument.createElement(canvas);canvas.widthcols;canvas.heightrows;letcontextcanvas.getContext(2d);letimageDatacontext.createImageData(cols,rows);// ... 逐像素 fillcontext.putImageData(imageData,0,0);letprovidernewCesium.SingleTileImageryProvider({url:canvas.toDataURL(image/png),rectangle,});this.resultLayerthis.viewer.imageryLayers.addImageryProvider(provider);这个方案的好处是性能好——无论多少格网点最终都是一张图渲染可控——每个像素的颜色、透明度完全自己掌控不需 Entity——不用创建成千上万个 Entity 来渲染但它有个限制Canvas 的cols × rows不能太大。我试过 1000×1000浏览器直接卡死。代码里用Math.min(1000, ...)做了上限保护——这个保护不是可有可无是生产环境必须加的。箭头坡向的方向可视化色带用颜色表示方向但人眼对颜色的方向感知其实不直观——东是红的、西是蓝的这需要看色卡才能读懂。所以代码里又加了一层箭头层——在格网范围内按固定间隔绘制箭头箭头从低处指向高处坡向的反方向…不对应该是指向坡面朝向。等一下让我再确认一下箭头的方向getArrow(lon,lat,lonLen,latLen,aspectDeg){letradCesium.Math.toRadians(aspectDeg);letdxMath.sin(rad)*lonLen;letdyMath.cos(rad)*latLen;return{startLon:lon-dx*0.5,startLat:lat-dy*0.5,endLon:londx*0.5,endLat:latdy*0.5,};}aspectDeg是坡向的方位角——坡面面向的方向。箭头从中心向(dx, dy)方向延伸这就是箭头指向的方向。所以箭头指向的就是坡面朝的方向——(地理上北是 0°箭头往北就是朝北坡)。箭头用的是 Cesium 的PolylineArrowMaterialProperty以线条形式 clamp 在地形上方向感非常直截了当。arrowStep控制箭头间距——值越小箭头越密但性能也越差。默认 10 格一跳100×100 的网格大概产生 100 个箭头渲染压力不大。交互矩形绘制也得自己搞Cesium 没有内置的画矩形选择范围功能得自己实现。代码用的是ScreenSpaceEventHandler第一次左键点击 → 记录第一个角点鼠标移动 → 实时预览半透明青色矩形第二次左键点击 → 确定范围触发分析这里有 一个容易忽略的坑矩形绘制用LEFT_CLICK但 Cesium 默认的LEFT_CLICK还会触发选中 Entity。如果你的场景里有很多 Entity比如之前分析留下的箭头点下去可能选中的是箭头而不是角点。代码里先clearAll()再开始绘制解决了这个问题。另外预览矩形的heightReference: CLAMP_TO_GROUND让它贴在地形表面classificationType: TERRAIN确保它跟随高低起伏——而不是悬在空中。写在最后回头看这个工具最有价值的不是坡向分析本身——桌面 GIS 早就有这个功能了——而是它证明了前端可以做空间分析这件事而且能做到可交互、可调参、即时出结果。有几个经验以后做类似项目时能复用把采样计算和 Canvas 渲染拆成独立步骤。采样参数改了 → 重新跑计算 → 重新画 Canvas。每次改渲染参数透明度、色带→ 只重建 Canvas 叠图不重新采样。这样参数切换的响应速度从几秒降到几十毫秒。能用一个图层解决就别用 Entity。SingleTileImageryProvider是 Cesium 里被低估的功能——可以当成一个万能贴图工具用。地形精度和性能是跷跷板。terrainLevelgridCols/gridRows这两个参数决定了计算耗时。100×100 level 12 大概 2-3 秒是一个比较甜的性能点。说说不足——这个工具目前最明显的短板是不支持不规则范围。只能画矩形多边形不行、流域边界更不行。这是下一步要考虑迭代的方向。如果读者有需求我后面补一篇多边形的实现。这篇文章的完整代码可以在aspect_analysis.html里找到配图用的色带效果见正文。有问题或更好的实现方案欢迎留言交流。完整代码见原文https://mp.weixin.qq.com/s/iewp3NNTWps0EhcoX7QwNQ关注公众号 “GIS 开发手记”及时获取首发内容往期精选Cesium 空间分析坡度分析Cesium 通视分析3D 城市模型上划线看可见区Cesium 3D 热力图从 Canvas 热力到 GPU 顶点着色器Cesium 点聚合海量点地图可视化方案自定义虚线箭头材质把两个内置 Material 焊在一起Cesium 中文字体贴地 / 贴墙 / 动态文字方案网络地图坐标系完全指南WGS84 / GCJ02 / BD09 / CGCS2000 与坐标转换实战