با یک رندر کاشی سه بعدی کار کنید

کاشی‌های سه‌بعدی فوتورئالیستی در قالب استاندارد OGC glTF هستند، به این معنی که می‌توانید از هر رندری که از مشخصات OGC 3D Tiles پشتیبانی می‌کند برای ساخت تجسم‌های سه بعدی خود استفاده کنید. به عنوان مثال، Cesium یک کتابخانه منبع باز اساسی برای ارائه تصاویر سه بعدی است.

با CesiumJS کار کنید

CesiumJS یک کتابخانه جاوا اسکریپت منبع باز برای تجسم سه بعدی در وب است. برای اطلاعات بیشتر در مورد استفاده از CesiumJS، به Learn 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 را برای عملکرد بهینه توصیه می کنیم.

  • رد شدن از سطوح جزئیات را فعال کنید. برای اطلاعات بیشتر، این موضوع سزیوم را ببینید.

با فعال کردن showCreditsOnScreen: true ، اطمینان حاصل کنید که اسناد داده ها را به درستی نمایش می دهید. برای اطلاعات بیشتر، به سیاست ها مراجعه کنید.

معیارهای رندرینگ

برای یافتن نرخ فریم، نگاه کنید که متد requestAnimationFrame چند بار در ثانیه فراخوانی می شود.

برای مشاهده نحوه محاسبه تاخیر فریم، نگاهی به کلاس PerformanceDisplay بیندازید.

نمونه های رندر CesiumJS

می‌توانید از رندر CesiumJS با کاشی‌های سه‌بعدی Map Tiles API به سادگی با ارائه URL مجموعه tileset root استفاده کنید.

مثال ساده

مثال زیر رندر CesiumJS را مقداردهی اولیه می کند و سپس مجموعه tileset root را بارگذاری می کند.

<!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 برای بازیابی اطلاعات بیشتر استفاده کنید. می‌توانید از ویجت تکمیل خودکار برای پرواز به نمای مکان‌ها استفاده کنید. این مثال از 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>

مدار دوربین

در سزیوم، می توانید دوربین را حول یک نقطه مورد نظر بچرخانید و از برخورد با ساختمان ها جلوگیری کنید. از طرف دیگر، می توانید ساختمان ها را هنگامی که دوربین در آنها حرکت می کند شفاف کنید.

ابتدا دوربین را روی یک نقطه قفل کنید، سپس می توانید یک مدار دوربین برای نمایش دارایی خود ایجاد کنید. همانطور که در این نمونه کد نشان داده شده است، می توانید این کار را با استفاده از عملکرد 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 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 باز کنید.

    4. منبع را از From Cesium Ion به From URL تغییر دهید.

    5. URL را به عنوان URL کاشی های سه بعدی Google تنظیم کنید.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. نمایش اعتبارات روی صفحه را فعال کنید تا اسناد به درستی نمایش داده شود.
  4. این دنیا را بار می کند. برای انتقال به هر LatLng، مورد CesiumGeoreference را در پانل Outliner انتخاب کنید و سپس مبدا Latitude/Longitude/Height را در پانل Details ویرایش کنید.

با سزیوم برای یونیتی کار کنید

برای استفاده از کاشی های فوتورئالیستی با سزیوم برای یونیتی، مراحل زیر را دنبال کنید.

  1. یک پروژه یونیتی جدید ایجاد کنید.

  2. یک رجیستری Scoped جدید در بخش Package Manager (از طریق ویرایشگر > تنظیمات پروژه ) اضافه کنید.

    • نام: سزیم

    • آدرس اینترنتی: https://unity.pkg.cesium.com

    • محدوده (ها): com.cesium.unity

  3. بسته Cesium for Unity را نصب کنید.

  4. به Google Photorealistic 3D Tiles API متصل شوید.

    1. با انتخاب Cesium > Cesium از منو، پنجره Cesium را باز کنید.

    2. روی Blank 3D Tiles Tileset کلیک کنید.

    3. در پانل سمت چپ، در گزینه Tileset Source در زیر منبع ، از URL (به جای From Cesium Ion) را انتخاب کنید.

    4. URL را روی URL کاشی های سه بعدی Google تنظیم کنید.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. نمایش اعتبارات روی صفحه را فعال کنید تا اسناد به درستی نمایش داده شود.
  5. این دنیا را بار می کند. برای انتقال به هر LatLng، مورد CesiumGeoreference را در سلسله مراتب صحنه انتخاب کنید و سپس مبدا عرض جغرافیایی/طولایی/ارتفاع را در Inspector ویرایش کنید.

با deck.gl کار کنید

deck.gl که توسط WebGL پشتیبانی می‌شود، یک چارچوب جاوا اسکریپت منبع باز برای تجسم داده‌های با کارایی بالا و در مقیاس بزرگ است.

انتساب

با استخراج فیلد copyright از tiles gltf asset و سپس نمایش آن در نمای رندر شده، اطمینان حاصل کنید که اسناد داده ها را به درستی نمایش می دهید. برای اطلاعات بیشتر، به نمایش اسناد داده‌ها مراجعه کنید.

نمونه های رندر deck.gl

مثال ساده

مثال زیر رندر deck.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 تجسم کنید

Deck.gl TerrainExtension در غیر این صورت داده های دوبعدی را روی یک سطح سه بعدی ارائه می کند. برای مثال، می‌توانید 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>