3D Karo Oluşturucu ile çalışma

Gerçekçi 3D parçalar OGC standart glTF biçimindedir. Bu, 3D görselleştirmelerinizi oluşturmak için OGC 3D Parçalar spesifikasyonunu destekleyen herhangi bir oluşturucu kullanabileceğiniz anlamına gelir. Örneğin, Cesium, 3D görselleştirmeleri oluşturmak için temel bir açık kaynak kitaplığıdır.

CesiumJS ile çalışma

CesiumJS, web'de 3D görselleştirme için açık kaynaklı bir JavaScript kitaplığıdır. CesiumJS'yi kullanma hakkında daha fazla bilgi için CesiumJS'yi öğrenme başlıklı makaleyi inceleyin.

Kullanıcı denetimleri

CesiumJS karo oluşturma aracında standart bir kullanıcı kontrol grubu bulunur.

İşlem Açıklama
Yatay kaydırma görünümü Sol tıklama ve sürükleme
Yakınlaştırma görünümü Sağ tıklayıp sürükleyin veya fare tekerleğini kaydırın
Görünümü döndürme Ctrl + sol/sağ tıklama ve sürükleme veya orta tıklama ve sürükleme

En iyi uygulamalar

CesiumJS 3D yükleme sürelerini azaltmak için uygulayabileceğiniz birkaç yaklaşım vardır. Örneğin:

  • Oluşturma HTML'nize aşağıdaki ifadeyi ekleyerek eşzamanlı istekleri etkinleştirin:

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

    REQUEST_COUNT ne kadar yüksek olursa karolar o kadar hızlı yüklenir. Ancak REQUEST_COUNT10'dan büyük ve önbellek devre dışıyken Chrome Tarayıcı'da yükleme yaparken bilinen bir Chrome sorunuyla karşılaşabilirsiniz. Çoğu kullanım alanı için optimum performans için REQUEST_COUNT değerini 18 olarak ayarlamanızı öneririz.

  • Ayrıntı düzeylerini atlama özelliğini etkinleştirin. Daha fazla bilgi için bu Cesium sorununa göz atın.

showCreditsOnScreen: true'ü etkinleştirerek veri ilişkilendirmelerini doğru şekilde gösterdiğinizden emin olun. Daha fazla bilgi için Politikalar başlıklı makaleyi inceleyin.

Oluşturma metrikleri

Kare hızını bulmak için requestAnimationFrame yönteminin saniyede kaç kez çağrıldığına bakın.

Kare gecikmesinin nasıl hesaplandığını görmek için PerformanceDisplay sınıfına göz atın.

CesiumJS oluşturma aracı örnekleri

Kök karo grubu URL'sini sağlayarak CesiumJS oluşturma aracını Map Tiles API'nin 3D karolarıyla kullanabilirsiniz.

Basit örnek

Aşağıdaki örnekte, CesiumJS oluşturma aracı başlatılır ve ardından kök karo grubu yüklenir.

<!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 hakkında bilgi edinmek için İstek oluşturma modunu etkinleştirme başlıklı makaleyi inceleyin.

HTML sayfası burada gösterildiği gibi oluşturulur.

Places API entegrasyonu

Daha fazla bilgi almak için CesiumJS'yi Places API ile birlikte kullanabilirsiniz. Otomatik Tamamlama widget'ını kullanarak Yerler'in görüntü alanını yakınlaştırabilirsiniz. Bu örnekte, bu talimatları uygulayarak etkinleştirilen Places Otomatik Tamamlama API'si ve bu talimatları uygulayarak etkinleştirilen Maps JavaScript API kullanılmaktadır.

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

Dönen drone görünümü

Kamerayı kontrol ederek karo grubunu canlandırabilirsiniz. Bu animasyon, Yerler API'si ve Yükseklik API'si ile birlikte kullanıldığında herhangi bir önemli yerin drone ile çekilmiş etkileşimli bir havadan görünümünü simüle eder.

Bu kod örneği, Otomatik Tamamlama widget'ında seçtiğiniz yerin etrafında uçmanızı sağlar.

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

Çoklu çizgiler ve etiketler çizme

Bu kod örneğinde, bir haritaya çoklu çizgi ve etiket ekleme işlemi gösterilmektedir. Arabayla ve yürüyerek yol tariflerini göstermek, mülk sınırlarını göstermek ya da arabayla ve yürüyerek yol sürelerini hesaplamak için haritaya çoklu çizgiler ekleyebilirsiniz. Ayrıca, sahneyi oluşturmadan da özellikleri alabilirsiniz.

Kullanıcıları bir mahallede özel olarak hazırlanmış bir tura çıkarabilir veya şu anda indirimde olan komşu mülkleri gösterebilir, ardından sahneye reklam panoları gibi 3D nesneler ekleyebilirsiniz.

Görüntülediğiniz tesisleri listeleyerek ve bu ayrıntıları sanal nesnelerde göstererek bir geziyi özetleyebilirsiniz.

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

Kamera yörüngesi

Cesium'da, kamerayı bir ilgi noktasının etrafında döndürerek binalarla çarpışmalardan kaçınabilirsiniz. Alternatif olarak, kamera binaların içinden geçerken binaları saydam hâle getirebilirsiniz.

Öncelikle kamerayı bir noktaya kilitleyin, ardından öğenizi göstermek için kamera yörüngesi oluşturabilirsiniz. Bunu, kameranın lookAtTransform işlevini bir etkinlik dinleyiciyle kullanarak yapabilirsiniz. Bu kod örneğinde gösterildiği gibi.

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

Kamerayı kontrol etme hakkında daha fazla bilgi için Kamerayı kontrol etme başlıklı makaleyi inceleyin.

Unreal için Cesium ile çalışma

Unreal için Cesium eklentisini 3D Tiles API ile kullanmak istiyorsanız aşağıdaki adımları uygulayın.

  1. Unreal için Cesium eklentisini yükleyin.

  2. Yeni bir Unreal projesi oluşturun.

  3. Google Gerçekçi Fotoğraf 3B Parçalar API'sine bağlanın.

    1. Menüden Cesium > Cesium'u seçerek Cesium penceresini açın.

    2. Boş 3D Parçalar Taş Grubu'nu seçin.

    3. Dünya Anahatlayıcısı'nda bu Cesium3DTileset'i seçerek Ayrıntılar panelini açın.

    4. KaynakCesium Ion'dan URL'den olarak değiştirin.

    5. URL'yi Google 3D Kartlar URL'si olarak ayarlayın.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Atıfları doğru şekilde görüntülemek için Kredileri Ekranda Göster'i etkinleştirin.
  4. Bu işlemle dünya yüklenir. Herhangi bir enlem/boylam değerine gitmek için Anahat panelinde CesiumGeoreference öğesini seçin ve ardından Ayrıntılar panelinde Kaynak Enlem/Boylam/Yükseklik'i düzenleyin.

Unity için Cesium ile çalışma

Unity için Cesium ile fotogerçekçi karoları kullanmak istiyorsanız aşağıdaki adımları uygulayın.

  1. Yeni bir Unity projesi oluşturun.

  2. Paket Yöneticisi bölümünde (Düzenleyici > Proje Ayarları üzerinden) yeni bir Kapsamlı Kayıt Defteri ekleyin.

    • Ad: Sezyum

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

    • Kapsamlar: com.cesium.unity

  3. Unity için Cesium paketini yükleyin.

  4. Google Gerçekçi Fotoğraf 3B Parçalar API'sine bağlanın.

    1. Menüden Cesium > Cesium'u seçerek Cesium penceresini açın.

    2. Boş 3D Karolar Karo Grubu'nu tıklayın.

    3. Sol paneldeki Kaynak bölümündeki Kart Grubu Kaynağı seçeneğinde URL'den'i (Cesium Ion'dan yerine) seçin.

    4. URL'yi Google 3D Kartlar URL'si olarak ayarlayın.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Atıfları doğru şekilde görüntülemek için Kredileri Ekranda Göster'i etkinleştirin.
  5. Bu işlemle dünya yüklenir. Herhangi bir enlem/boylam değerine gitmek için Sahne Hiyerarşisi'nde CesiumGeoreference öğesini seçin ve ardından İnceleyici'de Kaynak Enlem/Boylam/Yükseklik değerini düzenleyin.

deck.gl ile çalışma

WebGL destekli deck.gl, yüksek performanslı, büyük ölçekli veri görselleştirmeleri için açık kaynak bir JavaScript çerçevesidir.

İlişkilendirme

copyrightasset karo gltf'sinden copyright alanını ayıklayıp oluşturulan görünümde görüntüleyerek veri ilişkilendirmelerini doğru şekilde gösterdiğinizden emin olun. Daha fazla bilgi için Görüntülü reklam verileri ilişkilendirmeleri bölümüne bakın.

deck.gl oluşturma aracı örnekleri

Basit örnek

Aşağıdaki örnekte deck.gl oluşturma aracı başlatılır ve ardından bir yer 3D olarak yüklenir. Kodunuzda YOUR_API_KEY değerini gerçek API anahtarınızla değiştirdiğinizden emin olun.

<!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 Gerçekçi Fotoğraf 3B Parçalar'ın üzerine 2D katmanlar görselleştirme

deck.gl TerrainExtension, aksi takdirde 2D verileri 3D bir yüzeyde oluşturur. Örneğin, bir binanın yerleşkesinin GeoJSON'ını Gerçekçi Fotoğraf 3B Parçalar geometrisinin üzerine yerleştirebilirsiniz.

Aşağıdaki örnekte, fotogerçekçi 3D karo yüzeyine uyarlanmış poligonlarla bir bina katmanı görselleştirilmiştir.

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