65.9K
CodeProject 正在变化。 阅读更多。
Home

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

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2019年12月16日

CPOL
viewsIcon

5292

本文将介绍如何使用 Esri 的 ArcGIS Runtime SDK 和 Toolkit for iOS 来构建引人入胜的增强现实体验。

引言

增强现实 (AR) 体验旨在用虚拟内容“增强”物理世界。这意味着在设备的摄像头画面之上显示虚拟内容。当设备移动时,该虚拟内容会尊重摄像机视角的真实世界比例、位置和方向。Esri 的ArcGIS Runtime SDK for iOSArcGIS 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 属性的 AGSSceneAGSScene 引用您想要在实时摄像头画面之上显示的 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>

Info.plist

为了加载和显示数据,请创建一个 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(当不需要设备位置时,例如设置原始相机时)。

此时,您可以构建并运行应用程序。底图的显示将与此类似,但位于您的地理位置。树木数据是位置特定的,但通过添加您自己的数据,您可以以很少的代码创建丰富的体验。旋转、倾斜和平移设备将导致场景的显示相应地改变。

World-scale screen shot

桌面

现在我们已经为世界尺度场景创建了一个基本的 AR 体验,让我们继续桌面场景。在桌面 AR 中,数据固定在一个物理表面上,就像 3D 打印模型一样。您可以围绕桌面行走并从不同角度查看场景。

桌面模式与世界尺度模式相比,有几个不同之处在于,它能够探索不仅仅是您周围的环境,并且不使用设备的真实世界位置来确定虚拟世界中的相机位置。

ArcGISARViewtranslationFactor 属性允许在虚拟世界中有比真实世界更多的移动。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

为了捕获用户点击,您想实现 sceneViewAGSGeoViewTouchDelegate。我们在 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")
    }
}

运行应用程序,然后缓慢地在桌面周围移动设备以找到一个平面;一旦找到一个,点击它即可放置数据。然后,您可以围绕桌面移动设备,从所有角度查看数据。

Tabletop screenshot

飞越

我们将介绍的最后一个 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)

此时构建和运行应用程序,您就可以从更高的海拔高度查看数据,并“飞越”数据以不同的方式查看。

Flyover screen shot

关注点

在移动设备上构建功能齐全的 AR 体验的一个棘手部分是设备 GPS 位置和方向的相对不准确性。准确性将根据 AR 场景的类型而有所不同。对于桌面和飞越,准确性通常不重要,因为在大多数情况下,您将直接设置初始位置。对于世界尺度的 AR,这可能非常重要,尤其是在您试图根据数字对应物定位真实世界特征时。可能需要某种类型的校准,即调整位置和方向以匹配真实世界的位置和方向。ArcGIS Runtime Toolkit for iOS 提供了一个校准视图和工作流程的示例,用于处理这些不准确性,您可以将其集成到您的应用程序中。有关更多信息,请参阅下面的 Toolkit AR 示例链接。

更多信息

  • Apple ARKit
  • ArcGIS Toolkit for iOS 包含一个 AR 示例,允许用户从几种不同的 AR 场景中进行选择。有关 Toolkit 的 AR 组件和示例的更多信息可以在此处找到。
  • 有关使用ArcGISRuntime SDKArcGIS Runtime Toolkit 构建引人入胜、功能齐全的应用程序的更多信息和细节,请参阅ArcGIS Runtime SDK Guide 主题:在增强现实中显示场景
  • 一个包含演示本文代码的完整功能应用程序的 GitHub 仓库可以在此处找到。
© . All rights reserved.