עבודה עם רינדור אריחים בתלת-ממד

קטעי מפה ריאליסטיים בתלת-ממד נמצאים בפורמט glTF הסטנדרטי של OGC, כלומר אפשר להשתמש בכל כלי רינדור שתומך במפרט של קטעי המפה התלת-ממדיים של OGC כדי ליצור את התצוגות החזותיות התלת-ממדיות. לדוגמה, Cesium היא ספריית קוד פתוח בסיסית לעיבוד חזותי של תצוגות תלת-ממדיות.

עבודה עם CesiumJS

CesiumJS היא ספריית JavaScript בקוד פתוח ליצירת תצוגות חזותיות תלת-ממדיות באינטרנט. מידע נוסף על השימוש ב-CesiumJS זמין במאמר מידע על CesiumJS.

פקדי משתמש

לכלי להצגת המשבצות של CesiumJS יש קבוצה רגילה של אמצעי בקרה למשתמש.

פעולה תיאור
תצוגה רחבה לחיצה על הלחצן השמאלי וגרירה
תצוגת זום לוחצים לחיצה ימנית וגוררים, או גוללים בגלגל העכבר
סיבוב התצוגה Ctrl + לחיצה ימינה/שמאלה וגרירה, או לחיצה בלחצן האמצעי וגרירה

שיטות מומלצות

יש כמה דרכים לקצר את זמני הטעינה של CesiumJS 3D. לדוגמה:

  • כדי להפעיל בקשות בו-זמניות, מוסיפים את ההצהרה הבאה ל-HTML שעבר עיבוד:

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

    ככל שהערך של REQUEST_COUNT גבוה יותר, כך הטעינה של המשבצות מהירה יותר. עם זאת, כשמטענים בדפדפן Chrome עם REQUEST_COUNT גדול מ-10 והמטמון מושבת, יכול להיות שתתקלו בבעיה ידועה ב-Chrome. ברוב התרחישים לדוגמה, מומלץ להגדיר את REQUEST_COUNT לערך 18 כדי לקבל ביצועים אופטימליים.

  • מאפשרים לדלג על רמות פירוט. מידע נוסף זמין בבעיה הזו בנושא Cesium.

כדי לוודא שהשיוך של הנתונים מוצג בצורה תקינה, מפעילים את האפשרות showCreditsOnScreen: true. מידע נוסף זמין במאמר מדיניות.

מדדי רינדור

כדי למצוא את קצב הפריימים, בודקים כמה פעמים לשנייה מתבצעת קריאה לשיטה requestAnimationFrame.

כדי לראות איך מחושב זמן האחזור של המסגרת, אפשר לעיין בכיתה PerformanceDisplay.

דוגמאות לכלי לרינדור של CesiumJS

כדי להשתמש במעבד הגרפיקה של CesiumJS עם המשבצות התלת-ממדיות של Map Tiles API, פשוט מספקים את כתובת ה-URL של קבוצת המשבצות ברמה הבסיסית.

דוגמה פשוטה

בדוגמה הבאה מתבצעת אתחול של המרתח (renderer) של 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 כדי לאחזר מידע נוסף. אתם יכולים להשתמש בווידג'ט ההשלמה האוטומטית כדי לעבור למסך התצוגה של Places. בדוגמה הזו נעשה שימוש ב-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>

תצוגה מסתובבת של רחפן

אתם יכולים לשלוט במצלמה כדי ליצור אנימציה של מעבר בין הפריטים ב-tileset. בשילוב עם Places API ו-Elevation API, האנימציה הזו מדמה מעבר אווירי אינטראקטיבי של רחפן מעל כל נקודה מעניינת.

דוגמת הקוד הזו מאפשרת לכם לעוף סביב המקום שבחרתם בווידג'ט של ההשלמה האוטומטית.

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

איך מציירים קווים פוליגונים ותוויות

דוגמת הקוד הזו ממחישה איך להוסיף קווים פוליגונים ותוויות למפה. אפשר להוסיף למפה קווים פוליגונים כדי להציג מסלולי נסיעה והליכה, להציג את גבולות הנכס או לחשב את משך הנסיעה וההליכה. אפשר גם לקבל מאפיינים בלי לבצע עיבוד של הסצנה בפועל.

תוכלו לקחת את המשתמשים לסיור מובנה בשכונה, או להציג נכסים שכנים שמוצעים כרגע למכירה, ואז להוסיף לסצנה אובייקטים תלת-ממדיים כמו מודעות חוצות.

תוכלו לסכם נסיעה, לרשום את הנכסים שצפיתם בהם ולהציג את הפרטים האלה באובייקטים וירטואליים.

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

מידע נוסף על שליטה במצלמה זמין במאמר שליטה במצלמה.

עבודה עם Cesium for Unreal

כדי להשתמש בפלאגין Cesium for Unreal עם 3D Tiles API, פועלים לפי השלבים הבאים.

  1. מתקינים את הפלאגין Cesium for Unreal.

  2. יוצרים פרויקט חדש ב-Unreal.

  3. מתחברים ל-Google Photorealistic 3D Tiles API.

    1. פותחים את החלון של Cesium על ידי בחירה באפשרות Cesium > Cesium בתפריט.

    2. בוחרים באפשרות ערכת משבצות ריקות של משבצות תלת-ממד.

    3. בכלי להצגת קווי המתאר של העולם, פותחים את החלונית פרטים על ידי בחירה בCesium3DTileset הזה.

    4. משנים את המקור מ-מ-Cesium Ion ל-מכתובת URL.

    5. מגדירים את כתובת ה-URL ככתובת ה-URL של Google 3D Tiles.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. מפעילים את האפשרות הצגת הקרדיטים במסך כדי להציג את הקרדיטים בצורה תקינה.
  4. כך העולם נטען. כדי לעבור ל-LatLng כלשהו, בוחרים את הפריט CesiumGeoreference בחלונית Outliner, ואז עורכים את הערכים של Origin Latitude/Longitude/Height בחלונית Details.

עבודה עם Cesium ל-Unity

כדי להשתמש בתמונות מפורטות במיוחד עם Cesium for Unity, פועלים לפי השלבים הבאים.

  1. יוצרים פרויקט חדש ב-Unity.

  2. מוסיפים רשומת Scoped Registry חדשה בקטע Package Manager (דרך Editor‏ > Project Settings).

    • שם: צסיום

    • כתובת URL: https://unity.pkg.cesium.com

    • היקפים: com.cesium.unity

  3. מתקינים את החבילה של Cesium for Unity.

  4. מתחברים ל-Google Photorealistic 3D Tiles API.

    1. פותחים את החלון של Cesium על ידי בחירה באפשרות Cesium > Cesium בתפריט.

    2. לוחצים על ערכת משבצות ריקה של משבצות תלת-ממדיות.

    3. בחלונית הימנית, באפשרות Tileset Source בקטע Source, בוחרים באפשרות From URL (במקום From Cesium Ion).

    4. מגדירים את כתובת ה-URL ככתובת ה-URL של Google 3D Tiles.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. מפעילים את האפשרות הצגת הקרדיטים במסך כדי להציג את הקרדיטים בצורה תקינה.
  5. כך העולם נטען. כדי לעבור לאתר כלשהו, בוחרים את הפריט CesiumGeoreference בהיררכיית הסצנה, ואז עורכים את קו הרוחב/קו האורך/הגובה של המקור בכלי לבדיקת השגיאות.

עבודה עם deck.gl

deck.gl, שמבוסס על WebGL, הוא מסגרת JavaScript בקוד פתוח ליצירת תצוגות חזותיות של נתונים בקנה מידה גדול וביצועים גבוהים.

שיוך (Attribution)

כדי להציג את השיוך לנתונים בצורה נכונה, צריך לחלץ את השדה copyright מ-tiles gltf asset ולהציג אותו בתצוגה שעבר רינדור. למידע נוסף, ראו שיוכים של נתוני תצוגה.

דוגמאות למעבד גרפיקה (renderer) של deck.gl

דוגמה פשוטה

בדוגמה הבאה מתבצעת אתחול של המרתח (renderer) של 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

ה-extension TerrainExtension של deck.gl מאפשר להציג נתונים דו-ממדיים על גבי משטח תלת-ממדי. לדוגמה, אפשר להציג את קובץ ה-GeoJSON של שטח הבניין מעל לגיאומטריה של קטעי המפה הריאליסטיים בתלת-ממד.

בדוגמה הבאה מוצגת שכבת בניינים עם הפוליגונים שמותאמים לפני השטח של המשבצות התלת-ממדיות הריאליסטיות.

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