在 Android SDK (Kotlin/Java) 中使用地理空间锚点定位真实内容

地理空间锚点是一种锚点,可让您在现实世界中放置 3D 内容。

地理空间锚点的类型

地理空间锚点有三种类型,它们处理海拔的方式有所不同:

  1. WGS84 锚点
    WGS84 锚点可让您在任何给定的纬度、经度和海拔高度放置 3D 内容。

  2. 地形锚点
    通过地形锚点,您仅使用纬度和经度(以相对于该位置的地形的高度)放置内容。海拔高度是相对于地面或地面(即 VPS 所知)确定的。

  3. 屋顶锚点
    借助屋顶锚点,您只需使用纬度和经度(相对于建筑物屋顶在该位置的高度)即可放置内容。海拔高度是相对于建筑物顶部而言的,也称为街景几何图形。如果不放置在建筑物上,默认采用地形海拔高度。

WGS84 地形 屋顶
水平位置 纬度,经度 纬度,经度 纬度,经度
垂直位置 相对于 WGS84 海拔高度 相对于 Google 地图确定的地形水平 相对于 Google 地图确定的屋顶楼层
需要由服务器解析?

前提条件

请务必先启用 Geospatial API,然后再继续操作。

放置地理空间锚点

每种锚点类型都有用于创建这些锚点的专用 API;如需了解详情,请参阅地理空间锚点类型

根据点击测试创建锚点

您还可以根据命中测试结果创建地理空间锚点。使用点击测试中的 Pose,并将其转换为 GeospatialPose。使用它来放置上述 3 种锚点类型中的任意一种。

通过 AR 姿势获取地理空间姿势

Earth.getGeospatialPose() 通过将 AR 姿势转换为地理空间姿势,提供了一种确定纬度和经度的另一种方式。

通过地理空间姿势获取 AR 姿势

Earth.getPose() 可将地球指定的水平位置、海拔高度和四元数旋转(相对于东经南坐标系)转换为相对于 GL 世界坐标的 AR 位置。

选择适合您的用例的方法

每种创建锚点的方法都有相关的权衡需要注意:

  • 使用街景几何图形时,请使用点击测试将内容附加到建筑物。
  • 首选地形或屋顶锚点而非 WGS84 锚点,因为它们使用由 Google 地图确定的海拔值。

确定某个地点的经纬度

您可以通过以下三种方式计算某个位置的纬度和经度:

  • 您可以使用 Geospatial Creator,通过 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. 在地标的标题字段中,输入地标的名称。

  7. 点击“Project”窗格中的后退箭头 ,然后选择 更多操作菜单。

  8. 从菜单中选择导出为 KML 文件

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 椭球上方以米为单位,这样地平面为零。您的应用负责为每个已创建的锚点提供这些坐标。

在现实世界中放置 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 此操作已被取消。

检查 Future 结果的地形锚点状态

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.
  }
}

屋顶锚栓

屋顶锚点

屋顶锚点是一种锚点,与上面的地形锚点非常相似。不同之处在于,您需要提供屋顶上方的海拔高度,而不是地形上方的海拔高度。

使用新的 Async API 创建 Rooftop 锚点

锚点不会立即准备就绪,需要解决。

如需创建并放置 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 结果的 Rooftop 锚点状态

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.
  }
}

后续步骤