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

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

地理空间锚点类型

地理空间锚点有三种类型,每种类型处理海拔的方式各不相同:

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

  2. 地形锚点
    借助地形锚点,您只需使用纬度和经度以及相对于该位置地形的高度即可放置内容。海拔是相对于 VPS 所知的地面或地板确定的。

  3. 屋顶锚点
    屋顶锚点可让您仅使用纬度和经度放置内容,且高度相对于建筑物屋顶在该位置。海拔高度是以 Streetscape Geometry 已知的建筑物顶部为参照物的。如果未放置在建筑物上,则此值将默认为地形海拔。

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

前提条件

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

放置地理空间锚点

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

根据点击测试创建锚点

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

从 AR 姿势获取地理空间姿势

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

从地理空间姿态获取 AR 姿态

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

选择适合您的使用场景的方法

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

  • 使用 Streetscape Geometry 时,请使用点击测试将内容附加到建筑物。
  • 首选地形锚点或屋顶锚点,而不是 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. 点击项目窗格中的返回箭头 ,然后选择 更多操作菜单。

  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 中的海拔高度,而无需使用其他来源(例如 Google 地图 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.
  }
}

屋顶锚点

Rooftop 锚点主打图片

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

使用新的 Async API 创建屋顶锚点

锚点不会立即就绪,需要解析。

如需创建并放置 Rooftop 锚点,请调用 Earth.resolveAnchorOnRooftopAsync()。与地形锚点类似,您也将访问 Future 的 FutureState。然后,您可以查看“未来”结果以访问 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 的状态

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

后续步骤