3D 타일 렌더기 사용

포토리얼리스틱 3D 카드는 OGC 표준 glTF 형식입니다. 즉, OGC 3D 타일 사양을 지원하는 모든 렌더기를 사용하여 3D 시각화를 빌드할 수 있습니다. 예를 들어 Cesium은 3D 시각화 렌더링을 위한 기본적인 오픈소스 라이브러리입니다.

CesiumJS 사용

CesiumJS는 웹에서 3D 시각화를 위한 오픈소스 JavaScript 라이브러리입니다. CesiumJS 사용에 대한 자세한 내용은 CesiumJS 알아보기를 참조하세요.

사용자 제어

CesiumJS 타일 렌더기에는 표준 사용자 컨트롤 세트가 있습니다.

작업 설명
화면 이동 왼쪽 클릭 및 드래그
확대/축소 뷰 마우스 오른쪽 버튼으로 클릭하고 드래그하거나 마우스 휠을 스크롤
보기 회전 Ctrl + 왼쪽/오른쪽 클릭 및 드래그 또는 가운데 클릭 및 드래그

권장사항

CesiumJS 3D 로드 시간을 줄이기 위해 취할 수 있는 몇 가지 방법이 있습니다. 예를 들면 다음과 같습니다.

  • 렌더링 HTML에 다음 문을 추가하여 동시 요청을 사용 설정합니다.

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

    REQUEST_COUNT가 높을수록 카드가 더 빠르게 로드됩니다. 그러나 REQUEST_COUNT이 10보다 크고 캐시가 사용 중지된 상태에서 Chrome 브라우저를 로드하면 알려진 Chrome 문제가 발생할 수 있습니다. 대부분의 사용 사례에서 최적의 성능을 위해 REQUEST_COUNT를 18로 설정하는 것이 좋습니다.

  • 세부정보 수준 건너뛰기를 사용 설정합니다. 자세한 내용은 이 Cesium 문제를 참고하세요.

showCreditsOnScreen: true를 사용 설정하여 데이터 저작자 표시가 올바르게 표시되는지 확인합니다. 자세한 내용은 정책을 참고하세요.

렌더링 측정항목

프레임 속도를 확인하려면 requestAnimationFrame 메서드가 호출되는 초당 횟수를 확인하세요.

프레임 지연 시간이 계산되는 방식을 보려면 PerformanceDisplay 클래스를 살펴보세요.

CesiumJS 렌더기 예시

루트 타일 집합 URL을 제공하기만 하면 Map Tiles API의 3D 타일에 CesiumJS 렌더기를 사용할 수 있습니다.

간단한 예시

다음 예에서는 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 페이지는 다음과 같이 렌더링됩니다.

Places API 통합

CesiumJS를 Places API와 함께 사용하여 추가 정보를 검색할 수 있습니다. 자동 완성 위젯을 사용하여 장소의 표시 영역으로 빠르게 이동할 수 있습니다. 이 예에서는 이 안내에 따라 사용 설정되는 Places Autocomplete API와 이 안내에 따라 사용 설정된 Maps JavaScript API를 사용합니다.

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

회전하는 드론 뷰

타일 집합을 통해 애니메이션되도록 카메라를 제어할 수 있습니다. 이 애니메이션은 Places API 및 Elevation API를 함께 사용하여 관심 장소의 대화형 드론 고가도로를 시뮬레이션합니다.

이 코드 샘플은 자동 완성 위젯에서 선택한 장소 주변으로 이동합니다.

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <title>CesiumJS 3D Tiles Rotating Drone View Demo</title>
  <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
  <link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
  <label for="pacViewPlace">Go to a place: </label>
  <input type="text" id="pacViewPlace" name="pacViewPlace" placeholder="Enter a location..." style="width: 300px" />
  <div id="cesiumContainer"></div>
  <script>
    // Enable simultaneous requests.
    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

    // Create the viewer and remove unneeded options.
    const viewer = new Cesium.Viewer("cesiumContainer", {
      imageryProvider: false,
      baseLayerPicker: false,
      homeButton: false,
      fullscreenButton: false,
      navigationHelpButton: false,
      vrButton: false,
      sceneModePicker: false,
      geocoder: false,
      globe: false,
      infobox: false,
      selectionIndicator: false,
      timeline: false,
      projectionPicker: false,
      clockViewModel: null,
      animation: false,
      requestRenderMode: true,
    });

    // Add 3D Tile set.
    const tileset = viewer.scene.primitives.add(
      new Cesium.Cesium3DTileset({
        url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
        // This property is required to display attributions.
        showCreditsOnScreen: true,
      })
    );

    // Point the camera at a location and elevation, at a viewport-appropriate distance.
    function pointCameraAt(location, viewport, elevation) {
      const distance = Cesium.Cartesian3.distance(
        Cesium.Cartesian3.fromDegrees(
          viewport.getSouthWest().lng(), viewport.getSouthWest().lat(), elevation),
        Cesium.Cartesian3.fromDegrees(
          viewport.getNorthEast().lng(), viewport.getNorthEast().lat(), elevation)
      ) / 2;
      const target = new Cesium.Cartesian3.fromDegrees(location.lng(), location.lat(), elevation);
      const pitch = -Math.PI / 4;
      const heading = 0;
      viewer.camera.lookAt(target, new Cesium.HeadingPitchRange(heading, pitch, distance));
    }

    // Rotate the camera around a location and elevation, at a viewport-appropriate distance.
    let unsubscribe = null;
    function rotateCameraAround(location, viewport, elevation) {
      if(unsubscribe) unsubscribe();
      pointCameraAt(location, viewport, elevation);
      unsubscribe = viewer.clock.onTick.addEventListener(() => {
        viewer.camera.rotate(Cesium.Cartesian3.UNIT_Z);
      });
    }

    function initAutocomplete() {
      const autocomplete = new google.maps.places.Autocomplete(
        document.getElementById("pacViewPlace"), {
          fields: [
            "geometry",
            "name",
          ],
        }
      );
      
      autocomplete.addListener("place_changed", async () => {
        const place = autocomplete.getPlace();
        
        if (!(place.geometry && place.geometry.viewport && place.geometry.location)) {
          window.alert(`Insufficient geometry data for place: ${place.name}`);
          return;
        }
        // Get place elevation using the ElevationService.
        const elevatorService = new google.maps.ElevationService();
        const elevationResponse =  await elevatorService.getElevationForLocations({
          locations: [place.geometry.location],
        });

        if(!(elevationResponse.results && elevationResponse.results.length)){
          window.alert(`Insufficient elevation data for place: ${place.name}`);
          return;
        }
        const elevation = elevationResponse.results[0].elevation || 10;

        rotateCameraAround(
          place.geometry.location,
          place.geometry.viewport,
          elevation
        );
      });
    }
  </script>
  <script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initAutocomplete"></script>
</body>

다중선 및 라벨 그리기

이 코드 샘플은 지도에 다중선과 라벨을 추가하는 방법을 보여줍니다. 지도에 다중선을 추가하여 운전 및 도보 경로를 표시하거나, 부동산 경계를 표시하거나, 운전 및 도보 시간을 계산할 수 있습니다. 실제로 장면을 렌더링하지 않고도 속성을 가져올 수도 있습니다.

사용자를 주변 지역을 선별하여 둘러보거나 현재 매매 중인 주변 부동산을 표시한 다음 빌보드와 같은 3D 객체를 장면에 추가할 수 있습니다.

이동을 요약하여 조회한 숙박 시설을 나열하고 세부정보를 가상 객체로 표시할 수 있습니다.

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <title>CesiumJS 3D Tiles Polyline and Label Demo</title>
  <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
  <link 
    href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css"
    rel="stylesheet"
  />
</head>
<body>
  <div id="cesiumContainer"></div>
  <script>
    // Enable simultaneous requests.
    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

    // Create the viewer.
    const viewer = new Cesium.Viewer("cesiumContainer", {
      imageryProvider: false,
      baseLayerPicker: false,
      requestRenderMode: true,
      geocoder: false,
      globe: false,
    });

    // Add 3D Tiles tileset.
    const tileset = viewer.scene.primitives.add(
      new Cesium.Cesium3DTileset({
        url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",

        // This property is required to display attributions as required.
        showCreditsOnScreen: true,
      })
    );

    // Draws a circle at the position, and a line from the previous position.
    const drawPointAndLine = (position, prevPosition) => {
      viewer.entities.removeAll();
      if (prevPosition) {
        viewer.entities.add({
          polyline: {
            positions: [prevPosition, position],
            width: 3,
            material: Cesium.Color.WHITE,
            clampToGround: true,
            classificationType: Cesium.ClassificationType.CESIUM_3D_TILE,
          },
        });
      }
      viewer.entities.add({
        position: position,
        ellipsoid: {
          radii: new Cesium.Cartesian3(1, 1, 1),
          material: Cesium.Color.RED,
        },
      });
    };

    // Compute, draw, and display the position's height relative to the previous position.
    var prevPosition;
    const processHeights = (newPosition) => {
      drawPointAndLine(newPosition, prevPosition);

      const newHeight = Cesium.Cartographic.fromCartesian(newPosition).height;
      let labelText = "Current altitude (meters above sea level):\n\t" + newHeight;
      if (prevPosition) {
        const prevHeight =
          Cesium.Cartographic.fromCartesian(prevPosition).height;
        labelText += "\nHeight from previous point (meters):\n\t" + Math.abs(newHeight - prevHeight);
      }
      viewer.entities.add({
        position: newPosition,
        label: {
          text: labelText,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
          pixelOffset: new Cesium.Cartesian2(0, -10),
          showBackground: true,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        }
      });

      prevPosition = newPosition;
    };

    const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
    handler.setInputAction(function (event) {
      const earthPosition = viewer.scene.pickPosition(event.position);
      if (Cesium.defined(earthPosition)) {
        processHeights(earthPosition);
      }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  </script>
</body>

카메라 궤도

Cesium에서는 건물과 충돌하지 않도록 관심 장소를 중심으로 카메라를 회전시킬 수 있습니다. 카메라가 건물을 지날 때 건물을 투명하게 만들 수도 있습니다

먼저 카메라를 특정 지점에 고정한 다음 카메라 궤도를 만들어 애셋을 표시할 수 있습니다. 이 코드 샘플에서 볼 수 있듯이 카메라의 lookAtTransform 함수를 이벤트 리스너와 함께 사용하면 됩니다.

// Lock the camera onto a point.
const center = Cesium.Cartesian3.fromRadians(
  2.4213211833389243,
  0.6171926869414084,
  3626.0426275055174
);

const transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);

viewer.scene.camera.lookAtTransform(
  transform,
  new Cesium.HeadingPitchRange(0, -Math.PI / 8, 2900)
);

// Orbit around this point.
viewer.clock.onTick.addEventListener(function (clock) {
  viewer.scene.camera.rotateRight(0.005);
});

카메라 제어에 관한 자세한 내용은 카메라 제어를 참고하세요.

Unreal용 Cesium 사용

3D Tiles API에서 Cesium for Unreal 플러그인을 사용하려면 아래 단계를 따르세요.

  1. Unreal용 Cesium 플러그인을 설치합니다.

  2. 새 Unreal 프로젝트를 만듭니다.

  3. Google 포토리얼리스틱 3D 타일 API에 연결합니다.

    1. 메뉴에서 Cesium > Cesium을 선택하여 Cesium 창을 엽니다.

    2. 빈 3D 타일 타일 세트를 선택합니다.

    3. World Outliner에서 Cesium3DTileset를 선택하여 Details 패널을 엽니다.

    4. 소스세슘 이온에서에서 URL로로 변경합니다.

    5. URL을 Google 3D Tiles URL로 설정합니다.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. 저작자를 올바르게 표시하려면 화면에 크레딧 표시를 사용 설정합니다.
  4. 이는 세계를 로드해 나갑니다. LatLng로 이동하려면 아웃라이너 패널에서 CesiumGeoreference 항목을 선택한 후 Details 패널에서 출발지 위도/경도/높이를 편집합니다.

Unity용 Cesium 사용

Unity용 Cesium으로 실사 타일을 사용하려면 다음 단계를 따르세요.

  1. 새 Unity 프로젝트를 만듭니다.

  2. 패키지 관리자 섹션에 편집기 > 프로젝트 설정을 통해 새로운 범위 지정 레지스트리를 추가합니다.

    • 이름: Cesium

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

    • 범위: com.cesium.unity

  3. Unity용 Cesium 패키지를 설치합니다.

  4. Google Photorealistic 3D Tiles API에 연결합니다.

    1. 메뉴에서 Cesium > Cesium을 선택하여 Cesium 창을 엽니다.

    2. 빈 3D 타일 타일 세트를 클릭합니다.

    3. 왼쪽 패널의 Source 아래 Tileset Source 옵션에서 From URL (Cesium Ion 대신)을 선택합니다.

    4. URL을 Google 3D Tiles URL로 설정합니다.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. 저작자를 올바르게 표시하려면 화면에 크레딧 표시를 사용 설정합니다.
  5. 이는 세계를 로드해 나갑니다. LatLng로 이동하려면 Scene Hierarchy에서 CesiumGeoreference 항목을 선택한 다음 Inspector에서 출발지 위도/경도/높이를 수정합니다.

deck.gl 사용

WebGL 기반 deck.gl은 고성능의 대규모 데이터 시각화를 위한 오픈소스 JavaScript 프레임워크입니다.

기여 분석

카드 gltf asset에서 copyright 필드를 추출한 다음 렌더링된 뷰에 표시하여 데이터 저작자 표시를 제대로 표시해야 합니다. 자세한 내용은 데이터 저작자 표시 표시를 참고하세요.

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>

Google 포토리얼리스틱 3D 타일 위에 2D 레이어 시각화

deck.gl TerrainExtension은 2D 데이터를 3D 표면에 렌더링합니다. 예를 들어 실사 3D 타일 도형 위에 건물 접지면의 GeoJSON을 배치할 수 있습니다.

다음 예에서는 건물 레이어가 실사 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>