使用地理空间锚点在 Android NDK 上定位真实世界的内容

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

地理空间锚点类型

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

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

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

  3. 屋顶锚点
    借助屋顶锚点,您只需使用纬度和经度以及相对于相应位置建筑物屋顶的高度即可放置内容。海拔是相对于建筑物顶部(由街景几何图形提供)确定的。如果未放置在建筑物上,则此值将默认为地形海拔。

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

前提条件

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

放置地理空间锚点

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

通过点击测试创建锚点

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

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

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

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

ArEarth_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. 点击项目窗格中的返回箭头 ,然后选择 更多操作菜单。

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

KLM 文件会在 <coordinates> 标记中报告地标的经纬度和海拔,以英文逗号分隔,如下所示:

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

请勿使用 <LookAt> 标记中的纬度和经度,因为它们指定的是相机位置,而不是地点。

前往实际营业地点

您可以亲自前往某个地点并进行本地观测,以计算该地点的海拔。

获取旋转四元数

ArGeospatialPose_getEastUpSouthQuaternion() 会从地理空间姿势中提取方向,并输出一个四元数,该四元数表示将矢量从目标坐标系转换为东北天 (EUS) 坐标系的旋转矩阵。X+ 指向东,Y+ 指向上,Z+ 指向南。值会按 {x, y, z, w} 的顺序写入。

WGS84 锚点

WGS84 锚点是一种锚点,可让您将 3D 内容放置在任何给定的纬度、经度和海拔高度。它依赖于姿势和方向,才能放置在现实世界中。位置由纬度、经度和海拔组成,这些信息采用 WGS84 坐标系指定。方向由四元数旋转组成。

海拔以相对于参考 WGS84 椭球体的高度(以米为单位)报告,因此地面高度不是零。您的应用负责为每个创建的锚点提供这些坐标。

在现实世界中放置 WGS84 锚点

确定地点的海拔高度

您可以通过多种方式确定位置的海拔以放置锚点:

  • 如果锚点的位置在用户附近,您可以使用与用户设备高度相近的高度。
  • 获取纬度和经度后,使用 Elevation API 根据 EGM96 规范获取海拔。您必须将 Maps API EGM96 海拔转换为 WGS84,以便与 ArGeospatialPose 海拔进行比较。查看同时具有命令行和 HTML 界面的 GeoidEval。Maps API 会根据 WGS84 规范直接报告纬度和经度。
  • 您可以通过 Google 地球获取某个地点的纬度、经度和海拔。这会导致误差范围最多达到几米。使用 KML 文件中 <coordinates> 标记(而非 <LookAt> 标记)中的纬度、经度和海拔。
  • 如果附近有现有锚点,并且您不在陡峭的斜坡上,则可以使用相机的 ArGeospatialPose 中的海拔高度,而无需使用其他来源(例如 Maps API)。

创建锚点

获得纬度、经度、海拔和旋转四元数后,使用 ArEarth_acquireNewAnchor() 将内容锚定到您指定的地理坐标。

float eus_quaternion_4[4] = {qx, qy, qz, qw};
if (ar_earth != NULL) {
  ArTrackingState earth_tracking_state = AR_TRACKING_STATE_STOPPED;
  ArTrackable_getTrackingState(ar_session, (ArTrackable*)ar_earth,
                               &earth_tracking_state);
  if (earth_tracking_state == AR_TRACKING_STATE_TRACKING) {
    ArAnchor* earth_anchor = NULL;
    ArStatus status = ArEarth_acquireNewAnchor(ar_session, ar_earth,
        /* location values */
        latitude, longitude, altitude,
        eus_quaternion_4, &earth_anchor);

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

地形锚点

地形锚点是一种锚点,可让您仅使用纬度和经度放置 AR 对象,并利用 VPS 中的信息查找相对于地面的确切海拔。

您需要提供相对于地形的高度,而不是输入所需的高度。如果此值为零,则锚点将与地形齐平。

设置飞机查找模式

平面检测是可选功能,无需使用此功能即可使用锚点。请注意,仅使用水平平面。水平平面有助于地面上的地形锚点动态对齐。

使用 ArPlaneFindingMode 选择应用检测飞机的方式。

使用新的 Async API 创建地形锚点

如需创建和放置地形锚点,请调用 ArEarth_resolveAnchorOnTerrainAsync()

锚点不会立即就绪,需要解析。问题解决后,您便可以在 ArResolveAnchorOnTerrainFuture 中看到该应用。

使用 ArResolveAnchorOnTerrainFuture_getResultTerrainAnchorState() 检查地形锚点状态。使用 ArResolveAnchorOnTerrainFuture_acquireResultAnchor() 获取解析后的锚点。

float eus_quaternion_4[4] = {qx, qy, qz, qw};
void* context = NULL;
ArResolveAnchorOnTerrainCallback callback = NULL;
ArResolveAnchorOnTerrainFuture* future = NULL;
if (ar_earth != NULL) {
  ArTrackingState earth_tracking_state = AR_TRACKING_STATE_STOPPED;
  ArTrackable_getTrackingState(ar_session, (ArTrackable*)ar_earth,
                               &earth_tracking_state);
  if (earth_tracking_state == AR_TRACKING_STATE_TRACKING) {
    ArStatus status = ArEarth_resolveAnchorOnTerrainAsync(
        ar_session, ar_earth,
        /* location values */
        latitude, longitude, altitude_above_terrain, eus_quaternion_4,
        context, callback, &future);
  }
}

查看 Future 的状态

Future 将具有关联的 ArFutureState

说明
AR_FUTURE_STATE_PENDING 该操作仍处于待处理状态。
AR_FUTURE_STATE_DONE 操作已完成,且结果可供获取。
AR_FUTURE_STATE_CANCELLED 操作已取消。

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

ArTerrainAnchorState 属于异步操作,是最终 Future 结果的一部分。

switch (terrain_anchor_state) {
  case AR_TERRAIN_ANCHOR_STATE_SUCCESS:
    // A resolving task for this anchor has been successfully resolved.
    break;
  case AR_TERRAIN_ANCHOR_STATE_ERROR_UNSUPPORTED_LOCATION:
    // The requested anchor is in a location that isn't supported by the
    // Geospatial API.
    break;
  case AR_TERRAIN_ANCHOR_STATE_ERROR_NOT_AUTHORIZED:
    // An error occurred while authorizing your app with the ARCore API. See
    // https://developers.google.com/ar/reference/c/group/ar-anchor#:~:text=from%20this%20error.-,AR_TERRAIN_ANCHOR_STATE_ERROR_NOT_AUTHORIZED,-The%20authorization%20provided
    // for troubleshooting steps.
    break;
  case AR_TERRAIN_ANCHOR_STATE_ERROR_INTERNAL:
    // The Terrain anchor could not be resolved due to an internal error.
    break;
  default:
    break;
}

屋顶锚点

Rooftop 锚点主打图片

屋顶锚点是一种锚点,与上文中的地形锚点非常相似。区别在于,您将提供相对于屋顶的海拔高度,而不是相对于地形的海拔高度。

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

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

如需创建和放置屋顶锚点,请调用 ArEarth_resolveAnchorOnRooftopAsync()。与 Terrain 锚点类似,您还将访问 Future 的 ArFutureState。然后,您可以查看“未来”结果以访问 ArRooftopAnchorState

使用 ArEarth_resolveAnchorOnRooftopAsync() 创建 ArResolveAnchorOnRooftopFuture

使用 ArResolveAnchorOnRooftopFuture_getResultRooftopAnchorState() 检查屋顶锚点状态。

使用 ArResolveAnchorOnRooftopFuture_acquireResultAnchor() 获取解析后的锚点。

float eus_quaternion_4[4] = {qx, qy, qz, qw};
void* context = NULL;
ArResolveAnchorOnRooftopCallback callback = NULL;
ArResolveAnchorOnRooftopFuture* future = NULL;
if (ar_earth != NULL) {
  ArTrackingState earth_tracking_state = AR_TRACKING_STATE_STOPPED;
  ArTrackable_getTrackingState(ar_session, (ArTrackable*)ar_earth,
                               &earth_tracking_state);
  if (earth_tracking_state == AR_TRACKING_STATE_TRACKING) {
    ArStatus status = ArEarth_resolveAnchorOnRooftopAsync(
        ar_session, ar_earth,
        /* location values */
        latitude, longitude, altitude_above_rooftop, eus_quaternion_4,
        context, callback, &future);
  }
}

查看 Future 的状态

Future 将有一个关联的 ArFutureState,请参阅上文中的表格

检查 Future 结果的屋顶锚点状态

ArRooftopAnchorState 属于异步操作,是最终 Future 结果的一部分。

switch (rooftop_anchor_state) {
  case AR_ROOFTOP_ANCHOR_STATE_SUCCESS:
    // A resolving task for this anchor has been successfully resolved.
    break;
  case AR_ROOFTOP_ANCHOR_STATE_ERROR_UNSUPPORTED_LOCATION:
    // The requested anchor is in a location that isn't supported by the
    // Geospatial API.
    break;
  case AR_ROOFTOP_ANCHOR_STATE_ERROR_NOT_AUTHORIZED:
    // An error occurred while authorizing your app with the ARCore API. See
    // https://developers.google.com/ar/reference/c/group/ar-anchor#:~:text=from%20this%20error.-,AR_ROOFTOP_ANCHOR_STATE_ERROR_NOT_AUTHORIZED,-The%20authorization%20provided
    // for troubleshooting steps.
    break;
  case AR_ROOFTOP_ANCHOR_STATE_ERROR_INTERNAL:
    // The Rooftop anchor could not be resolved due to an internal error.
    break;
  default:
    break;
}

后续步骤