Praca z renderowaniem płytek 3D

Fotorealistyczne kafelki 3D są w standardowym formacie OGC, co oznacza, że do tworzenia wizualizacji 3D możesz użyć dowolnego mechanizmu renderowania, który obsługuje specyfikację kafelków 3D OGC. Na przykład Cesium to podstawowa biblioteka open source do renderowania wizualizacji 3D.

Praca z CesiumJS

CesiumJS to biblioteka open source JavaScript służąca do wizualizacji 3D w internecie. Więcej informacji o korzystaniu z CesiumJS znajdziesz na stronie o CesiumJS.

Kontrola użytkowników

Mechanizm renderowania kafelków CesiumJS ma standardowy zestaw elementów sterujących użytkownika.

Działanie Opis
Widok przesuwania Kliknij i przeciągnij lewym przyciskiem
Powiększenie Kliknij prawym przyciskiem myszy i przeciągnij lub obróć kółko myszy
Obróć widok Ctrl + lewy/prawy kliknij i przeciągnij lub kliknij środkowo i przeciągnij

sprawdzone metody

Czas wczytywania danych 3D w CesiumJS można skrócić na kilka sposobów. Na przykład:

  • Włącz jednoczesne żądania, dodając tę instrukcję do renderowanego kodu HTML:

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

    Im większa wartość REQUEST_COUNT, tym szybciej kafelki wczytują się. Jednak podczas wczytywania w przeglądarce Chrome z REQUEST_COUNT większą niż 10 i wyłączoną pamięcią podręczną może wystąpić znany problem z Chrome. W większości przypadków zalecamy REQUEST_COUNT w wysokości 18, aby uzyskać optymalną wydajność.

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

Zadbaj o prawidłowe wyświetlanie atrybucji danych, włączając funkcję showCreditsOnScreen: true. Więcej informacji znajdziesz w artykule Zasady.

Dane renderowania

Aby określić liczbę klatek, sprawdź, ile razy na sekundę jest wywoływana metoda requestAnimationFrame.

Aby zobaczyć, jak obliczane jest opóźnienie klatki, zapoznaj się z klasą PerformanceDisplay.

Przykłady mechanizmu renderowania CesiumJS

Możesz użyć mechanizmu renderowania CesiumJS z kafelkami 3D w interfejsie Map Tiles API, podając po prostu główny adres URL zbioru kafelków.

Prosty przykład

Poniższy przykład inicjuje mechanizm renderowania 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>

Więcej informacji na temat requestRenderMode znajdziesz w sekcji poświęconej włączaniu trybu renderowania żądań.

Strona HTML wyświetla się w sposób pokazany poniżej.

Integracja interfejsu Places API

Aby uzyskać więcej informacji, możesz użyć CesiumJS z Places API. Widżetu autouzupełniania pozwala przejść do widocznego obszaru Miejsc. W tym przykładzie korzystamy z interfejsu Places Autocomplete API, który można włączyć, wykonując te instrukcje, oraz Maps JavaScript API, który można włączyć, wykonując te instrukcje.

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

Widok z drona z obrotem

Możesz sterować kamerą za pomocą zestawu kafelków. W połączeniu z interfejsami Places API i Elevation API animacja ta symuluje interaktywne estakada drona w dowolnym miejscu.

Ten przykładowy kod umożliwia poruszanie się po miejscu 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>

Rysuj linie łamane i etykiety

Ten przykładowy kod pokazuje, jak dodawać do mapy linie łamane i etykiety. Możesz dodawać do mapy linie łamane, aby wyświetlać trasy samochodowe i piesze, pokazywać granice nieruchomości lub obliczać czas trwania jazdy samochodem i pieszo. Możesz też uzyskać atrybuty bez renderowania sceny.

Możesz zabrać ich na wycieczkę po okolicy albo pokazać sąsiednie nieruchomości, które aktualnie są na sprzedaż, a potem dodać do sceny obiekty 3D, takie jak billboardy.

Możesz podsumować podróż, wymieniając przeglądane miejsca zakwaterowania i prezentując je w formie wirtualnych obiektów.

<!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ół wybranego miejsca, co pozwala uniknąć kolizji z budynkami. Budynki mogą być też przezroczyste, gdy kamera się przez nie porusza.

Najpierw zablokuj kamerę w odpowiednim punkcie, a potem utwórz jej orbitę, aby zaprezentować zasób. W tym celu użyj funkcji lookAtTransform kamery z detektorem zdarzeń, jak pokazano w tym przykładzie kodu.

// 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 na temat sterowania kamerą znajdziesz w artykule Sterowanie kamerą.

Współpraca z Cesium for Unreal

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

  1. Zainstaluj wtyczkę Cesium for Unreal.

  2. Utwórz nowy projekt Unreal.

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

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

    2. Wybierz Pusty zestaw kafelków 3D.

    3. W Szkicowniku świata otwórz panel Szczegóły, wybierając Cesium3DTileset.

    4. Zmień ustawienie Źródło z Z Cesium Ion na Z adresu URL.

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

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Włącz Pokaż twórców na ekranie, aby prawidłowo wyświetlać atrybucje.
  4. Spowoduje to wczytanie świata. Aby przejść do dowolnej długości geograficznej, wybierz element CesiumGeoreference w panelu Szkicownik i edytuj w panelu Szczegóły szerokość/długość geograficzną/wysokość.

Praca z Cesium for Unity

Aby używać fotorealistycznych kafelków w Cesium dla Unity, wykonaj poniższe czynności.

  1. Utwórz nowy projekt w Unity.

  2. Dodaj nowy rejestr ograniczony w sekcji Menedżer pakietów (kliknij Edytor > Ustawienia projektu).

    • Nazwa: Cesium

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

    • Zakresy: com.cesium.unity

  3. Zainstaluj pakiet Cesium dla Unity.

  4. Połącz się z interfejsem API fotorealistycznych kafelków Google 3D.

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

    2. Kliknij Pusty zestaw kafelków 3D.

    3. W panelu bocznym po lewej stronie, w opcji Tileset Source, w sekcji Source (Źródło) wybierz From URL (Z adresu URL) (a nie z Cesium Ion).

    4. Podaj adres URL kafelków Google 3D.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Włącz Pokaż twórców na ekranie, aby prawidłowo wyświetlać atrybucje.
  5. Spowoduje to wczytanie świata. Aby przejść do dowolnej długości geograficznej, wybierz element CesiumGeoreference w Hierarchii scen i edytuj szerokość/długość geograficzną/wysokość punktu początkowego w inspektorze.

Praca z dekla.gl

deck.gl wykorzystująca WebGL to platforma JavaScript typu open source do tworzenia wysokiej jakości wizualizacji na dużą skalę.

Atrybucja

Zadbaj o prawidłowe wyświetlanie informacji o atrybucji, wyodrębniając pole copyright z kafelka gltf asset, a następnie wyświetlając je w renderowanym widoku. Więcej informacji znajdziesz w artykule o atrybucji danych displayowych.

przykłady mechanizmu renderowania screen.gl

Prosty przykład

W poniższym przykładzie inicjuje się mechanizm renderujący tab.gl, a następnie wczytuje miejsce w 3D. Pamiętaj, aby w kodzie zastąpić YOUR_API_KEY rzeczywistym 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>

Wizualizuj warstwy 2D na fotorealistycznych kafelkach 3D Google

Komponent TerrainExtension renderuje na powierzchni 3D dane 2D. Na przykład na element fotorealistyczny kafelków 3D można nakładać dane geoJSON podstawy budynku.

W poniższym przykładzie wizualizowana jest warstwa budynków z wielokątami dostosowanymi 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>