Trabalhar com um renderizador de blocos 3D

Desenvolvedores do Espaço Econômico Europeu (EEE)

Os blocos fotorrealistas em 3D estão no formato glTF padrão da OGC, o que significa que você pode usar qualquer renderizador compatível com a especificação de blocos 3D da OGC para criar visualizações em 3D. Por exemplo, o Cesium é uma biblioteca de código aberto fundamental para renderizar visualizações em 3D.

Trabalhar com o CesiumJS

O CesiumJS é uma biblioteca de código aberto em JavaScript para visualização em 3D na Web. Para mais informações sobre como usar o CesiumJS, consulte Aprenda a usar o CesiumJS.

Controles de usuário

O renderizador de blocos do CesiumJS tem um conjunto padrão de controles de usuário.

Ação Descrição
Visualização panorâmica Clique com o botão esquerdo e arraste
Visualização de zoom Clique com o botão direito e arraste ou role a roda do mouse
Visualização de rotação Ctrl + clique com o botão esquerdo/direito e arraste ou clique com o botão do meio e arraste

Práticas recomendadas

Há várias abordagens que podem ser adotadas para diminuir os tempos de carregamento do CesiumJS 3D. Exemplo:

  • Ative solicitações simultâneas adicionando a instrução a seguir ao HTML de renderização:

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

    Quanto maior o REQUEST_COUNT, mais rápido os blocos serão carregados. No entanto, ao carregar em um navegador Chrome com REQUEST_COUNT maior que 10 e o cache desativado, você poderá encontrar um problema conhecido do Chrome. Para a maioria dos casos de uso, recomendamos um REQUEST_COUNT de 18 para um desempenho ideal.

  • Ative a opção de pular níveis de detalhes. Para mais informações, consulte este problema do Cesium.

Verifique se você está mostrando as atribuições de dados corretamente ativando showCreditsOnScreen: true. Para mais informações, consulte Políticas.

Renderização de métricas

Para encontrar a taxa de frames, confira quantas vezes por segundo o requestAnimationFrame método é chamado.

Para saber como a latência do frame é calculada, consulte a PerformanceDisplay.

Exemplos de renderizador do CesiumJS

Você pode usar o renderizador do CesiumJS com os blocos 3D da API Map Tiles fornecendo o URL do conjunto de blocos raiz.

exemplo simples

O exemplo a seguir inicializa o renderizador do CesiumJS e, em seguida, carrega o conjunto de blocos raiz.

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

Para informações sobre requestRenderMode, consulte Como ativar o modo de renderização de solicitação.

A página HTML é renderizada conforme mostrado aqui.

Integração da API Places

Você pode usar o CesiumJS com a API Places para recuperar mais informações. É possível usar o widget de preenchimento automático para acessar a janela de visualização de lugares. Este exemplo usa a API Places Autocomplete, que é ativada seguindo estas instruções, e a API Maps JavaScript, que é ativada seguindo estas instruções.

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

Visualização de drone rotativa

Você pode controlar a câmera para animar o conjunto de blocos. Quando combinada com a API Places e a API Elevation, essa animação simula um sobrevoo interativo de drone de qualquer ponto de interesse.

Este exemplo de código mostra o lugar selecionado no widget de preenchimento automático.

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

Desenhar polilinhas e rótulos

Este exemplo de código demonstra como adicionar polilinhas e rótulos a um mapa. Você pode adicionar polilinhas a um mapa para mostrar trajetos de carro e rotas a pé, limites de propriedades ou para calcular durações de carro e a pé. Também é possível receber atributos sem renderizar a cena.

Você pode levar os usuários a um tour guiado por um bairro ou mostrar propriedades vizinhas que estão à venda e, em seguida, adicionar objetos 3D, como outdoors, à cena.

Você pode resumir uma viagem, listando as propriedades que visualizou e mostrando esses detalhes em objetos virtuais.

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

Órbita da câmera

No Cesium, é possível orbitar a câmera em torno de um ponto de interesse, evitando colisões com edifícios. Como alternativa, você pode tornar os edifícios transparentes quando a câmera se move por eles.

Primeiro, bloqueie a câmera em um ponto e, em seguida, crie uma órbita da câmera para mostrar seu recurso. Você pode fazer isso usando a função da câmera lookAtTransform com um listener de eventos, conforme demonstrado neste exemplo de código.

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

Para mais informações sobre como controlar a câmera, consulte Controlar a câmera

Trabalhar com o Cesium for Unreal

Para usar o plug-in Cesium for Unreal com a API 3D Tiles, siga as etapas abaixo.

  1. Instale o plug-in Cesium for Unreal.

  2. Crie um novo projeto do Unreal.

  3. Conecte-se à API Google Photorealistic 3D Tiles.

    1. Abra a janela do Cesium selecionando Cesium > Cesium no menu.

    2. Selecione Blank 3D Tiles Tileset.

    3. No World Outliner, abra o pain0}Details selecionando este Cesium3DTileset.

    4. Mude a Source de From Cesium Ion para From URL.

    5. Defina o URL como o URL de blocos 3D do Google.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Ative a opção Show Credits On Screen para mostrar as atribuições corretamente.
  4. Isso carrega o mundo. Para acessar qualquer LatLng, selecione o item CesiumGeoreference no painel Outliner e edite a Origin Latitude/Longitude/Height no painel Details.

Trabalhar com o Cesium for Unity

Para usar blocos fotorrealistas com o Cesium for Unity, siga as etapas abaixo.

  1. Crie um novo projeto do Unity.

  2. Adicione um novo registro com escopo na seção Package Manager (em Editor > Project Settings).

    • Nome: Cesium

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

    • Escopos: com.cesium.unity

  3. Instale o pacote Cesium for Unity.

  4. Conecte-se à API Google Photorealistic 3D Tiles.

    1. Abra a janela do Cesium selecionando Cesium > Cesium no menu.

    2. Clique em Blank 3D Tiles Tileset.

    3. No painel esquerdo, na opção Tileset Source em Source, selecione From URL (em vez de From Cesium Ion).

    4. Defina o URL como o URL de blocos 3D do Google.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Ative a opção Show Credits On Screen para mostrar as atribuições corretamente.
  5. Isso carrega o mundo. Para acessar qualquer LatLng, selecione o CesiumGeoreference item na Scene Hierarchy e edite a latitude/longitude/altura de origem no Inspector.

Trabalhar com o deck.gl

deck.gl, com tecnologia WebGL, é um framework de JavaScript de código aberto para visualizações de dados de alta performance, e em grande escala.

Atribuição

Verifique se você está mostrando as atribuições de dados corretamente extraindo o campo copyright do asset gltf de blocos e mostrando-o na visualização renderizada. Para mais informações, consulte Mostrar atribuições de dados.

Exemplos de renderizador do deck.gl

exemplo simples

O exemplo a seguir inicializa o renderizador do deck.gl e, em seguida, carrega um lugar em 3D. No código, substitua YOUR_API_KEY pela sua chave de 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>

Visualizar camadas 2D sobre blocos fotorrealistas em 3D do Google

A TerrainExtension do deck.gl TerrainExtension renderiza dados 2D em uma superfície 3D. Por exemplo, é possível sobrepor o GeoJSON de uma planta de edifício à geometria de blocos fotorrealistas em 3D.

No exemplo a seguir, uma camada de edifícios é visualizada com os polígonos adaptados à superfície de blocos fotorrealistas em 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>