使用 CesiumJS 在三维城市中展示拟建建筑

1,471 阅读8分钟

原文地址:Visualize a Proposed Building in a 3D City – Cesium

在本教程中,你将学习如何创建一个 Cesium 应用程序,并使用自己的 3D 模型替换现实城市中的建筑物。

您可以通过这种方式来直观地了解拟建建筑的影响。它会如何改变城市天际线?从特定楼层或房间看外面的景象会是什么样?

我们将涵盖以下内容:

  • 在 Web 上设置和部署 Cesium 应用程序。
  • 添加全球 3D 建筑物、地形和影像的基础图层。
  • 隐藏单个建筑物,并用您自己的 3D 模型替换它们。

准备工作

我们将从 Cesium ion 获得全球卫星影像、三维建筑和地形数据。如果您还没有Cesium ion账户,请注册一个免费账户。登陆后:

  1. 跳转至访问令牌标签页。
  2. 注意默认令牌旁边的复制按钮。在下一步中,我们将使用此令牌。

image.png

1. 设置您的 Cesium 应用程序

官方文档中推荐使用 Glitch——一个在线 IDE——来托管和开发 Cesium 应用程序。感兴趣的同学可以直接阅读文档中的 [相关部分](Visualize a Proposed Building in a 3D City – Cesium) 以获取更多详细信息。

但在接下来的介绍中,我将使用 入门教程 中的模版代码来进行讲解。

2. 添加 Cesium OSM Buildings 和 Cesium World Terrain

Cesium OSM Buildings 是一个全球基础图层,拥有超过 3.5 亿座建筑,这些建筑来自 OpenStreetMap 数据。它以 3D Tiles 的形式提供,这是 Cesium 创建的一项开放标准,可以将 3D 内容流式传输到任何兼容的客户端。

让我们添加这些图层,然后将相机移动到我们虚构的、新建筑即将放置的城市——美国科罗拉多州丹佛市。通过点击和拖动来探索场景,有可以在拖动时按住 CTRL 键来倾斜相机。

您的完整 index.html 将如下所示(访问令牌除外)。在后续步骤中,你将在现有代码的 <script> 标签内添加新代码。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <!-- 引入 CesiumJS 的 JavaScript 和 CSS 文件 -->
  <script src="https://cesium.com/downloads/cesiumjs/releases/1.117/Build/Cesium/Cesium.js"></script>
  <link href="https://cesium.com/downloads/cesiumjs/releases/1.117/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
  <div id="cesiumContainer"></div>
  <script type="module">
    // 您的访问令牌可以在这里找到:https://ion.cesium.com/tokens.
    // 用您的 Cesium ion 访问令牌替换 `your_access_token`。
    Cesium.Ion.defaultAccessToken = 'your_access_token';
    // 在带有 `cesiumContainer` ID 的 HTML 元素中初始化 Cesium Viewer。
    const viewer = new Cesium.Viewer('cesiumContainer', {
      terrain: Cesium.Terrain.fromWorldTerrain(),
    });    
    // 将相机飞到给定的经度、纬度和高度的旧金山。
    viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(-122.4175, 37.655, 400),
      orientation: {
        heading: Cesium.Math.toRadians(0.0),
        pitch: Cesium.Math.toRadians(-15.0),
      }
    });
    // 添加 Cesium OSM Buildings,一个全球 3D 建筑层。
    const buildingsTileset = await Cesium.createOsmBuildingsAsync();
    viewer.scene.primitives.add(buildingsTileset);   
  </script>
</div>
</body>
</html>

Cesium OSM Buildings 被固定在高分辨率 3D 地形图层 Cesium World Terrain 上。这使其成为需要精准建筑物高度的应用程序(例如洪水分析工具)的理想选择。

3. 确定新建筑区域

在添加新建筑物之前,让我们添加一个 GeoJSON 文件来标记其占地面积。这将向我们展示哪些现有建筑物需要拆除。

  1. 下载GeoJSON文件
  2. 将 GeoJSON 文件拖放到您的 Cesium ion 仪表板中
  3. 点击上传

4. 上传后,记下预览窗口下的资产 ID

image.png

将下述代码添加到 index.html 中。替换 your_asset_id 为你的资产 ID。ID 是一个数字,所以你不需要引号。

// STEP 3 CODE
async function addBuildingGeoJSON() {
  // 从 Cesium ion 加载 GeoJSON 文件。
  const geoJSONURL = await Cesium.IonResource.fromAssetId(your_asset_id);
  // 从 GeoJSON 创建几何图形,并将其贴地。
  const geoJSON = await Cesium.GeoJsonDataSource.load(geoJSONURL, { clampToGround: true });
  // 将其添加到场景中。
  const dataSource = await viewer.dataSources.add(geoJSON);
  // 默认情况下,CesiumJS 中的多边形将覆盖场景中的所有 3D 内容。
  // 修改多边形,使这种覆盖只适用于地形,而不适用于 3D 建筑物。
  for (const entity of dataSource.entities.values) {
    entity.polygon.classificationType = Cesium.ClassificationType.TERRAIN;
  }
  // 移动相机,使多边形处于视图中。
  viewer.flyTo(dataSource);
}
addBuildingGeoJSON();

现在你将在地面上看到建筑物的占地情况。可以使用鼠标滚轮放大或右击 + 拖动以更仔细地查看。

image.png

5. 隐藏现有 3D 建筑物

现在我们已经确定了新建筑的位置,我们可以看到当前那里有哪些建筑物。我们将使用 3D Tiles 样式语言来隐藏它们。

在上图中,我们可以看到新拟建建筑的场地上有六座建筑——一栋大楼和五栋小得多的建筑物。

  1. 添加以下代码,它隐藏了所有较小的 3D 建筑。

    // STEP 4 CODE
    // 使用 3D Tiles 样式语言隐藏该区域内的单个建筑物。
    buildingsTileset.style = new Cesium.Cesium3DTileStyle({
      // 创建一个样式规则来控制每栋建筑的“显示”属性。
      show: {
        conditions : [
          // 任何具有此 elementId 的建筑将有 `show = false`。
          ['${elementId} === 332469316', false],
          ['${elementId} === 332469317', false],
          ['${elementId} === 235368665', false],
          ['${elementId} === 530288180', false],
          ['${elementId} === 530288179', false],
          // 如果建筑没有这些 elementId 之一,设置 `show = true`。
          [true, true]
        ]
      },
      // 为这个特别的 3D Tileset 设置默认颜色样式。
      // 对于任何具有 `cesium#color` 属性的建筑物,使用该颜色,否则使其变为白色。
      color: "Boolean(${feature['cesium#color']}) ? color(${feature['cesium#color']}) : color('#ffffff')"
    });
    
  2. 扩展这段代码以隐藏剩余的 3D 建筑。

    • 点击建筑物找到其 elementIdimage.png
    • 添加另一行,如:['${elementId} === large_building_elementId', false]。添加后完成后的效果如下图所示: image.png

6. 上传并定位新建筑

让我们上传拟建的建筑模型。

  1. 下载此 glTF 模型。

  2. 将其拖放到您的 Cesium ion 仪表板中

  3. 选择 3D Model (tile as 3D Tiles) 并进行上传。 image.png

  4. 处理完成后,选择刚才上传的模型,并点击资产预览窗口的顶部的 Adjust Tileset Location 按钮。 image.png

  5. 在搜索框中输入建筑的地址 1250 Cherokee Street,然后点击 下一步

  6. 使用查看器上的控件,直观地定位和旋转建筑物,使其与下方的卫星图像对齐。您的最终设置应大致如下:

    • Longitude: -104.9909
    • Latitude: 39.73579
    • Height: 1577
    • Heading: -8 image.png
  7. 点击保存。

确保地理位置准确性:在本教程中,您手动定位了一个新建筑。但如果建筑物已经进行了地理配准,Cesium ion 将自动将其放置在正确的位置。同时,您还可以使用 REST API 对其定位。想了解更多信息,可以访问该指南

7. 将新建筑添加到场景中

现在让我们将新建筑添加到场景中。

  1. 在资产预览窗口下获取我们上传建筑模型的资产 ID。

    image.png

  2. index.html 中添加下述代码,注意将 your_asset_id 替换为您的资产 ID。

    // STEP 6 CODE
    // 从你的 Cesium ion 账户添加你创建的 3D tiles。
    const newBuildingTileset = await Cesium.Cesium3DTileset.fromIonAssetId(your_asset_id);
    viewer.scene.primitives.add(newBuildingTileset);
    // 将相机移动到新建筑处。
    viewer.flyTo(newBuildingTileset);
    

    image.png

8. 添加按钮显示/隐藏新建筑

  1. index.html 中,将按钮直接添加到 <body> 标签中。

    <button id="toggle-building">切换新建筑</button>
    
  2. head 标签内添加以下 CSS style 标签。

    <style type="text/css">
      #toggle-building { z-index: 1; position: fixed; top: 5px; left: 5px; }
    </style>
    
  3. index.html 中添加下面的 JavaScript。

    // STEP 7 CODE
    // 当按钮被点击时,切换 tileset 的显示状态。
    document.querySelector('#toggle-building').onclick = function() {
      newBuildingTileset.show = !newBuildingTileset.show;
    };
    

9. 考虑建筑对其周围环境的影响

现在,您可以比较有和没有这座新建筑的场景!这座建筑如何影响其他地方(例如科罗拉多州议会大厦)的景观?我们甚至可以探索从国会大厦入口处看到的景观将会发生怎样的变化。

完整教程源代码

下述是带有 your_access_tokenyour_asset_id 占位符的完整教程源代码。

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://cesium.com/downloads/cesiumjs/releases/1.118/Build/Cesium/Cesium.js"></script>
  <link href="https://cesium.com/downloads/cesiumjs/releases/1.118/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
  <link href="style.css" rel="stylesheet">
```auto
  <style type="text/css">
    #toggle-building { z-index: 1; position: fixed; top: 5px; left: 5px; }
  </style>
</head>
<body type="module">
  <div id="cesiumContainer"></div>
  <button id="toggle-building">切换新建筑</button>
  
  <script>
    // 你的访问令牌可以在这里找到:https://ion.cesium.com/tokens。
    // 请将 `your_access_token` 替换成你的 Cesium ion 访问令牌。
    Cesium.Ion.defaultAccessToken = 'your_access_token';
    // 保留你上面的 Cesium.Ion.defaultAccessToken = 'your_token_here' 行。 
    // STEP 2 CODE
    // 用 Cesium 世界地形初始化查看器。
    const viewer = new Cesium.Viewer('cesiumContainer', {
      terrain: Cesium.Terrain.fromWorldTerrain(),
    });
    // 将相机飞到美国科罗拉多州丹佛给定的经度、纬度和高度。
    /* viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(-104.9965, 39.74248, 4000)
    }); */
    // 添加 Cesium OSM 建筑。
    const buildingsTileset = await Cesium.createOsmBuildingsAsync();
    viewer.scene.primitives.add(buildingsTileset);
    // STEP 3 CODE
    async function addBuildingGeoJSON() {
      // 从 Cesium ion 加载 GeoJSON 文件。
      const geoJSONURL = await Cesium.IonResource.fromAssetId(your_asset_id);
      // 从 GeoJSON 创建几何图形,并将其固定到地面。
      const geoJSON = await Cesium.GeoJsonDataSource.load(geoJSONURL, { clampToGround: true });
      // 将其添加到场景中。
      const dataSource = await viewer.dataSources.add(geoJSON);
      // 默认情况下,CesiumJS 中的多边形将覆盖场景中的所有 3D 内容。
      // 修改多边形,使这种覆盖只适用于地形,而不适用于 3D 建筑物。
      for (const entity of dataSource.entities.values) {
        entity.polygon.classificationType = Cesium.ClassificationType.TERRAIN;
      }
      // 移动相机使多边形处于视线中。
      // viewer.flyTo(dataSource);
    }
    addBuildingGeoJSON();
    // STEP 4 CODE
    // 使用 3D Tiles 样式语言隐藏这个区域内的单个建筑物。
    buildingsTileset.style = new Cesium.Cesium3DTileStyle({
      // 创建一个样式规则来控制每栋建筑的 "show" 属性。
      show: {
        conditions: [
          // 任何具有此 elementId 的建筑都将 `show = false`。
          ['${elementId} === 532245203', false],
          ['${elementId} === 332469316', false],
          ['${elementId} === 332469317', false],
          ['${elementId} === 235368665', false],
          ['${elementId} === 530288180', false],
          ['${elementId} === 530288179', false],
          // 如果建筑没有这些 elementId 之一,那么设置 `show = true`。
          [true, true]
        ]
      },
      // 为这个特定的 3D Tileset 设置默认的颜色样式。
      // 对于具有 `cesium#color` 属性的任何建筑,使用该颜色,否则将其设为白色。
      color: "Boolean(${feature['cesium#color']}) ? color(${feature['cesium#color']}) : color('#ffffff')"
    });
    // STEP 6 CODE
    // 添加你从 Cesium ion 账户创建的 3D Tileset。
    const newBuildingTileset = await Cesium.Cesium3DTileset.fromIonAssetId(your_asset_id);
    viewer.scene.primitives.add(newBuildingTileset);
    // 将相机移动到新建筑处。
    viewer.flyTo(newBuildingTileset);
    // STEP 7 CODE
    // 当按钮被点击时,切换tileset 的显示属性。
        document.querySelector('#toggle-building').onclick = function() {
      newBuildingTileset.show = !newBuildingTileset.show;
    };
  </script>
</body>
</html>

下一步

如果您有自己的建筑模型(支持 glTF、OBJ 和 FBX 等多种格式),请尝试使用它来代替示例中的模型;或者将建筑物放置到您自己的城市中。

查看3D 模型导入指南以了解更多信息。