地下管网的产品经理问我:能不能用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(备注来意)。

相关推荐
熟悉不过7 天前
cesium项目之cesiumlab地形数据加载
前端·javascript·vue.js·cesium·webgis·cesiumlab
前端小菜鸟一枚s8 天前
`ConstantPositionProperty` 的使用与应用
前端·javascript·cesium
前端小菜鸟一枚s8 天前
`ConstantProperty` 的使用与应用
前端·javascript·cesium
不浪brown8 天前
【实战篇】Cesium Shader实现军事级雷达探测波!数字孪生开发者必学(附开源代码)
cesium
阿铎前端9 天前
Cesium系列:从入门到实践,打造属于你的3D地球应用
vue·cesium
放逐者-保持本心,方可放逐13 天前
Cesium 核心思想及基础概念应用
scene·cesium·camera·entity·primitive·viewer
该怎么办呢19 天前
Cesium双击放大地图
javascript·cesium·webgis
DragonBallSuper20 天前
在Cesium中使用ThreeJs材质(不是场景融合哦)
webgl·材质·threejs·cesium·可视化渲染
前端付杰20 天前
轻松搞定 TIFF:基于 Three.js 和 Cesium 的渲染技巧分享
前端·three.js·cesium