使用 ArcGIS Runtime SDK for iOS 进行增强现实





0/5 (0投票)
本文将介绍如何使用 Esri 的 ArcGIS Runtime SDK 和 Toolkit for iOS 来构建引人入胜的增强现实体验。
引言
增强现实 (AR) 体验旨在用虚拟内容“增强”物理世界。这意味着在设备的摄像头画面之上显示虚拟内容。当设备移动时,该虚拟内容会尊重摄像机视角的真实世界比例、位置和方向。Esri 的ArcGIS Runtime SDK for iOS 和ArcGIS Runtime Toolkit for iOS 共同提供了一种简化的方法来开发 AR 解决方案,将地图和地理数据叠加在实时摄像头画面之上。用户可以感觉仿佛在真实世界中查看数字地图内容。
在本文中,我们将学习如何为用户提供这种 AR 地图体验。但首先,一些术语:在 Runtime 术语中,Scene
是对包含多种 3D 地理数据类型的 3D“地图”的描述。Runtime SceneView
是一个用于向用户显示该 Scene 的 UI 组件。当与ArcGIS Toolkit结合使用时,SceneView
可以快速轻松地变成 AR 体验,将 3D 地理数据作为虚拟内容叠加在摄像头画面之上。
在开始之前,请务必查看此链接,注册一个免费的 ArcGIS for Developers 订阅。
您需要 XCode 10.2 或更高版本以及运行 iOS 11.0 或更高版本的设备。
如果您想了解本文中介绍的任何内容的更多详细信息,文末有链接。
背景
为了使用本文中提供的信息和代码,您需要安装一些项目
- ArcGIS Runtime SDK for iOS - 一个现代、高性能的地图 API,可在 Swift 和 Objective-C 中使用。
- ArcGIS Runtime Toolkit for iOS - 开源代码,包含一系列组件,可简化您使用 ArcGIS Runtime 进行 iOS 应用开发。这包括我们将在这里展示的 AR 组件。
AR Toolkit 组件允许您使用三种常见的 AR 模式来构建应用程序
世界尺度 - 一种 AR 场景,其中场景内容按其在物理世界中的实际位置渲染。这用于从查看隐藏基础设施到显示导航航点的各种场景。在 AR 中,真实世界而不是底图提供了地理数据的上下文。
桌面 - 一种 AR 场景,其中场景内容固定在一个物理表面上,就像 3D 打印模型一样。您可以围绕桌面行走并从不同角度查看场景。桌面场景中的原始相机通常位于场景的最低点。
飞越 - 飞越 AR 是一种 AR 场景,它允许您将设备作为虚拟世界的窗口来探索场景。典型的飞越 AR 场景将从场景的虚拟相机定位在感兴趣的区域上方开始。您可以“穿过”数据行走,并通过重新定向设备来聚焦场景中的特定内容。飞越场景中的原始相机通常位于场景中最高的内容上方。
AR 工具包组件由一个类组成:ArcGISARView
。这是一个 UIView
的子类,其中包含在应用程序中显示 AR 体验所需的功能。它使用 ARKit,Apple 的增强现实框架,来显示实时摄像头画面并处理与 Runtime SDK 的 AGSSceneView
的真实世界跟踪和同步。ArcGISARView
负责启动和管理 ARKit 会话。它使用 AGSLocationDataSource
(一个封装设备位置信息和更新的类)来获取初始 GPS 位置并在需要连续 GPS 跟踪时进行跟踪。
ArcGISARView
的功能
- 允许显示实时摄像头画面
- 管理 ARKit
ARSession
生命周期 - 通过 ARKit 和设备 GPS 的组合跟踪用户位置和设备方向
- 提供对
AGSSceneView
的访问,以便在实时摄像头画面之上显示您的 GIS 3D 数据 ARScreenToLocation
方法,用于将屏幕点转换为真实世界坐标- 轻松访问所有 ARKit 和
AGSLocationDataSource
委托方法
实现上述三种 AR 模式所需的步骤相似,将在下面详细介绍。
有关安装 ArcGIS Runtime SDK 的信息可以在此处找到。
有关将 Toolkit 集成到您的项目的信息可以在此处找到。
文章的其余部分假定您已安装 SDK,克隆或 fork 了 Toolkit 仓库,并已设置好项目以集成两者。文章底部有一个 GitHub 仓库链接,其中包含本文提出的代码。
使用代码
使用上述 SDK 和 Toolkit 创建一个支持 AR 的应用程序非常简单。基本步骤是
- 将
ArcGISARView
添加到应用程序的视图控制器视图的子视图中。这可以通过代码或通过 Storyboard/.xib 文件完成。 - 将必要的条目添加到应用程序的 plist 文件(用于摄像头和 GPS)。
- 创建并设置
ArcGISARView.sceneView.scene
属性的AGSScene
(AGSScene
引用您想要在实时摄像头画面之上显示的 3D 数据)。 - 在
ArcGISARView
上设置位置数据源(如果您想跟踪设备位置)。稍后将详细介绍... - 调用
ArcGISARView.startTracking(_)
来开始跟踪位置和设备运动,当您的应用程序准备好开始其 AR 会话时。
所有三种 AR 模式都使用相同的基本步骤。区别在于 ArcGISARView
的某些属性如何设置。这些属性是
originCamera
- 视图的初始相机位置;用于在数据不在您当前真实世界位置时指定一个位置。translationFactor
- 指定相机在真实世界中移动每米时,在虚拟世界中移动多少米。用于桌面和飞越场景。locationDataSource
- 这指定了用于获取位置更新的数据源。ArcGIS Runtime SDK 包含AGSCLLocationDataSource
,这是一个使用 CoreLocation 生成位置更新的位置数据源。如果您不关心位置更新(例如在飞越和桌面场景中),此属性可以为nil
。startTracking(_ locationTrackingMode: ARLocationTrackingMode)
-locationTrackingMode
参数表示您想如何使用位置数据源。有三个选项
.ignore
:完全忽略位置数据源更新.initial
:仅使用第一个位置数据源位置更新.continuous
:使用所有位置更新
世界尺度
我们将首先创建一个基本的“世界尺度”AR 体验。这将显示一个包含影像底图和要素图层的Web Scene,您可以通过移动设备查看周围数据。Web Scene 还包含一个高程源,用于以其实际世界高度显示数据。
我们需要做的第一件事是将 ArcGISARView
添加到您的应用程序中。这可以在 Storyboard/.xib 文件或代码中完成。首先,定义 ArcGISARView
变量并将其添加到视图控制器的视图中
// Creates an ArcGISARView and specifies we want to view the live camera feed.
let arView = ArcGISARView(renderVideoFeed: true)
override func viewDidLoad() {
super.viewDidLoad()
// Add the ArcGISARView to our view and set up constraints.
view.addSubview(arView)
arView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
arView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
arView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
arView.topAnchor.constraint(equalTo: view.topAnchor),
arView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
接下来,您需要为摄像头和设备位置在应用程序的 plist 文件中添加所需的隐私密钥
<key>NSCameraUsageDescription</key>
<string>For displaying the live camera feed</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>For determining your current location</string>
为了加载和显示数据,请创建一个 AGSScene
。我们将使用一个包含底图和要素图层的 Web Scene。由于要素数据是位置特定的,您可能不会在您当前位置看到任何数据,需要添加您自己的数据。将此代码添加到您的 viewDidLoad
方法中
// Create our scene and set it on the arView.sceneView.
let scene = AGSScene(url: URL(string: "https://runtime.maps.arcgis.com/home/webscene/viewer.html?webscene=b887e33acae84a0195e725c8c093c69a")!)!
arView.sceneView.scene = scene
然后创建一个位置数据源并将其设置在 arView
上。我们正在使用ArcGISRuntime SDK 中包含的 AGSCLLocationDataSource
,通过 Apple 的 CoreLocation 框架提供位置信息。设置场景后,添加此行
arView.locationDataSource = AGSCLLocationDataSource()
最后一步是调用 arView
上的 startTracking
来启动 ARKit 的位置和设备运动跟踪。这在您的视图控制器viewDidAppear
方法中完成,因为我们不需要在视图显示之前开始跟踪位置和运动。当视图不再可见时(在 viewDidDisappear
中),您也应该停止跟踪。
override func viewDidAppear(_ animated: Bool) {
arView.startTracking(.continuous)
}
override func viewDidDisappear(_ animated: Bool) {
arView.stopTracking()
}
我们在上面使用了 .continous
,因为我们想通过 GPS 持续跟踪设备位置。这对于世界尺度的 AR 体验很常见。对于桌面和飞越体验,您可以使用 .initial
(仅获取初始设备位置)或 .ignore
(当不需要设备位置时,例如设置原始相机时)。
此时,您可以构建并运行应用程序。底图的显示将与此类似,但位于您的地理位置。树木数据是位置特定的,但通过添加您自己的数据,您可以以很少的代码创建丰富的体验。旋转、倾斜和平移设备将导致场景的显示相应地改变。
桌面
现在我们已经为世界尺度场景创建了一个基本的 AR 体验,让我们继续桌面场景。在桌面 AR 中,数据固定在一个物理表面上,就像 3D 打印模型一样。您可以围绕桌面行走并从不同角度查看场景。
桌面模式与世界尺度模式相比,有几个不同之处在于,它能够探索不仅仅是您周围的环境,并且不使用设备的真实世界位置来确定虚拟世界中的相机位置。
ArcGISARView
的 translationFactor
属性允许在虚拟世界中有比真实世界更多的移动。translationFactor
的默认值为 1.0,这意味着在真实世界中移动设备 1 米,在虚拟世界中相机移动 1 米。将 translationFactor
设置为 500.0 意味着设备在真实世界中每移动一米,相机在虚拟世界中就会移动 500.0 米。我们还将设置一个原始相机,它表示相机的初始位置和方向,而不是使用设备的真实世界位置。
这次我们将使用一些不同的数据,因此我们将创建一个新的场景,没有底图,一个代表优胜美地国家公园数据的图层,以及一个高程源(使用 AGSScene
扩展)。
// Create the scene.
let scene = AGSScene()
scene.addElevationSource()
// Create the Yosemite layer.
let layer = AGSIntegratedMeshLayer(url: URL(string: "https://tiles.arcgis.com/tiles/FQD0rKU8X5sAQfh8/arcgis/rest/services/VRICON_Yosemite_Sample_Integrated_Mesh_scene_layer/SceneServer")!)
scene.operationalLayers.add(layer)
// Set the scene on the arView.sceneView.
arView.sceneView.scene = scene
// Create and set the origin camera.
let camera = AGSCamera(latitude: 37.730776, longitude: -119.611843, altitude: 1213.852173, heading: 0, pitch: 90.0, roll: 0)
arView.originCamera = camera
// Set translationFactor.
arView.translationFactor = 18000
这是将高程源添加到您的场景的 AGSScene
扩展
// MARK: AGSScene extension.
extension AGSScene {
/// Adds an elevation source to the given scene.
func addElevationSource() {
let elevationSource = AGSArcGISTiledElevationSource(url: URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!)
let surface = AGSSurface()
surface.elevationSources = [elevationSource]
surface.name = "baseSurface"
surface.isEnabled = true
surface.backgroundGrid.isVisible = false
surface.navigationConstraint = .none
baseSurface = surface
}
}
在 viewDidAppear
中,您可以将 .ignore
传递给 startTracking
以忽略位置数据
// For Tabletop AR, ignore location updates.
// Start tracking, but ignore the GPS as we've set an origin camera.
arView.startTracking(.ignore)
运行应用程序时,如果您缓慢地围绕桌面或其他您想固定数据的平面缓慢移动相机,arView
(使用 ARKit)将自动检测水平平面。这些平面可用于确定您想固定的表面。可以使用以下几行代码和下面定义的 Plane 类来可视化这些平面。
在 viewDidLoad
中
// Set ourself as delegate so we can get ARSCNViewDelegate method calls.
arView.arSCNViewDelegate = self
接下来,将 ARSCNViewDelegate
实现为扩展。您需要导入 ARKit 才能获取 ARSCNViewDelegate
的定义。
import ARKit
// MARK: ARSCNViewDelegate
extension ViewController: ARSCNViewDelegate {
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
// Place content only for anchors found by plane detection.
guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
// Create a custom object to visualize the plane geometry and extent.
let plane = Plane(anchor: planeAnchor, in: arView.arSCNView)
// Add the visualization to the ARKit-managed node so that it tracks
// changes in the plane anchor as plane estimation continues.
node.addChildNode(plane)
}
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
// Update only anchors and nodes set up by renderer(_:didAdd:for:).
guard let planeAnchor = anchor as? ARPlaneAnchor,
let plane = node.childNodes.first as? Plane
else { return }
// Update extent visualization to the anchor's new bounding rectangle.
if let extentGeometry = plane.node.geometry as? SCNPlane {
extentGeometry.width = CGFloat(planeAnchor.extent.x)
extentGeometry.height = CGFloat(planeAnchor.extent.z)
plane.node.simdPosition = planeAnchor.center
}
}
}
一旦显示了平面,用户就可以点击它并将数据固定到它上面。我们可以使 sceneView
半透明,以便更好地看到检测到的平面(在 viewDidLoad
方法中)
// Dim the SceneView until the user taps on a surface.
arView.sceneView.alpha = 0.5
为了捕获用户点击,您想实现 sceneView
的 AGSGeoViewTouchDelegate
。我们在 viewDidLoad
中通过设置 sceneView
上的 touchDelegate
来实现这一点
// Set ourself as touch delegate so we can get touch events.
arView.sceneView.touchDelegate = self
然后我们在扩展中实现 geoView:didTapAtScreenPoint:mapPoint:
方法
// MARK: AGSGeoViewTouchDelegate
extension ViewController: AGSGeoViewTouchDelegate {
public func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
// Place the scene at the given point by setting the initial transformation.
if arView.setInitialTransformation(using: screenPoint) {
// Show the SceneView now that the user has tapped on the surface.
UIView.animate(withDuration: 0.5) { [weak self] in
self?.arView.sceneView.alpha = 1.0
}
}
}
}
arView.setInitialTransformation
方法将采用点击的屏幕点,并确定 ARKit 平面是否与屏幕点相交;如果相交,它将设置 arView.initialTransformation
属性,从而将数据固定到点击的平面上。上面的代码还将使 sceneView
完全不透明。
用于可视化 ARKit 平面的 Plane
类
/// Helper class to visualize a plane found by ARKit
class Plane: SCNNode {
let node: SCNNode
init(anchor: ARPlaneAnchor, in sceneView: ARSCNView) {
// Create a node to visualize the plane's bounding rectangle.
let extent = SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))
node = SCNNode(geometry: extent)
node.simdPosition = anchor.center
// `SCNPlane` is vertically oriented in its local coordinate space, so
// rotate it to match the orientation of `ARPlaneAnchor`.
node.eulerAngles.x = -.pi / 2
super.init()
node.opacity = 0.6
guard let material = node.geometry?.firstMaterial
else { fatalError("SCNPlane always has one material") }
material.diffuse.contents = UIColor.white
// Add the plane node as child node so they appear in the scene.
addChildNode(node)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
运行应用程序,然后缓慢地在桌面周围移动设备以找到一个平面;一旦找到一个,点击它即可放置数据。然后,您可以围绕桌面移动设备,从所有角度查看数据。
飞越
我们将介绍的最后一个 AR 场景是飞越。飞越 AR 是一种 AR 场景,它允许您将设备作为虚拟世界的窗口来探索场景。我们将像桌面场景一样开始,创建一个新的场景,没有底图,一个代表美国-墨西哥边境数据的图层,以及一个高程源
// Create the scene.
let scene = AGSScene()
scene.addElevationSource()
// Create the border layer.
let layer = AGSIntegratedMeshLayer(url: URL(string: "https://tiles.arcgis.com/tiles/FQD0rKU8X5sAQfh8/arcgis/rest/services/VRICON_SW_US_Sample_Integrated_Mesh_scene_layer/SceneServer")!)
// Add layer to the scene's operational layers array.
scene.operationalLayers.add(layer)
// Set the scene on the arView.sceneView.
arView.sceneView.scene = scene
// Create and set the origin camera.
let camera = AGSCamera(latitude: 32.533664, longitude: -116.924699, altitude: 126.349876, heading: 0, pitch: 90.0, roll: 0)
arView.originCamera = camera
// Set translationFactor.
arView.translationFactor = 1000
我们将 arView.startTracking(.ignore)
方法与桌面场景的相同。
// For Tabletop and Flyover AR, ignore location updates.
// Start tracking, but ignore the GPS as we've set an origin camera.
arView.startTracking(.ignore)
此时构建和运行应用程序,您就可以从更高的海拔高度查看数据,并“飞越”数据以不同的方式查看。
关注点
在移动设备上构建功能齐全的 AR 体验的一个棘手部分是设备 GPS 位置和方向的相对不准确性。准确性将根据 AR 场景的类型而有所不同。对于桌面和飞越,准确性通常不重要,因为在大多数情况下,您将直接设置初始位置。对于世界尺度的 AR,这可能非常重要,尤其是在您试图根据数字对应物定位真实世界特征时。可能需要某种类型的校准,即调整位置和方向以匹配真实世界的位置和方向。ArcGIS Runtime Toolkit for iOS 提供了一个校准视图和工作流程的示例,用于处理这些不准确性,您可以将其集成到您的应用程序中。有关更多信息,请参阅下面的 Toolkit AR 示例链接。
更多信息
- Apple ARKit
- ArcGIS Toolkit for iOS 包含一个 AR 示例,允许用户从几种不同的 AR 场景中进行选择。有关 Toolkit 的 AR 组件和示例的更多信息可以在此处找到。
- 有关使用ArcGISRuntime SDK 和ArcGIS Runtime Toolkit 构建引人入胜、功能齐全的应用程序的更多信息和细节,请参阅ArcGIS Runtime SDK Guide 主题:在增强现实中显示场景。
- 一个包含演示本文代码的完整功能应用程序的 GitHub 仓库可以在此处找到。