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

Разработчики Европейской экономической зоны (ЕЭЗ)

Фотореалистичные 3D-плитки представлены в стандартном формате OGC glTF , что означает, что вы можете использовать любой рендерер, поддерживающий спецификацию 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.

  • Включить пропуск уровней детализации. Подробнее см. в этой статье о Cesium .

Обеспечьте корректное отображение данных об авторстве, включив параметр showCreditsOnScreen: true . Подробнее см. в разделе «Политики» .

Метрики рендеринга

Чтобы узнать частоту кадров, посмотрите, сколько раз в секунду вызывается метод requestAnimationFrame .

Чтобы увидеть, как рассчитывается задержка кадра, взгляните на класс PerformanceDisplay .

Примеры рендерера CesiumJS

Вы можете использовать рендерер CesiumJS с 3D-плитками API Map Tiles, просто указав 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 для получения дополнительной информации. Вы можете использовать виджет автозаполнения для перехода к области просмотра Places. В этом примере используются API автозаполнения Places, который включается следующим образом , и API JavaScript Maps, который включается следующим образом .

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

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

Работа с Cesium для Unreal

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

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

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

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

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

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

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

    4. Измените Источник с Из Cesium Ion на Из URL .

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

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

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

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

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

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

    • Имя: Цезий

    • 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. Это загрузит мир. Чтобы перейти к любой точке LatLng, выберите элемент CesiumGeoreference в иерархии сцены , а затем отредактируйте широту/долготу/высоту начала координат в инспекторе .

Работа с deck.gl

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

Атрибуция

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

примеры рендеринга 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-слои поверх Google Photorealistic 3D Tiles

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

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

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