地下管网的产品经理问我:能不能用Cesium在地表挖一个洞,让我直观的看到地下的管道(附开源代码)

大家好,我是日拱一卒的攻城师不浪,致力于前沿科技探索,摸索小而美工作室,这是2025年输出的第23/100篇文章。

前言

前几天,负责地下管网的产品经理急冲冲的跑过来问我:能不能在地表给我挖一个洞,让我能直接看到地底下的管道!?

:这。。。

实际上,不止在地下管网项目,包括数字孪生智慧城市或者城市应急项目中,都经常会涉及到一个需求:挖洞

那么,今天我们就来看一下,如何在Cesium中实现挖洞的功能,还得支持地形挖洞源码请文末领取噢~

什么是地形挖洞?

地形挖洞(Terrain Excavation)是指在三维场景中,对地形表面进行局部开挖操作,使得地表以下的结构可以被展示出来。

通过地形挖洞,我们可以在不破坏原始地形数据的情况下,实现地下空间的可视化展示。

地形挖洞的应用场景

  1. 地下管网展示 :展示埋在地下的水管、电缆、燃气管等基础设施,还能通过动态设置挖洞深度,预判是否会挖碰到地下管道,提前避免不必要的损失。

  2. 地质勘探与矿产开发:展示地下矿产资源分布和开采情况

  3. 考古模拟:模拟考古挖掘过程,展示地下文物分布

  4. 地下建筑可视化:地下停车场、地铁站、地下商场等建筑的展示

  5. 军事模拟:地下工事、地下通道等军事设施的可视化

  6. 城市规划:综合展示地上地下空间利用情况

实现原理

挖洞,包含几个关键技术点:

  1. 裁剪平面(ClippingPlane):通过裁剪平面集合定义需要挖掘的区域边界

  2. 实体(Entity):创建底部和侧面实体来可视化挖掘区域

  3. 地形采样:获取真实地形高度数据以保证挖掘结果的准确性

  4. 材质定义:为挖掘出的区域添加合适的纹理材质

实现流程

  1. 定义挖掘区域的边界点

  2. 根据边界点创建一系列裁剪平面

  3. 将裁剪平面应用到地球的globe对象上

  4. 创建底部多边形实体,表示挖掘区域的底面

  5. 创建墙体实体,表示挖掘区域的侧面

  6. 为底面和侧面应用纹理材质

核心代码解析

我们会提前创建好挖洞的类,然后直接调用。

1. 基本使用方法

首先看一下如何调用这个功能:

javascript 复制代码
// 创建挖洞实例
excavateInstance = new ExcavateTerrain(viewer, {
  positions: mr, // 挖洞区域的边界点
  height: Number(height.value), // 挖洞深度
  bottom: "/images/excavationregion_top.jpg", // 底部纹理
  side: "/images/excavationregion_side.jpg", // 侧面纹理
});

调用时,只需提供边界点挖掘深度纹理图片即可。

2. 边界点处理

javascript 复制代码
analysis() {
  var viewer = this.viewer;
  var config = this.config;
  var ellipsoid = viewer.scene.globe.ellipsoid;
  var arr = config.positions;
  var nArr = [];
  var nArr2 = [];

  arr.forEach((element) => {
    var catographic = Cesium.Cartographic.fromCartesian(element);
    var height = Number(catographic.height.toFixed(2));
    var cartographic = ellipsoid.cartesianToCartographic({
      x: element.x,
      y: element.y,
      z: element.z,
    });
    var lat = Cesium.Math.toDegrees(cartographic.latitude);
    var lng = Cesium.Math.toDegrees(cartographic.longitude);
    nArr.push({
      x: lng,
      y: lat,
      z: height,
    });
    nArr2.push({
      x: lng,
      y: lat,
      z: height,
    });
  });
  // ...
}

挖洞的第一步是处理边界点,将笛卡尔坐标转换为经纬度格式,然后保存高度信息。nArrnArr2用于后续挖洞操作。

3. 裁剪平面的创建

挖洞的核心是创建裁剪平面集合:

javascript 复制代码
var points = [];
arr.forEach((element) => {
  points.push(Cesium.Cartesian3.fromDegrees(element.x, element.y));
});
var pointsLength = points.length;
var clippingPlanes = [];

for (var i = 0; i < pointsLength; ++i) {
  var nextIndex = (i + 1) % pointsLength;
  var midpoint = Cesium.Cartesian3.add(
    points[i],
    points[nextIndex],
    new Cesium.Cartesian3()
  );
  midpoint = Cesium.Cartesian3.multiplyByScalar(midpoint, 0.5, midpoint);

  var up = Cesium.Cartesian3.normalize(midpoint, new Cesium.Cartesian3());
  var right = Cesium.Cartesian3.subtract(
    points[nextIndex],
    midpoint,
    new Cesium.Cartesian3()
  );
  right = Cesium.Cartesian3.normalize(right, right);

  var normal = Cesium.Cartesian3.cross(right, up, new Cesium.Cartesian3());
  normal = Cesium.Cartesian3.normalize(normal, normal);

  var originCenteredPlane = new Cesium.Plane(normal, 0.0);
  var distance = Cesium.Plane.getPointDistance(
    originCenteredPlane,
    midpoint
  );
  clippingPlanes.push(new Cesium.ClippingPlane(normal, distance));
}

这段代码为多边形的每条边创建了一个裁剪平面:

  1. 计算边的中点(midpoint

  2. 确定平面的法向量(normal

  3. 计算平面与原点的距离(distance

  4. 创建并添加裁剪平面

然后将裁剪平面集合应用到地球上:

javascript 复制代码
viewer.scene.globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
  planes: clippingPlanes,
  edgeWidth: 1.0,
  edgeColor: Cesium.Color.OLIVE,
});

4. 创建底部多边形

为了让挖洞区域更加逼真,需要创建一个底部多边形:

javascript 复制代码
viewer.entities.add({
  id: "entityDM",
  polygon: {
    hierarchy: Cesium.Cartesian3.fromDegreesArray(nar),
    material: new Cesium.ImageMaterialProperty({
      image: config.bottom,
      color: new Cesium.Color.fromCssColorString("#cbc6c2"),
      repeat: new Cesium.Cartesian2(30, 30),
    }),
    height: hhh[0] - config.height,
  },
});

这里创建了一个多边形实体,通过height属性设置底部高度(即最低点高度减去挖掘深度),并应用了自定义纹理。

5. 创建侧面墙体

为了表现挖掘区域的侧面,需要创建墙体:

javascript 复制代码
var p1 = Cesium.sampleTerrainMostDetailed(
  viewer.terrainProvider,
  terrainSamplePositions
);
Promise.all([p1]).then((samples) => {
  console.log(samples[0]);
  samples = samples[0];
  for (let index = 0; index < samples.length; index++) {
    maximumHeightsARR.push(samples[index].height);
    minimumHeights.push(hhh[0] - config.height);
  }
  viewer.entities.add({
    id: "entityDMBJ",
    wall: {
      positions: Cesium.Cartesian3.fromDegreesArray(nar22),
      maximumHeights: maximumHeightsARR,
      minimumHeights: minimumHeights,
      material: new Cesium.ImageMaterialProperty({
        image: config.side,
        repeat: new Cesium.Cartesian2(30, 30),
      }),
    },
  });
});

这段代码首先通过sampleTerrainMostDetailed方法获取地形高度数据,然后创建墙体实体:

  • maximumHeights: 墙体顶部高度,使用真实地形高度

  • minimumHeights: 墙体底部高度,与底部多边形高度一致

  • 同样应用自定义纹理

6. 判断多边形方向

代码中还包含了判断多边形是否为顺时针方向的函数:

javascript 复制代码
isClockWise(latLngArr) {
  // 找到y值最小的点
  let latMin = {
    i: -1,
    val: 100000000,
  };
  for (let i = 0; i < latLngArr.length; i++) {
    let y = latLngArr[i].y;
    if (y < latMin.val) {
      latMin.val = y;
      latMin.i = i;
    }
  }
  // 计算叉积判断方向
  let i1 = (latMin.i + latLngArr.length - 1) % latLngArr.length;
  let i2 = latMin.i;
  let i3 = (latMin.i + 1) % latLngArr.length;

  let v2_1 = {
    y: latLngArr[i2].y - latLngArr[i1].y,
    x: latLngArr[i2].x - latLngArr[i1].x,
  };
  let v3_2 = {
    y: latLngArr[i3].y - latLngArr[i2].y,
    x: latLngArr[i3].x - latLngArr[i2].x,
  };
  let result = v3_2.x * v2_1.y - v2_1.x * v3_2.y;
  
  return result === 0 ? latLngArr[i3].x < latLngArr[i1].x : result > 0;
}

这个函数确保多边形的顶点顺序一致,以便正确创建裁剪平面。

如果多边形是顺时针方向,则会将顶点顺序反转:

javascript 复制代码
var flag = _this.isClockWise(arr);
if (flag === true) {
  arr.reverse();
  nArr2.reverse();
}

清除挖掘效果

javascript 复制代码
function clearDigging() {
  if (viewer) {
    // 清除裁剪平面
    if (viewer.scene.globe.clippingPlanes) {
      viewer.scene.globe.clippingPlanes = undefined;
    }

    // 删除实体
    viewer.entities.removeById("entityDM");
    viewer.entities.removeById("entityDMBJ");
  }
}

最后

源码 】:github.com/jiawanlong/...

不浪的Cesium案例集合 】:github.com/tingyuxuan2...

如果开源对您有帮助,也欢迎帮我们点一个免费的star,以鼓励和支持我们开源更多案例!

如有任何问题或需要进一步探讨,欢迎在评论区留言交流!

想系统学习Cesium的小伙伴儿,可以了解下不浪的教程《Cesium从入门到实战》,将Cesium的知识点进行串联,让不了解Cesium的小伙伴拥有一个完整的学习路线,并最终完成一个智慧城市的完整项目,课程最近也更新了不少新内容,想了解+作者:brown_7778(备注来意)。
有需要进可视化&Webgis交流群可以加我:brown_7778(备注来意)。

相关推荐
gis_rc3 天前
python下shp转3dtiles
python·3d·cesium·3dtiles·数字孪生模型
grasperp4 天前
3DTiles数据切片工具,支持LAS、OBJ、FBX 3DTiles怎么切片?3DTiles切片
cesium·3dtiles·三维gis·3dtiles切片·数据切片
duansamve6 天前
Cesium中实现在地图上移动/旋转点、线、面
cesium
冥界摄政王7 天前
CesiumJS学习第四章 替换指定3D建筑模型
3d·vue·html·webgl·js·cesium
冥界摄政王9 天前
Cesium学习第二章 camera 相机
node.js·html·vue3·js·cesium
冥界摄政王10 天前
Cesium学习第一章 安装下载 基于vue3引入Cesium项目开发
vue·vue3·html5·webgl·cesium
你们瞎搞11 天前
Cesium加载20GB航测影像.tif
前端·cesium·gdal·地图切片
闲云一鹤13 天前
Cesium 使用 Turf 实现坐标点移动(偏移)
前端·gis·cesium
二狗哈13 天前
Cesium快速入门34:3dTile高级样式设置
前端·javascript·算法·3d·webgl·cesium·地图可视化
二狗哈14 天前
Cesium快速入门33:tile3d设置样式
3d·状态模式·webgl·cesium·地图可视化