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

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

עבודה עם CesiumJS

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

פקדי משתמש

לכלי להצגת המשבצות של 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.

דוגמאות לרינדור CisiumJS

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

דוגמה פשוטה

בדוגמה הבאה מתבצעת אתחול של ה-renderer של CesiumJS, ולאחר מכן טעינת tileset ברמה הבסיסית.

<!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 כדי לאחזר מידע נוסף. אתם יכולים להשתמש בווידג'ט של ההשלמה האוטומטית כדי לעבור למסך התצוגה של מפות Google. בדוגמה הזו נעשה שימוש ב-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, האנימציה הזו מדמה גיחה רחפן אינטראקטיבית מעל כל נקודה מעניינת.

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

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

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

עבודה עם Cisium 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. משנים את המקור מ-From Cisium Ion ל-מכתובת אתר.

    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. כדי לפתוח את חלון צסיום, בוחרים באפשרות צסיום > צסיום בתפריט.

    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 מהמשבצות 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 של שטח הבניין מעל לגיאומטריה של קטעי המפה הריאליסטיים בתלת-ממד.

בדוגמה הבאה מוצגת שכבת בניינים עם הפוליגונים שמותאמים למשטח של 'שבבי 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>