Praca z renderowaniem płytek 3D

Deweloperzy z Europejskiego Obszaru Gospodarczego (EOG)

Fotorealistyczne kafelki 3D są w standardowym formacie OGC glTF, co oznacza, że do tworzenia wizualizacji 3D możesz używać dowolnego renderera obsługującego specyfikację OGC 3D Tiles. Na przykład, Cesium to podstawowa biblioteka open source do renderowania wizualizacji 3D.

Praca z CesiumJS

CesiumJS to biblioteka open source w języku JavaScript do wizualizacji 3D w internecie. Więcej informacji o korzystaniu z CesiumJS znajdziesz w artykule Learn CesiumJS.

Kontrola użytkowników

Renderer kafelków CesiumJS ma standardowy zestaw elementów sterujących.

Działanie Opis
Przesuń widok Kliknij i przeciągnij lewym przyciskiem myszy
Powiększ Kliknij i przeciągnij prawym przyciskiem myszy lub przewiń kółko myszy
Obracanie widoku Przytrzymaj Ctrl i kliknij i przeciągnij lewym lub prawym przyciskiem myszy albo kliknij i przeciągnij środkowym przyciskiem myszy

Sprawdzone metody

Istnieje kilka sposobów na skrócenie czasu wczytywania 3D w CesiumJS. Na przykład:

  • Aby włączyć jednoczesne żądania, dodaj do kodu HTML renderowania ten fragment:

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

    Im większa wartość REQUEST_COUNT, tym szybciej wczytują się kafelki. Jeśli jednak wczytujesz dane w przeglądarce Chrome z wartością REQUEST_COUNT większą niż 10 i wyłączoną pamięcią podręczną, możesz napotkać znany problem z Chrome. W większości przypadków zalecamy ustawienie wartości REQUEST_COUNT na 18, aby uzyskać optymalną wydajność.

  • Włącz pomijanie poziomów szczegółowości. Więcej informacji znajdziesz w tym artykule o problemie z Cesium.

Aby prawidłowo wyświetlać atrybucje danych, włącz opcję showCreditsOnScreen: true. Więcej informacji znajdziesz w zasadach.

Wskaźniki renderowania

Aby sprawdzić liczbę klatek na sekundę, zobacz, ile razy na sekundę wywoływana jest metoda requestAnimationFrame.

Aby zobaczyć, jak obliczana jest latencja klatek, zapoznaj się z klasą PerformanceDisplay.

Przykłady renderera CesiumJS

Możesz używać renderera CesiumJS z kafelkami 3D interfejsu Map Tiles API, podając tylko adres URL głównego zbioru kafelków.

Prosty przykład

Ten przykład inicjuje renderer CesiumJS, a następnie wczytuje główny zbiór kafelków.

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

Informacje o requestRenderMode znajdziesz w artykule Włączanie trybu renderowania żądań.

Strona HTML jest renderowana w sposób pokazany tutaj.

Integracja z Places API (nowość)

Aby pobrać więcej informacji, możesz używać CesiumJS z Places API (nowość). Za pomocą widżetu autouzupełniania możesz przejść do widocznego obszaru miejsc. Ten przykład korzysta z interfejsu Places Autocomplete API, który jest włączany zgodnie z tymi instrukcjami, oraz z Maps JavaScript API, który jest włączany zgodnie z tymi instrukcjami.

<!DOCTYPE html>
<head>
 <meta charset="utf-8" />
 <title>CesiumJS 3D Tiles Places API (New) 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>

Obracanie widoku z drona

Możesz sterować kamerą, aby animować zbiór kafelków. W połączeniu z Places API (nowość) i Elevation API ta animacja symuluje interaktywny przelot drona nad dowolnym interesującym miejscem.

Ten przykładowy kod pozwala przelecieć nad miejscem wybranym w widżecie autouzupełniania.

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

Rysowanie polilinii i etykiet

Ten przykładowy kod pokazuje, jak dodać polilinie i etykiety do mapy. Możesz dodawać polilinie do mapy, aby pokazywać wskazówki dojazdu i dojścia, granice nieruchomości lub obliczać czas jazdy i przejścia. Możesz też pobierać atrybuty bez renderowania sceny.

Możesz oprowadzać użytkowników po okolicy lub pokazywać sąsiednie nieruchomości, które są obecnie na sprzedaż, a następnie dodawać do sceny obiekty 3D, takie jak billboardy.

Możesz podsumować podróż, wyświetlając nieruchomości, które zostały przez Ciebie obejrzane, i wyświetlając te szczegóły w obiektach wirtualnych.

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

Orbita kamery

W Cesium możesz obracać kamerę wokół interesującego miejsca, unikając kolizji z budynkami. Możesz też sprawić, że budynki będą przezroczyste, gdy kamera będzie się przez nie poruszać.

Najpierw zablokuj kamerę na punkcie, a potem utwórz orbitę kamery, aby zaprezentować zasób. Możesz to zrobić, używając funkcji aparatu lookAtTransform z detektorem zdarzeń, jak pokazano w tym przykładowym kodzie.

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

Więcej informacji o sterowaniu kamerą znajdziesz w artykule Sterowanie kamerą.

Praca z Cesium for Unreal

Aby używać wtyczki Cesium for Unreal z interfejsem 3D Tiles API, wykonaj te czynności.

  1. Zainstaluj wtyczkę Cesium for Unreal.

  2. Utwórz nowy projekt Unreal.

  3. Połącz się z Google Photorealistic 3D Tiles API.

    1. Otwórz okno Cesium, wybierając w menu Cesium > Cesium.

    2. Kliknij Blank 3D Tiles Tileset.

    3. W World Outliner otwórz panel Details, klikając ten Cesium3DTileset.

    4. Zmień Source z From Cesium Ion na From URL.

    5. Ustaw adres URL na adres URL kafelków 3D Google.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Aby prawidłowo wyświetlać atrybucje, włącz opcję Show Credits On Screen.
  4. Spowoduje to wczytanie świata. Aby przejść do dowolnego LatLng, w panelu Outliner kliknij CesiumGeoreference, a następnie w panelu Details zmień Origin Latitude/Longitude/Height.

Praca z Cesium for Unity

Aby używać fotorealistycznych kafelków z Cesium for Unity, wykonaj te czynności.

  1. Utwórz nowy projekt Unity.

  2. Dodaj nowy Scoped Registry w sekcji Package Manager (w Editor > ustawienia projektu).

    • Nazwa: Cesium

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

    • Zakresy: com.cesium.unity

  3. Zainstaluj pakiet Cesium for Unity.

  4. Połącz się z Google Photorealistic 3D Tiles API.

    1. Otwórz okno Cesium, wybierając w menu Cesium > Cesium.

    2. Kliknij Blank 3D Tiles Tileset.

    3. W panelu po lewej stronie w opcji Tileset Source w sekcji Source, kliknij From URL (zamiast From Cesium Ion).

    4. Ustaw adres URL na adres URL kafelków 3D Google.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Aby prawidłowo wyświetlać atrybucje, włącz opcję Show Credits On Screen.
  5. Spowoduje to wczytanie świata. Aby przejść do dowolnego LatLng, w Scene Hierarchy kliknij CesiumGeoreference, a następnie w Inspector zmień Origin Latitude/Longitude/Height.

Praca z deck.gl

deck.gl to platforma JavaScript typu open source oparta na WebGL, która umożliwia wizualizację danych na dużą skalę.

Atrybucja

Aby prawidłowo wyświetlać atrybucje danych, wyodrębnij pole copyright z elementu asset kafelków gltf, a następnie wyświetl je w renderowanym widoku. Więcej informacji znajdziesz w artykule Wyświetlanie atrybucji danych.

Przykłady renderera deck.gl

Prosty przykład

Ten przykład inicjuje renderer deck.gl, a następnie wczytuje miejsce w 3D. W kodzie zastąp YOUR_API_KEY swoim prawdziwym kluczem interfejsu 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>

Wizualizacja warstw 2D na fotorealistycznych kafelkach 3D Google

Rozszerzenie deck.gl TerrainExtension renderuje dane 2D na powierzchni 3D. Możesz na przykład nałożyć GeoJSON konturu budynku na geometrię fotorealistycznych kafelków 3D.

W tym przykładzie warstwa budynków jest wizualizowana za pomocą wielokątów dostosowanych do powierzchni fotorealistycznych kafelków 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>