Работа с визуализатором 3D-плиток

Фотореалистичные 3D-плитки имеют стандартный формат glTF OGC . Это означает, что вы можете использовать любой модуль рендеринга, поддерживающий спецификацию OGC 3D Tiles, для создания 3D-визуализации. Например, Cesium — это базовая библиотека с открытым исходным кодом для рендеринга 3D-визуализации.

Работа с CesiumJS

CesiumJS — это библиотека Javascript с открытым исходным кодом для 3D-визуализации в Интернете. Дополнительные сведения об использовании CesiumJS см. в разделе Изучение 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 с 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-страница отображается, как показано здесь.

Интеграция API мест

Вы можете использовать CesiumJS с API-интерфейсом Places для получения дополнительной информации. Вы можете использовать виджет «Автозаполнение», чтобы перейти к области просмотра «Места». В этом примере используется API автозаполнения мест, который можно включить, следуя этим инструкциям , и API 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>

Вращающийся обзор дрона

Вы можете управлять камерой для анимации через набор плиток. В сочетании с API-интерфейсами Places и API-интерфейсом Elevation эта анимация имитирует интерактивный пролет дрона над любой достопримечательностью.

Этот пример кода проведет вас по месту, которое вы выбрали в виджете автозаполнения.

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

Рисование полилиний и меток

В этом примере кода показано, как добавлять на карту полилинии и метки. Вы можете добавить на карту полилинии, чтобы показать направления движения и ходьбы, или показать границы собственности, или рассчитать продолжительность поездки и ходьбы. Вы также можете получить атрибуты без фактического рендеринга сцены.

Вы можете провести для пользователей тщательно подобранную экскурсию по району или показать соседние объекты недвижимости, которые в настоящее время продаются, а затем добавить на сцену 3D-объекты, такие как рекламные щиты.

Вы можете подвести итоги поездки, перечислив просмотренные вами объекты недвижимости, отобразив эти детали в виртуальных объектах.

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

Дополнительную информацию об управлении камерой см. в разделе Управление камерой.

Работа с Цезием для Unreal

Чтобы использовать плагин Cesium for Unreal с API 3D Tiles, выполните следующие действия.

  1. Установите плагин Cesium for Unreal.

  2. Создайте новый проект Unreal.

  3. Подключитесь к API Google Photorealistic 3D Tiles.

    1. Откройте окно «Цезий», выбрав в меню «Цезий» > «Цезий» .

    2. Выберите набор плиток «Пустые 3D-плитки» .

    3. В World Outliner откройте панель Details , выбрав этот Cesium3DTileset .

    4. Измените источник с «Ион цезия» на «От URL» .

    5. Установите URL-адрес в качестве URL-адреса Google 3D Tiles.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Включите «Показывать авторство на экране», чтобы правильно отображать авторство.
  4. Это загружает мир. Чтобы перейти к любой широте и длине, выберите элемент CesiumGeoreference на панели Outliner , а затем отредактируйте исходную широту/долготу/высоту на панели Details .

Работайте с цезием ради Unity

Чтобы использовать фотореалистичные плитки с цезием для Unity, выполните следующие действия.

  1. Создайте новый проект Unity.

  2. Добавьте новый реестр с ограниченной областью действия в разделе «Диспетчер пакетов» (через «Редактор» > «Настройки проекта» ).

    • Название: Цезий

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

    • Область применения: com.cesium.unity

  3. Установите пакет Cesium для Unity.

  4. Подключитесь к API Google Photorealistic 3D Tiles.

    1. Откройте окно «Цезий», выбрав в меню «Цезий» > «Цезий» .

    2. Нажмите «Набор пустых 3D-плиток» .

    3. На левой панели в разделе «Источник набора плиток» в разделе «Источник» выберите «От URL-адреса» (вместо «От иона цезия»).

    4. Установите URL-адрес URL-адреса Google 3D Tiles.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Включите «Показывать авторство на экране», чтобы правильно отображать авторство.
  5. Это загружает мир. Чтобы перейти к любой широте, выберите элемент CesiumGeoreference в иерархии сцен , а затем отредактируйте исходную широту/долготу/высоту в инспекторе .

Работа с палубой.gl

eck.gl , основанный на WebGL, представляет собой JavaScript-фреймворк с открытым исходным кодом для высокопроизводительной крупномасштабной визуализации данных.

Атрибуция

Убедитесь, что вы правильно отображаете атрибуцию данных, извлекая поле copyright из плитки gltf asset и затем отображая его в визуализированном представлении. Дополнительную информацию см. в разделе Атрибуция отображаемых данных .

Примеры рендеринга Deck.gl

Простой пример

В следующем примере инициализируется средство визуализации 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>

Визуализируйте 2D-слои поверх фотореалистичных 3D-плиток Google.

Deck.gl TerrainExtension отображает 2D-данные на 3D-поверхности. Например, вы можете поместить GeoJSON контура здания поверх фотореалистичной геометрии 3D-плиток.

В следующем примере слой зданий визуализируется с помощью полигонов, адаптированных к поверхности Photorealistic 3D Tiles.

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