Làm việc với trình kết xuất Thẻ 3D

Ô 3D ảnh thực tế trong Định dạng glTF tiêu chuẩn của OGC, nghĩa là bạn có thể sử dụng bất kỳ trình kết xuất nào hỗ trợ thông số Thẻ thông tin 3D OGC để tạo các hình ảnh 3D của bạn. Ví dụ: Xesi là thư viện nguồn mở nền tảng để kết xuất hình ảnh trực quan 3D.

Làm việc với CesiumJS

CesiumJS là một thư viện JavaScript nguồn mở để hiển thị 3D trên web. Để biết thêm thông tin về cách sử dụng CesiumJS, hãy xem Tìm hiểu về CesiumJS.

Quyền kiểm soát của người dùng

Trình kết xuất thẻ thông tin CesiumJS có một bộ các chế độ kiểm soát tiêu chuẩn cho người dùng.

Hành động Mô tả
Chế độ xem có thể xoay Nhấp chuột trái & kéo
Chế độ xem thu phóng Nhấp chuột phải và kéo hoặc cuộn con lăn chuột
Xoay chế độ xem Ctrl + nhấp chuột trái/phải & kéo, hoặc nhấp chuột giữa và kéo

Các phương pháp hay nhất

Có một số phương pháp bạn có thể thực hiện để giảm tải CesiumJS 3D lần. Ví dụ:

  • Bật các yêu cầu đồng thời bằng cách thêm câu lệnh sau vào HTML hiển thị của bạn:

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

    REQUEST_COUNT càng cao thì càng nhanh tải ô. Tuy nhiên, khi tải trong trình duyệt Chrome bằng REQUEST_COUNT lớn hơn 10 và bộ nhớ đệm bị tắt, bạn có thể gặp phải lỗi Vấn đề về Chrome. Đối với hầu hết các trường hợp sử dụng, bạn nên đặt REQUEST_COUNT là 18 để có kết quả tối ưu hiệu suất.

  • Bật bỏ qua các cấp chi tiết. Để biết thêm thông tin, hãy xem Vấn đề về xesi.

Đảm bảo rằng bạn hiển thị đúng các thuộc tính dữ liệu bằng cách bật showCreditsOnScreen: true. Để biết thêm thông tin, hãy xem Chính sách.

Chỉ số kết xuất

Để biết tốc độ khung hình, hãy xem xét số lần mỗi giây requestAnimationFrame được gọi.

Để biết cách tính độ trễ khung hình, hãy xem PerformanceDisplay .

Ví dụ về trình kết xuất CesiumJS

Bạn có thể sử dụng trình kết xuất CesiumJS với Thẻ thông tin 3D của API Thẻ thông tin bản đồ bằng cách chỉ cần cung cấp URL tập hợp thẻ thông tin gốc.

Ví dụ đơn giản

Ví dụ sau đây sẽ khởi chạy trình kết xuất CesiumJS, rồi tải thư mục gốc loạt ô.

<!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>

Để biết thông tin về requestRenderMode, hãy xem Bật chế độ hiển thị yêu cầu.

Trang HTML hiển thị như dưới đây.

Tích hợp API Địa điểm

Bạn có thể sử dụng CesiumJS với API Địa điểm để truy xuất thêm thông tin. Bạn có thể sử dụng tiện ích Tự động hoàn thành để di chuyển đến khung nhìn của Địa điểm. Ví dụ này sử dụng API Tự động hoàn thành của địa điểm, được bật bởi theo các hướng dẫn này, và API JavaScript của Maps, được bật bằng theo hướng dẫn này.

<!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>

Xoay chế độ xem máy bay không người lái

Bạn có thể điều khiển máy ảnh để tạo ảnh động thông qua bộ ô. Khi kết hợp với Places API và Height API (API Độ cao), ảnh động này mô phỏng một ảnh động tương tác bay không người lái ở bất kỳ địa điểm ưa thích nào.

Mã mẫu này sẽ đưa bạn đi xung quanh địa điểm mà bạn đã chọn trong Tiện ích tự động hoàn thành.

<!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>

Vẽ hình nhiều đường và nhãn

Mã mẫu này minh hoạ cách thêm hình nhiều đường và nhãn vào bản đồ. Bạn có thể thêm hình nhiều đường vào bản đồ để hiển thị chỉ đường lái xe và đi bộ hoặc để hiển thị ranh giới của địa điểm hoặc để tính toán thời gian lái xe và đi bộ. Bạn cũng có thể nhận các thuộc tính mà không thực sự kết xuất cảnh.

Bạn có thể đưa người dùng tham gia một chuyến tham quan có chọn lọc một khu vực xung quanh, hoặc bạn có thể hiển thị các bất động sản lân cận hiện đang được bán, sau đó bạn có thể thêm mô hình 3D chẳng hạn như biển quảng cáo tại hiện trường.

Bạn có thể tóm tắt một chuyến đi, liệt kê những cơ sở lưu trú mà bạn đã xem, cho thấy những chi tiết này trong đối tượng ảo.

<!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>

Quỹ đạo của máy ảnh

Trong Cesium, bạn có thể quay máy ảnh theo quỹ đạo xung quanh một địa điểm yêu thích, tránh va chạm với các toà nhà. Ngoài ra, bạn có thể làm cho các toà nhà trở nên trong suốt khi máy ảnh di chuyển qua các điểm đó.

Trước tiên, khoá máy ảnh vào một điểm, sau đó bạn có thể tạo quỹ đạo của máy ảnh để làm nổi bật tài sản của bạn. Bạn có thể thực hiện việc này bằng cách sử dụng lookAtTransform bằng trình nghe sự kiện, như minh hoạ trong mã mẫu này.

// 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);
});

Để biết thêm thông tin về cách điều khiển camera, hãy xem Điều khiển camera

Hợp tác với Cesium cho Unreal

Để sử dụng Cesium cho Trình bổ trợ Unreal với API Thẻ thông tin 3D, hãy làm theo các bước bên dưới.

  1. Cài đặt Cesium cho trình bổ trợ Unreal.

  2. Tạo một dự án Unreal mới.

  3. Kết nối với API Thẻ thông tin 3D thực tế của Google.

    1. Mở cửa sổ Cesium bằng cách chọn Cesium > Cesium trong trình đơn.

    2. Chọn Ô tô 3D trống.

    3. Trong World Outliner ( ai cũng được), hãy mở bảng điều khiển Details (Chi tiết) bằng cách chọn biểu tượng này Bộ thẻ thông tin Cesium3D.

    4. Thay đổi Nguồn từ Từ Cesium Ion thành Từ URL.

    5. Đặt URL thành URL Thẻ thông tin 3D của Google.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Bật tính năng Hiển thị phần ghi công trên màn hình để hiển thị thông tin ghi nhận sự đóng góp một cách hợp lý.
  4. Quá trình này sẽ làm cho cả thế giới tải nội dung. Để di chuyển đến vĩ độ/kinh độ bất kỳ, hãy chọn Mục CesiumGeoreference trong bảng điều khiển Outliner rồi chỉnh sửa Vĩ độ/Kinh độ/Chiều cao ban đầu trong bảng điều khiển Chi tiết.

Dùng Cesium cho Unity

Để sử dụng thẻ thông tin ảnh thực bằng Cesium cho Unity, hãy làm theo các bước dưới đây.

  1. Tạo một dự án Unity mới.

  2. Thêm một Sổ đăng ký có giới hạn mới trong phần Trình quản lý gói (thông qua Trình chỉnh sửa > Cài đặt dự án).

    • Tên: Cesium

    • URL: https://unity.pkg.cesium.com

    • (Các) phạm vi: com.cesium.unity

  3. Cài đặt gói Cesium cho Unity.

  4. Kết nối với API Thẻ thông tin 3D thực tế của Google.

    1. Mở cửa sổ Cesium bằng cách chọn Cesium > Cesium trong trình đơn.

    2. Nhấp vào Thẻ thông tin 3D trống.

    3. Trên bảng điều khiển bên trái, trong tùy chọn Nguồn thẻ thông tin bên dưới Nguồn, hãy chọn Từ URL (thay vì Từ Cesium Ion).

    4. Đặt URL thành URL Thẻ thông tin 3D của Google.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Bật tính năng Hiển thị phần ghi công trên màn hình để hiển thị thông tin ghi nhận sự đóng góp một cách hợp lý.
  5. Quá trình này sẽ làm cho cả thế giới tải nội dung. Để di chuyển đến vĩ độ/kinh độ bất kỳ, hãy chọn Mục CesiumGeoreference trong Scene Hierarchy (Hệ phân cấp cảnh) rồi chỉnh sửa Vĩ độ/Kinh độ/Chiều cao của điểm khởi hành trong Trình kiểm tra.

Xử lý Deck.gl

deck.gl, được hỗ trợ bởi WebGL, là khung JavaScript nguồn mở mang lại hiệu suất cao, trực quan hoá dữ liệu trên quy mô lớn.

Phân bổ

Đảm bảo rằng bạn hiển thị đúng các thuộc tính dữ liệu bằng cách trích xuất copyright từ ô gltf asset rồi hiện trường đó trên chế độ xem được kết xuất. Cho thông tin khác, xem Phân bổ dữ liệu hiển thị.

Ví dụ về kết xuất đồ hoạ Deck.gl

Ví dụ đơn giản

Ví dụ sau đây sẽ khởi chạy trình kết xuất Deck.gl, sau đó tải một vị trí ở chế độ 3D. Trong mã của bạn, hãy nhớ thay thế YOUR_API_KEY bằng khoá API thực.

<!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>

Trực quan hoá các lớp 2D trên Thẻ thông tin 3D thực tế của Google

The Deck.gl TerrainExtension kết xuất dữ liệu 2D trên bề mặt 3D. Ví dụ: bạn có thể xếp chồng GeoJSON của một dấu vết toà nhà trên khối hình học Ô 3D ảnh thực tế.

Trong ví dụ sau, một lớp toà nhà được hiển thị bằng hình đa giác được điều chỉnh cho phù hợp với bề mặt Ô 3D ảnh thực tế.

<!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>