使用地理空間錨點在 Android SDK (Kotlin/Java) 上定位實際內容

地理空間錨點是一種錨點,可讓您將 3D 內容放置在現實世界中。

地理空間錨點類型

地理空間錨點分為三種類型,每種類型處理高度的方式各不相同:

  1. WGS84 錨點
    WGS84 錨點可讓您在任何指定的經緯度及海拔高度放置 3D 內容。

  2. 地形錨點
    地形錨點可讓您只使用緯度和經度來放置內容,並以該位置的地形高度為依據。海拔高度是相對於地面或樓層 (由 VPS) 決定。

  3. 屋頂錨點
    屋頂錨點可讓您只使用經緯度和相對於該位置建築物屋頂的高度來放置內容。海拔高度是相對於街景服務幾何圖形的建築物頂端。未放置在建築物上時,預設為地形高度。

WGS84 地形 屋頂
水平位置 緯度, 經度 緯度, 經度 緯度, 經度
垂直位置 相對於 WGS84 高度 相對於 Google 地圖所決定地形等級的比較 相對於 Google 地圖所判定的屋頂樓層
是否需要由伺服器解析?

必要條件

請務必先啟用 Geospatial API,再繼續操作。

放置地理空間錨點

每個錨點類型都有專屬的 API 可用來建立錨點,詳情請參閱「地理空間錨點類型」。

根據命中測試建立錨點

您也可以使用命中測試結果建立地理空間錨點。使用命中測試的 Pose,並將其轉換為 GeospatialPose。可用來放置上述任一錨點類型。

從 AR 姿勢取得地理空間姿勢

Earth.getGeospatialPose() 提供另一種方法,可透過將 AR 姿勢轉換為地理空間姿勢,判斷經緯度。

從地理空間姿勢取得 AR 姿勢

Earth.getPose() 會將地球指定的水平位置、高度和四元數旋轉 (相對於東-上-南座標架構) 轉換為 AR 姿勢 (相對於 GL 世界座標)。

選擇最適合用途的方法

每種建立錨點的方法都會帶來相關的取捨,請留意:

  • 使用 街景地形時,請使用命中測試將內容附加至建築物。
  • 請優先使用地形或屋頂錨點,而非 WGS84 錨點,因為前者會使用 Google 地圖判定的高度值。

判斷位置的經緯度

計算地點的經緯度有三種方法:

  • 使用地理空間創作者,即可透過 3D 內容查看及擴增世界,無須實際前往該地。這樣一來,您就能在 Unity 編輯器中使用 Google 地圖,以視覺化方式放置 3D 沈浸式內容。系統會自動計算內容的緯度、經度、旋轉角度和高度。
  • 使用 Google 地圖
  • 使用 Google 地球。請注意,使用 Google 地球取得這些座標 (而非 Google 地圖) 時,誤差範圍最多可達數公尺。
  • 前往實際地點

使用 Google 地圖

如要使用 Google 地圖取得某個地點的經緯度,請按照下列步驟操作:

  1. 在電腦上前往 Google 地圖

  2. 依序前往「圖層」 >「更多」

  3. 將「地圖類型」變更為「衛星」,然後取消勾選畫面左下角的「地球儀檢視」核取方塊。

    這會強制使用 2D 視角,並消除可能因 3D 角度檢視畫面而產生的錯誤。

  4. 在地圖上按一下要複製的位置,然後選取經度/緯度,將其複製到剪貼簿。

使用 Google 地球

只要在使用者介面中按一下某個位置,即可讀取地標詳細資料的資料,在 Google 地球中計算位置的經緯度。

如要透過 Google 地球取得特定地點的經緯度,請按照下列步驟操作:

  1. 在電腦上前往 Google 地球

  2. 前往漢堡選單 ,然後選取「地圖樣式」

  3. 關閉「3D 建築物」切換鈕。

  4. 關閉「3D 建築物」切換鈕後,按一下圖釘圖示 ,即可在所選位置新增地標。

  5. 指定要包含地標的專案,然後按一下「儲存」

  6. 在地標的「Title」欄位中,輸入地標名稱。

  7. 按一下專案窗格中的返回箭頭 ,然後選取 「More Actions」選單。

  8. 從選單中選擇「Export as KML file」

KLM 檔案會回報 <coordinates> 標記中地標的經緯度和海拔高度 (以半形逗號分隔),如下所示:

<coordinates>-122.0755182435043,37.41347299422944,7.420342565583832</coordinates>

請勿使用 <LookAt> 標記中的經緯度,因為該標記指定的是相機位置,而非地點。

前往實際地點

您可以前往該地點並進行當地觀測,計算該地點的高度。

取得旋轉四元數

GeospatialPose.getEastUpSouthQuaternion() 會從地理空間姿勢中擷取方向,並輸出四元數,代表將向量從目標轉換至東上南 (EUS) 座標系統的旋轉矩陣。X+ 指向東方、Y+ 指向上方,而 Z+ 指向南方。值會以 {x, y, z, w} 的順序寫入。

WGS84 錨點

WGS84 錨點是一種錨點,可讓您在任何指定的緯度、經度和高度上放置 3D 內容。它會依據姿勢和方向,放置在真實世界中。位置包含緯度、經度和海拔高度,這些項目皆以 WGS84 座標系統指定。方向包含四元數旋轉。

海拔高度值於參考 WGS84 橢圓球體上方以公尺為單位,因此地面等級「不是」0。您的應用程式負責為每個已建立的錨點提供這些座標。

在現實世界中放置 WGS84 錨點

判斷位置的高度

您可以透過下列幾種方式,判斷地點的高度以便放置錨點:

  • 如果錨點位置位於使用者附近,您可以使用與使用者裝置高度相近的高度。
  • 取得經緯度後,請使用 Elevation API 取得根據 EGM96 規格計算的海拔高度。您必須將 Maps API EGM96 海拔高度轉換為 WGS84,以便與 GeospatialPose 高度進行比較。請參閱同時提供指令列和 HTML 介面的 GeoidEval。Maps API 會根據預設的 WGS84 規格回報經緯度。
  • 你可以從 Google 地球取得特定地點的經緯度和海拔高度。這會產生最多數公尺的誤差範圍。使用 KML 檔案中 <coordinates> 標記而非 <LookAt> 標記的經緯度和高度。
  • 如果附近有現有的錨點,而且你並未處於陡峭的斜坡上,則可以使用相機的 GeospatialPose 提供的高度資訊,而不需要使用其他來源,例如 Maps API。

建立錨點

取得經緯度、高度和旋轉四元數後,請使用 Earth.createAnchor() 將內容固定在您指定的地理座標。

Java

if (earth != null && earth.getTrackingState() == TrackingState.TRACKING) {
  Anchor anchor =
    earth.createAnchor(
      /* Location values */
      latitude,
      longitude,
      altitude,
      /* Rotational pose values */
      qx,
      qy,
      qz,
      qw);

  // Attach content to the anchor specified by geodetic location and pose.
}

Kotlin

if (earth.trackingState == TrackingState.TRACKING) {
  val anchor =
    earth.createAnchor(
      /* Location values */
      latitude,
      longitude,
      altitude,
      /* Rotational pose values */
      qx,
      qy,
      qz,
      qw
    )

  // Attach content to the anchor specified by geodetic location and pose.
}

地形錨點

地形錨點是一種錨點,可讓您只使用經緯度來放置 AR 物件,利用 VPS 的資訊找出精確的高度。

您不需要輸入所需的高度,而是提供高於地形的高度。如果為零,錨點會與地形保持水平。

設定飛機搜尋模式

找尋平面功能為選用功能,不必使用錨點。請注意,系統只會使用水平平面。水平平面可協助地面上地形錨點的動態對齊方式。

使用 Config.PlaneFindingMode 選取應用程式偵測飛機的方式。

使用新的 Async API 建立地形錨點

如要建立及放置地形錨點,請呼叫 Earth.resolveAnchorOnTerrainAsync()

錨點不會立即就緒,需要解決。解決後,就會顯示在 ResolveAnchorOnTerrainFuture 中。

Java

final ResolveAnchorOnTerrainFuture future =
  earth.resolveAnchorOnTerrainAsync(
    latitude,
    longitude,
    /* altitudeAboveTerrain= */ 0.0f,
    qx,
    qy,
    qz,
    qw,
    (anchor, state) -> {
      if (state == TerrainAnchorState.SUCCESS) {
        // do something with the anchor here
      } else {
        // the anchor failed to resolve
      }
    });

Kotlin

var future =
  earth.resolveAnchorOnTerrainAsync(
    latitude,
    longitude,
    altitudeAboveTerrain,
    qx,
    qy,
    qz,
    qw,
    { anchor, state ->
      if (state == TerrainAnchorState.SUCCESS) {
        // do something with the anchor here
      } else {
        // the anchor failed to resolve
      }
    }
  )

查看未來狀態

Future 會具有相關的 FutureState

說明
FutureState.PENDING 這項作業仍在處理中。
FutureState.DONE 作業已完成,結果可供使用。
FutureState.CANCELLED 作業已取消。

查看未來結果的地形錨點狀態

Anchor.TerrainAnchorState 屬於非同步作業,也是最終 Future 結果的一部分。

Java

switch (terrainAnchorState) {
  case SUCCESS:
    // A resolving task for this Terrain anchor has finished successfully.
    break;
  case ERROR_UNSUPPORTED_LOCATION:
    // The requested anchor is in a location that isn't supported by the Geospatial API.
    break;
  case ERROR_NOT_AUTHORIZED:
    // An error occurred while authorizing your app with the ARCore API. See
    // https://developers.google.com/ar/reference/java/com/google/ar/core/Anchor.TerrainAnchorState#error_not_authorized
    // for troubleshooting steps.
    break;
  case ERROR_INTERNAL:
    // The Terrain anchor could not be resolved due to an internal error.
    break;
  default:
    // not reachable
    break;
}

Kotlin

when (state) {
  TerrainAnchorState.SUCCESS -> {
    // A resolving task for this Terrain anchor has finished successfully.
  }
  TerrainAnchorState.ERROR_UNSUPPORTED_LOCATION -> {
    // The requested anchor is in a location that isn't supported by the Geospatial API.
  }
  TerrainAnchorState.ERROR_NOT_AUTHORIZED -> {
    // An error occurred while authorizing your app with the ARCore API. See
    // https://developers.google.com/ar/reference/java/com/google/ar/core/Anchor.TerrainAnchorState#error_not_authorized
    // for troubleshooting steps.
  }
  TerrainAnchorState.ERROR_INTERNAL -> {
    // The Terrain anchor could not be resolved due to an internal error.
  }
  else -> {
    // Default.
  }
}

屋頂錨點

屋頂固定點 Hero

屋頂固定點是一種固定點,與上述地形固定點非常相似。差別在於你要提供屋頂以上的高度,而不是地形以上的高度。

使用新的 Async API 建立 Rooftop 錨點

錨點不會立即就緒,需要解決。

如要建立及放置屋頂錨點,請呼叫 Earth.resolveAnchorOnRooftopAsync()。與地形錨點類似,您還可以存取 Future 的 FutureState。接著,您可以查看 Future 結果,存取 Anchor.RooftopAnchorState

Java

final ResolveAnchorOnRooftopFuture future =
  earth.resolveAnchorOnRooftopAsync(
    latitude,
    longitude,
    /* altitudeAboveRooftop= */ 0.0f,
    qx,
    qy,
    qz,
    qw,
    (anchor, state) -> {
      if (state == RooftopAnchorState.SUCCESS) {
        // do something with the anchor here
      } else {
        // the anchor failed to resolve
      }
    });

Kotlin

var future =
  earth.resolveAnchorOnRooftopAsync(
    latitude,
    longitude,
    altitudeAboveRooftop,
    qx,
    qy,
    qz,
    qw,
    { anchor, state ->
      if (state == RooftopAnchorState.SUCCESS) {
        // do something with the anchor here
      } else {
        // the anchor failed to resolve
      }
    }
  )

查看未來狀況

Future 會與 FutureState 建立關聯,請參閱上方的表格

查看 Future 結果的屋頂錨點狀態

Anchor.RooftopAnchorState 屬於非同步作業,也是最終 Future 結果的一部分。

Java

switch (rooftopAnchorState) {
  case SUCCESS:
    // A resolving task for this Rooftop anchor has finished successfully.
    break;
  case ERROR_UNSUPPORTED_LOCATION:
    // The requested anchor is in a location that isn't supported by the Geospatial API.
    break;
  case ERROR_NOT_AUTHORIZED:
    // An error occurred while authorizing your app with the ARCore API.
    // https://developers.google.com/ar/reference/java/com/google/ar/core/Anchor.RooftopAnchorState#error_not_authorized
    // for troubleshooting steps.
    break;
  case ERROR_INTERNAL:
    // The Rooftop anchor could not be resolved due to an internal error.
    break;
  default:
    // not reachable
    break;
}

Kotlin

when (state) {
  RooftopAnchorState.SUCCESS -> {
    // A resolving task for this Rooftop anchor has finished successfully.
  }
  RooftopAnchorState.ERROR_UNSUPPORTED_LOCATION -> {
    // The requested anchor is in a location that isn't supported by the Geospatial API.
  }
  RooftopAnchorState.ERROR_NOT_AUTHORIZED -> {
    // An error occurred while authorizing your app with the ARCore API. See
    // https://developers.google.com/ar/reference/java/com/google/ar/core/Anchor.RooftopAnchorState#error_not_authorized
    // for troubleshooting steps.
  }
  RooftopAnchorState.ERROR_INTERNAL -> {
    // The Rooftop anchor could not be resolved due to an internal error.
  }
  else -> {
    // Default.
  }
}

後續步驟