使用 3D 資訊方塊轉譯器

擬真 3D 圖塊採用 OGC 標準 glTF 格式,也就是說,您可以使用任何支援 OGC 3D 圖塊規格的轉譯器建構 3D 視覺化內容。舉例來說,Cesium 是轉譯 3D 視覺化內容的基礎開放原始碼程式庫。

使用 CesiumJS

CesiumJS 是開放原始碼 JavaScript 程式庫,用於在網路上呈現 3D 視覺化內容。 如要進一步瞭解如何使用 CesiumJS,請參閱瞭解 CesiumJS

使用者控制項

CesiumJS 圖塊轉譯器具有一組標準的使用者控制項。

操作 說明
平移檢視畫面 按住滑鼠左鍵並拖曳
縮放檢視畫面 按一下滑鼠右鍵並拖曳,或捲動滑鼠滾輪
旋轉檢視畫面 Ctrl + 向左/向右鍵再拖曳,或是按住並拖曳

最佳做法

有幾種方法可以縮短 CesiumJS 3D 載入時間。例如:

  • 將下列陳述式新增至轉譯的 HTML,即可啟用同時要求的功能:

    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = <REQUEST_COUNT>
    

    REQUEST_COUNT 越高,資訊方塊載入速度就會越快。不過,若在 REQUEST_COUNT 超過 10 的情況下載入 Chrome 瀏覽器並停用快取,您可能會遇到已知的 Chrome 問題。在多數情況下,我們建議將 REQUEST_COUNT 設為 18,以獲得最佳效能。

  • 啟用略過層級的詳細資料。詳情請參閱這個 Cesium 問題

啟用 showCreditsOnScreen: true 即可正確顯示資料歸因。詳情請參閱政策一文。

轉譯指標

如要找出影格速率,請查看每秒呼叫 requestAnimationFrame 方法的次數。

如要瞭解影格延遲的計算方式,請查看 PerformanceDisplay 類別。

CesiumJS 轉譯器範例

只要提供根圖塊集網址,即可將 CesiumJS 轉譯器與 Map Tiles API 的 3D 圖塊搭配使用。

簡易範例

以下範例會初始化 CesiumJS 轉譯器,然後載入根圖塊集。

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <title>CesiumJS 3D Tiles Simple Demo</title>
  <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
  <link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
  <div id="cesiumContainer"></div>
  <script>

    // Enable simultaneous requests.
    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

    // Create the viewer.
    const viewer = new Cesium.Viewer('cesiumContainer', {
      imageryProvider: false,
      baseLayerPicker: false,
      geocoder: false,
      globe: false,
      // https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/#enabling-request-render-mode
      requestRenderMode: true,
    });

    // Add 3D Tiles tileset.
    const tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
      url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
      // This property is needed to appropriately display attributions
      // as required.
      showCreditsOnScreen: true,
    }));
  </script>
</body>

如要瞭解 requestRenderMode,請參閱啟用要求轉譯模式一文。

HTML 網頁會如下所示。

Places API 整合

您可以將 CesiumJS 與 Places API 搭配使用,以擷取更多資訊。您可以使用 Autocomplete 小工具,前往地點的可視區域。本範例使用 Places Autocomplete API (可按照這裡的說明啟用),以及啟用的 Maps JavaScript API (如按照這些操作說明啟用)。

<!DOCTYPE html>
<head>
 <meta charset="utf-8" />
 <title>CesiumJS 3D Tiles Places API Integration Demo</title>
 <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
 <link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
 <label for="pacViewPlace">Go to a place: </label>
 <input
   type="text"
   id="pacViewPlace"
   name="pacViewPlace"
   placeholder="Enter a location..."
   style="width: 300px"
 />
 <div id="cesiumContainer"></div>
 <script>
   // Enable simultaneous requests.
   Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

   // Create the viewer.
   const viewer = new Cesium.Viewer("cesiumContainer", {
     imageryProvider: false,
     baseLayerPicker: false,
     requestRenderMode: true,
     geocoder: false,
     globe: false,
   });

   // Add 3D Tiles tileset.
   const tileset = viewer.scene.primitives.add(
     new Cesium.Cesium3DTileset({
       url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
       // This property is required to display attributions as required.
       showCreditsOnScreen: true,
     })
   );

   const zoomToViewport = (viewport) => {
     viewer.entities.add({
       polyline: {
         positions: Cesium.Cartesian3.fromDegreesArray([
           viewport.getNorthEast().lng(), viewport.getNorthEast().lat(),
           viewport.getSouthWest().lng(), viewport.getNorthEast().lat(),
           viewport.getSouthWest().lng(), viewport.getSouthWest().lat(),
           viewport.getNorthEast().lng(), viewport.getSouthWest().lat(),
           viewport.getNorthEast().lng(), viewport.getNorthEast().lat(),
         ]),
         width: 10,
         clampToGround: true,
         material: Cesium.Color.RED,
       },
     });
     viewer.flyTo(viewer.entities);
   };

   function initAutocomplete() {
     const autocomplete = new google.maps.places.Autocomplete(
       document.getElementById("pacViewPlace"),
       {
         fields: [
           "geometry",
           "name",
         ],
       }
     );
     autocomplete.addListener("place_changed", () => {
       viewer.entities.removeAll();
       const place = autocomplete.getPlace();
       if (!place.geometry || !place.geometry.viewport) {
         window.alert("No viewport for input: " + place.name);
         return;
       }
       zoomToViewport(place.geometry.viewport);
     });
   }
 </script>
 <script
   async=""
   src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initAutocomplete"
 ></script>
</body>

旋轉無人機檢視畫面

你可以控制相機,透過地圖方塊集呈現動畫效果。這個動畫與 Places API 和 Elevation API 搭配使用時,會模擬任何搜尋點的互動式無人機飛越式飛行活動。

此程式碼範例會引導您瀏覽在 Autocomplete 小工具中選取的地點。

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <title>CesiumJS 3D Tiles Rotating Drone View Demo</title>
  <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
  <link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
  <label for="pacViewPlace">Go to a place: </label>
  <input type="text" id="pacViewPlace" name="pacViewPlace" placeholder="Enter a location..." style="width: 300px" />
  <div id="cesiumContainer"></div>
  <script>
    // Enable simultaneous requests.
    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

    // Create the viewer and remove unneeded options.
    const viewer = new Cesium.Viewer("cesiumContainer", {
      imageryProvider: false,
      baseLayerPicker: false,
      homeButton: false,
      fullscreenButton: false,
      navigationHelpButton: false,
      vrButton: false,
      sceneModePicker: false,
      geocoder: false,
      globe: false,
      infobox: false,
      selectionIndicator: false,
      timeline: false,
      projectionPicker: false,
      clockViewModel: null,
      animation: false,
      requestRenderMode: true,
    });

    // Add 3D Tile set.
    const tileset = viewer.scene.primitives.add(
      new Cesium.Cesium3DTileset({
        url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
        // This property is required to display attributions.
        showCreditsOnScreen: true,
      })
    );

    // Point the camera at a location and elevation, at a viewport-appropriate distance.
    function pointCameraAt(location, viewport, elevation) {
      const distance = Cesium.Cartesian3.distance(
        Cesium.Cartesian3.fromDegrees(
          viewport.getSouthWest().lng(), viewport.getSouthWest().lat(), elevation),
        Cesium.Cartesian3.fromDegrees(
          viewport.getNorthEast().lng(), viewport.getNorthEast().lat(), elevation)
      ) / 2;
      const target = new Cesium.Cartesian3.fromDegrees(location.lng(), location.lat(), elevation);
      const pitch = -Math.PI / 4;
      const heading = 0;
      viewer.camera.lookAt(target, new Cesium.HeadingPitchRange(heading, pitch, distance));
    }

    // Rotate the camera around a location and elevation, at a viewport-appropriate distance.
    let unsubscribe = null;
    function rotateCameraAround(location, viewport, elevation) {
      if(unsubscribe) unsubscribe();
      pointCameraAt(location, viewport, elevation);
      unsubscribe = viewer.clock.onTick.addEventListener(() => {
        viewer.camera.rotate(Cesium.Cartesian3.UNIT_Z);
      });
    }

    function initAutocomplete() {
      const autocomplete = new google.maps.places.Autocomplete(
        document.getElementById("pacViewPlace"), {
          fields: [
            "geometry",
            "name",
          ],
        }
      );
      
      autocomplete.addListener("place_changed", async () => {
        const place = autocomplete.getPlace();
        
        if (!(place.geometry && place.geometry.viewport && place.geometry.location)) {
          window.alert(`Insufficient geometry data for place: ${place.name}`);
          return;
        }
        // Get place elevation using the ElevationService.
        const elevatorService = new google.maps.ElevationService();
        const elevationResponse =  await elevatorService.getElevationForLocations({
          locations: [place.geometry.location],
        });

        if(!(elevationResponse.results && elevationResponse.results.length)){
          window.alert(`Insufficient elevation data for place: ${place.name}`);
          return;
        }
        const elevation = elevationResponse.results[0].elevation || 10;

        rotateCameraAround(
          place.geometry.location,
          place.geometry.viewport,
          elevation
        );
      });
    }
  </script>
  <script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initAutocomplete"></script>
</body>

繪製折線和標籤

這個程式碼範例示範如何將折線和標籤加到地圖。您可以在地圖中加入折線,用於顯示開車和步行路線、顯示屬性界線或計算行車和步行時間。您也可以在不實際轉譯場景的情況下取得屬性。

您可帶領使用者查看社區的精選導覽,或者顯示鄰近的待售房源,也可以在場景中加入看板廣告等 3D 物件。

您可以提供行程摘要,列出您瀏覽過的屬性,並在虛擬物件中顯示這些詳細資料。

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <title>CesiumJS 3D Tiles Polyline and Label Demo</title>
  <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
  <link 
    href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css"
    rel="stylesheet"
  />
</head>
<body>
  <div id="cesiumContainer"></div>
  <script>
    // Enable simultaneous requests.
    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

    // Create the viewer.
    const viewer = new Cesium.Viewer("cesiumContainer", {
      imageryProvider: false,
      baseLayerPicker: false,
      requestRenderMode: true,
      geocoder: false,
      globe: false,
    });

    // Add 3D Tiles tileset.
    const tileset = viewer.scene.primitives.add(
      new Cesium.Cesium3DTileset({
        url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",

        // This property is required to display attributions as required.
        showCreditsOnScreen: true,
      })
    );

    // Draws a circle at the position, and a line from the previous position.
    const drawPointAndLine = (position, prevPosition) => {
      viewer.entities.removeAll();
      if (prevPosition) {
        viewer.entities.add({
          polyline: {
            positions: [prevPosition, position],
            width: 3,
            material: Cesium.Color.WHITE,
            clampToGround: true,
            classificationType: Cesium.ClassificationType.CESIUM_3D_TILE,
          },
        });
      }
      viewer.entities.add({
        position: position,
        ellipsoid: {
          radii: new Cesium.Cartesian3(1, 1, 1),
          material: Cesium.Color.RED,
        },
      });
    };

    // Compute, draw, and display the position's height relative to the previous position.
    var prevPosition;
    const processHeights = (newPosition) => {
      drawPointAndLine(newPosition, prevPosition);

      const newHeight = Cesium.Cartographic.fromCartesian(newPosition).height;
      let labelText = "Current altitude (meters above sea level):\n\t" + newHeight;
      if (prevPosition) {
        const prevHeight =
          Cesium.Cartographic.fromCartesian(prevPosition).height;
        labelText += "\nHeight from previous point (meters):\n\t" + Math.abs(newHeight - prevHeight);
      }
      viewer.entities.add({
        position: newPosition,
        label: {
          text: labelText,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
          pixelOffset: new Cesium.Cartesian2(0, -10),
          showBackground: true,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        }
      });

      prevPosition = newPosition;
    };

    const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
    handler.setInputAction(function (event) {
      const earthPosition = viewer.scene.pickPosition(event.position);
      if (Cesium.defined(earthPosition)) {
        processHeights(earthPosition);
      }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  </script>
</body>

相機軌道

在 Cesium 中,您可以在搜尋點周圍旋轉攝影機,避免與建築物發生衝突。或者,當相機移動時,您可將建築物設為透明。

首先,將相機鎖定在某個點上,然後建立一個相機軌道來展示素材資源。方法是搭配使用攝影機的 lookAtTransform 函式和事件監聽器,如以下程式碼範例所示。

// Lock the camera onto a point.
const center = Cesium.Cartesian3.fromRadians(
  2.4213211833389243,
  0.6171926869414084,
  3626.0426275055174
);

const transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);

viewer.scene.camera.lookAtTransform(
  transform,
  new Cesium.HeadingPitchRange(0, -Math.PI / 8, 2900)
);

// Orbit around this point.
viewer.clock.onTick.addEventListener(function (clock) {
  viewer.scene.camera.rotateRight(0.005);
});

如要進一步瞭解如何控制相機,請參閱「控制攝影機」一文

與 Unreal 的 Cesium 合作

如要搭配使用 Cesium for Unreal 外掛程式與 3D Tiles API,請按照下列步驟操作。

  1. 安裝 Cesium for Unreal 外掛程式。

  2. 建立新的 Unreal 專案。

  3. 連線至 Google Photorealistic 3D Tiles API。

    1. 從選單中選取「Cesium」 >「Cesium」,開啟 Cesium 視窗。

    2. 選取「Blank 3D Tiles Tileset」

    3. 在「World Outliner」中選取這個「Cesium3DTileset」,開啟「Details」面板。

    4. 將「來源」從「從 Cesium Ion」變更為「從網址」

    5. 將網址設為 Google 3D 圖塊網址。

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. 啟用「在畫面上顯示抵免額」,讓系統正確顯示作者資訊。
  4. 這會載入整個世界。如要移動到任何 LatLng,請在「Outliner」CesiumGeoreference面板中選取「CesiumGeoreference」CesiumGeoreference項目,然後在「Details」CesiumGeoreference面板中編輯「Origin 緯度/Longitude/Height」CesiumGeoreference

使用適用於 Unity 的 Cesium

如要搭配 Unity 適用的 Cesium 使用擬真圖塊,請按照下列步驟操作。

  1. 建立新的 Unity 專案。

  2. 在「Package Manager」部分中新增 Scoped Registry (依序前往「Editor」 >「Project Settings」)。

    • 名稱:Cesium

    • 網址:https://unity.pkg.cesium.com

    • 範圍:com.cesium.unity

  3. 安裝適用於 Unity 套件的 Cesium 套件。

  4. 連線至 Google Photorealistic 3D Tiles API。

    1. 從選單中選取「Cesium」 >「Cesium」,開啟 Cesium 視窗。

    2. 按一下「Blank 3D Tiles Tileset」

    3. 在左側面板的「Source」下方的「Tileset Source」選項中,選取「From URL」 (而非 from Cesium Ion)。

    4. 將網址設為 Google 3D 圖塊網址。

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. 啟用「在畫面上顯示抵免額」,讓系統正確顯示作者資訊。
  5. 這會載入整個世界。如要移動到任何 LatLng,請在「Scene Hierarchy」中選取 CesiumGeoreference 項目,然後在「Inspector」中編輯起點緯度/Longitude/Height。

使用 deck.gl

deck.gl 採用 WebGL 技術,是一個開放原始碼 JavaScript 架構,可用於進行大規模的資料視覺化作業。

歸因

請從圖塊 gltf asset 中擷取 copyright 欄位,然後在算繪的檢視畫面中顯示,確保正確顯示資料歸因。詳情請參閱「顯示資料作者資訊」一文。

deck.gl 轉譯器範例

簡易範例

下例會初始化 deck.gl 轉譯器,接著載入 3D 中的地點。請務必將程式碼中的 YOUR_API_KEY 換成實際的 API 金鑰。

<!DOCTYPE html>
<html>
 <head>
   <title>deck.gl Photorealistic 3D Tiles example</title>
   <script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
   <style>
     body { margin: 0; padding: 0;}
     #map { position: absolute; top: 0;bottom: 0;width: 100%;}
     #credits { position: absolute; bottom: 0; right: 0; padding: 2px; font-size: 15px; color: white;
        text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;}
   </style>
 </head>

 <body>
   <div id="map"></div>
   <div id="credits"></div>
   <script>
     const GOOGLE_API_KEY = YOUR_API_KEY;
     const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;
     const creditsElement = document.getElementById('credits');
     new deck.DeckGL({
       container: 'map',
       initialViewState: {
         latitude: 50.0890,
         longitude: 14.4196,
         zoom: 16,
         bearing: 90,
         pitch: 60,
         height: 200
       },
       controller: {minZoom: 8},
       layers: [
         new deck.Tile3DLayer({
           id: 'google-3d-tiles',
           data: TILESET_URL,
           loadOptions: {
            fetch: {
              headers: {
                'X-GOOG-API-KEY': GOOGLE_API_KEY
              }
            }
          },
           onTilesetLoad: tileset3d => {
             tileset3d.options.onTraversalComplete = selectedTiles => {
               const credits = new Set();
               selectedTiles.forEach(tile => {
                 const {copyright} = tile.content.gltf.asset;
                 copyright.split(';').forEach(credits.add, credits);
                 creditsElement.innerHTML = [...credits].join('; ');
               });
               return selectedTiles;
             }
           }
         })
       ]
     });
   </script>
 </body>
</html>

在 Google 擬真成像 3D 圖塊上繪製 2D 圖層

deck.gl TerrainExtension 會將 2D 資料算繪到 3D 介面。舉例來說,您可以在擬真成像 3D 圖塊幾何圖形上方,將建築物足跡的 GeoJSON 包裝起來。

在下方範例中,以「擬真成像 3D 圖塊」介面顯示的多邊形,以視覺化方式呈現建築物圖層。

<!DOCTYPE html>
<html>
 <head>
   <title>Google 3D tiles example</title>
   <script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
   <style>
     body { margin: 0; padding: 0;}
     #map { position: absolute; top: 0;bottom: 0;width: 100%;}
     #credits { position: absolute; bottom: 0; right: 0; padding: 2px; font-size: 15px; color: white;
        text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;}
   </style>
 </head>

 <body>
   <div id="map"></div>
   <div id="credits"></div>
   <script>
     const GOOGLE_API_KEY = YOUR_API_KEY;
     const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;
     const BUILDINGS_URL = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/google-3d-tiles/buildings.geojson'
     const creditsElement = document.getElementById('credits');
     const deckgl = new deck.DeckGL({
       container: 'map',
       initialViewState: {
         latitude: 50.0890,
         longitude: 14.4196,
         zoom: 16,
         bearing: 90,
         pitch: 60,
         height: 200
       },
       controller: true,
       layers: [
         new deck.Tile3DLayer({
           id: 'google-3d-tiles',
           data: TILESET_URL,
           loadOptions: {
            fetch: {
              headers: {
                'X-GOOG-API-KEY': GOOGLE_API_KEY
              }
            }
          },
          onTilesetLoad: tileset3d => {
             tileset3d.options.onTraversalComplete = selectedTiles => {
               const credits = new Set();
               selectedTiles.forEach(tile => {
                 const {copyright} = tile.content.gltf.asset;
                 copyright.split(';').forEach(credits.add, credits);
                 creditsElement.innerHTML = [...credits].join('; ');
               });
               return selectedTiles;
             }
           },
           operation: 'terrain+draw'
         }),
         new deck.GeoJsonLayer({
           id: 'buildings',
           // This dataset is created by CARTO, using other Open Datasets available. More info at: https://3dtiles.carto.com/#about.
           data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/google-3d-tiles/buildings.geojson',
           stroked: false,
           filled: true,
           getFillColor: ({properties}) => {
             const {tpp} = properties;
             // quantiles break
             if (tpp < 0.6249)
               return [254, 246, 181]
             else if (tpp < 0.6780)
               return [255, 194, 133]
             else if (tpp < 0.8594)
               return [250, 138, 118]
             return [225, 83, 131]
           },
           opacity: 0.2,
           extensions: [new deck._TerrainExtension()]
         })
       ]
     });
   </script>
 </body>
</html>