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

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

עבודה עם CesiumJS

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

פקדי משתמש

מעבד המשבצות של CesiumJS כולל קבוצה סטנדרטית של בקרות משתמש.

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

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

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

  • כדי להפעיל בקשות בו-זמניות, יש להוסיף את ההצהרה הבאה ל-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 בקרבת ה-API. פשוט מספקים את כתובת ה-URL של ערכת האריחים ברמה הבסיסית.

דוגמה פשוטה

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

שילוב API

תוכלו להשתמש ב-CesiumJS עם Places API כדי לאחזר מידע נוסף. ניתן להשתמש בווידג'ט להשלמה אוטומטית על מנת לטוס לאזור התצוגה של מקומות. בדוגמה הזו נשתמש בממשק ה-API להשלמה אוטומטית של מקומות, שמופעל על ידי ביצוע ההוראות האלו, וב-API JavaScript של מפות Google, שמופעל על ידי ביצוע ההוראות האלו.

<!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 של המצלמה עם event listener, כמו בדוגמת הקוד הזו.

// 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 עבור Unreal

כדי להשתמש ב-Cesium for Unreal Plugin עם ממשק ה-API של 3D+, בצעו את השלבים הבאים.

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

  2. יצירת פרויקט חדש של Unreal.

  3. התחברות ל-API של משבצות תלת-ממדיות בפוטוריאליסטי של Google.

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

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

    3. ב-World Outliner, פותחים את החלונית Details על ידי לחיצה על Cesium3DTileset.

    4. משנים את הערך Source מ-From Cesium Ion ל-From URL.

    5. יש להגדיר את כתובת ה-URL כך שתהיה כתובת ה-URL של משבצות תלת-הממד של Google.

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

עבודה עם Cesium ל-Unity

כדי להשתמש במשבצות תמונה מציאותיות עם Cesium ל-Unity, יש לפעול לפי השלבים הבאים.

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

  2. מוסיפים רישום חדש עם היקף הרשאות בקטע 'מנהל החבילות' (דרך Editor > Project Settings).

    • שם: צסיום

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

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

  3. התקנת חבילת Cesium ל-Unity.

  4. התחברות ל-API של משבצות תלת-ממדיות בפוטוריאליסטי של Google.

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

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

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

    4. יש להגדיר את כתובת ה-URL לכתובת ה-URL של משבצות תלת-הממד של Google.

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

עבודה עם default.gl

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

שיוך (Attribution)

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

דוגמאות לרינדור של דסקית.gl

דוגמה פשוטה

הדוגמה הבאה מפעילה את כלי הרינדור של הפרטים ב-Cock.gl, ואז טוענת מקום בתלת-ממד. בקוד, הקפידו להחליף את 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 בתלת-ממד.

TerrainExtension ב-Cide.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>