العمل مع عارض ثلاثي الأبعاد للمربّعات

مربّعات الصور الواقعية ثلاثية الأبعاد بتنسيق glTF القياسي لـ OGC، ما يعني أنّه يمكنك استخدام أي عارض يتوافق مع مواصفات OGC 3D Tiles لإنشاء العروض المرئية الثلاثية الأبعاد. على سبيل المثال، تُعد 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 مع مربعات 3D في Map Tiles 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 كما هو موضح هنا.

دمج Places API

يمكنك استخدام CesiumJS مع واجهة برمجة التطبيقات للأماكن لاسترداد المزيد من المعلومات. يمكنك استخدام أداة الإكمال التلقائي للانتقال إلى إطار عرض الأماكن. يستخدم هذا المثال واجهة برمجة التطبيقات الخاصة بميزة "الإكمال التلقائي" للأماكن، التي يتم تفعيلها من خلال اتّباع هذه التعليمات، وواجهة برمجة تطبيقات JavaScript للخرائط التي يتم تفعيلها من خلال اتّباع هذه التعليمات.

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

عرض دوّار من طائرة تصوير

يمكنك التحكّم في الكاميرا لتحريك مجموعة المربّعات. عند دمجها مع واجهة برمجة تطبيقات الأماكن وواجهة برمجة التطبيقات للارتفاع، تقوم هذه الصورة المتحركة بمحاكاة جسر تفاعلي لطائرة طائرة بدون طيار لأي نقطة اهتمام.

يرشدك نموذج التعليمات البرمجية هذا حول المكان الذي حددته في أداة الإكمال التلقائي.

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

لمزيد من المعلومات حول التحكّم في الكاميرا، يمكنك الاطّلاع على التحكّم في الكاميرا.

العمل مع Cesium for Unreal

لاستخدام المكوّن الإضافي Cesium for Unreal مع 3D Tiles API، يُرجى اتّباع الخطوات أدناه.

  1. تثبيت المكوّن الإضافي Cesium for Unreal

  2. أنشئ مشروع Unreal جديدًا.

  3. الربط بواجهة برمجة تطبيقات الصور الواقعية ثلاثية الأبعاد من Google

    1. افتح نافذة الخلية عن طريق اختيار Cesium > Cesium من القائمة.

    2. اختَر مجموعة مربّعات فارغة ثلاثية الأبعاد.

    3. في World Outliner "عالم Outline"، افتح لوحة التفاصيل من خلال اختيار Cesium3DTileset هذا.

    4. غيِّر المصدر من From Cesium Ion إلى من عنوان URL.

    5. اضبط عنوان URL ليكون عنوان URL لمربّعات Google الثلاثية الأبعاد.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. فعِّل عرض المساهمين على الشاشة لعرض الإحالات بشكلٍ صحيح.
  4. هذا يحمّل العالم. للانتقال إلى خط الطول والعرض، حدد عنصر CesiumGeoreference في لوحة Outliner، ثم عدّل خط العرض/خط الطول/الارتفاع لنقطة الشحن في لوحة التفاصيل.

التعاون مع Cesium لدعم الانسجام

لاستخدام مربّعات صور واقعية مع Cesium for Unity، اتّبِع الخطوات أدناه.

  1. أنشِئ مشروعًا جديدًا في Unity.

  2. أضِف سجلّاً مُفصَّلًا جديدًا في قسم "مدير الحِزم" (من خلال المحرِّر > إعدادات المشروع).

    • الاسم: سيزيوم

    • عنوان URL: https://unity.pkg.cesium.com

    • النطاقات: com.cesium.unity

  3. ثبِّت حزمة Cesium for Unity.

  4. الربط بواجهة برمجة تطبيقات الصور الواقعية ثلاثية الأبعاد من Google

    1. افتح نافذة الخلية عن طريق اختيار Cesium > Cesium من القائمة.

    2. انقر على مجموعة مربّعات فارغة ثلاثية الأبعاد.

    3. في اللوحة الجانبية اليمنى، في خيار مصدر مجموعة التجانب ضمن المصدر، اختَر من عنوان URL (بدلاً من "من سيزيوم أيون").

    4. اضبط عنوان URL على عنوان URL لمربّعات Google الثلاثية الأبعاد.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. فعِّل عرض المساهمين على الشاشة لعرض الإحالات بشكلٍ صحيح.
  5. هذا يحمّل العالم. للانتقال إلى خط الطول والعرض، حدد العنصر CesiumGeoreference في تسلسل هرمي للمشهد، ثم عدِّل خط العرض/خط الطول/الارتفاع لنقطة العرض الأصلية في أداة الفحص.

العمل باستخدام Dec.gl

deck.gl هو إطار عمل JavaScript مفتوح المصدر لتقديم عروض مرئية عالية الأداء للبيانات على نطاق واسع.

تحديد المصدر

تأكّد من عرض إحالات البيانات بشكل صحيح من خلال استخراج الحقل copyright من مربّعات gltf asset، ثم عرضه في العرض المعروض. لمزيد من المعلومات، اطّلِع على معلومات تحديد المصدر على الشبكة الإعلانية.

أمثلة على عارض Dec.gl

مثال بسيط

يؤدي المثال التالي إلى تهيئة عارض Dec.gl، ثم تحميل مكان بتنسيق ثلاثي الأبعاد. في الرمز الخاص بك، تأكد من استبدال YOUR_API_KEY بمفتاح واجهة برمجة التطبيقات الفعلي.

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

يعرض ملف Dec.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>