你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

如何借助 Swift 来使用 Azure 空间定位点创建和查找定位点

借助 Azure 空间定位点,可以在不同设备之间共享全球的定位点。 它支持多个不同的开发环境。 本文将深入介绍如何借助 Swift 来使用 Azure 空间定位点 SDK 执行以下操作:

  • 正确设置和管理 Azure 空间定位点会话。
  • 创建和设置本地定位点的属性。
  • 将其上传到云。
  • 查找和删除云空间定位点。

必备条件

要完成本指南,请确保:

Cross Platform

初始化会话

此 SDK 的主入口点是表示会话的类。 通常,你会在此类中声明一个用于管理视图和本机 AR 会话的字段。

详细了解 ASACloudSpatialAnchorSession 类。

    var _cloudSession : ASACloudSpatialAnchorSession? = nil
    // In your view handler
    _cloudSession = ASACloudSpatialAnchorSession()

设置身份验证

若要访问服务,需要提供帐户密钥、访问令牌或 Microsoft Entra 身份验证令牌。 还可在“身份验证概念”页中了解此方面的详细信息。

帐户密钥

帐户密钥是一种允许应用程序使用 Azure 空间定位点服务进行身份验证的凭据。 帐户密钥的预期用途是帮助你快速入门, 尤其是在应用程序与 Azure 空间定位点集成的开发阶段。 因此,可以这样使用帐户密钥:在开发过程中将它们嵌入客户端应用程序。 完成开发阶段以后,强烈建议迁移到属于生产级别、受访问令牌支持的身份验证机制或 Microsoft Entra 用户身份验证。 若要获取用于开发的帐户密钥,请访问 Azure 空间定位点帐户并导航到“密钥”选项卡。

详细了解 ASASessionConfiguration 类。

    _cloudSession!.configuration.accountKey = "MyAccountKey"

访问令牌

访问令牌是一种更可靠的方法,用于使用 Azure 空间定位点进行身份验证。 为生产部署准备应用程序时,它尤其有用。 概括而言,此方法设置客户端应用程序可以用来安全地进行身份验证的后端服务。 后端服务在运行时与 AAD 进行交互并与 Azure 空间定位点安全令牌服务进行交互来请求访问令牌。 然后,该令牌将被传递给客户端应用程序,并在 SDK 中用来通过 Azure 空间定位点进行身份验证。

    _cloudSession!.configuration.accessToken = "MyAccessToken"

如果未设置访问令牌,则必须处理 TokenRequired 事件,或在委托协议上实现 tokenRequired 方法。

可以通过设置事件参数上的属性来同步处理事件。

详细了解 tokenRequired 协议方法。

    internal func tokenRequired(_ cloudSession:ASACloudSpatialAnchorSession!, _ args:ASATokenRequiredEventArgs!) {
        args.accessToken = "MyAccessToken"
    }

如果需要在处理程序中执行异步工作,则可以通过请求 deferral 对象然后完成该对象来延迟设置令牌,如以下示例所示。

    internal func tokenRequired(_ cloudSession:ASACloudSpatialAnchorSession!, _ args:ASATokenRequiredEventArgs!) {
        let deferral = args.getDeferral()
        myGetTokenAsync( withCompletionHandler: { (myToken: String?) in
            if (myToken != nil) {
                args.accessToken = myToken
            }
            deferral?.complete()
        })
    }

Microsoft Entra 身份验证

Azure 空间定位点还允许应用程序通过用户 Microsoft Entra ID (Active Directory) 令牌进行身份验证。 例如,你可以使用 Microsoft Entra 令牌与 Azure 空间定位点集成。 如果企业在 Microsoft Entra ID 中维护用户,则你可以在 Azure 空间定位点 SDK 中提供用户 Microsoft Entra 令牌。 通过执行此操作,对于属于同一 Microsoft Entra 租户的帐户,可以直接向 Azure 空间定位点服务进行身份验证。

    _cloudSession!.configuration.authenticationToken = "MyAuthenticationToken"

与访问令牌一样,如果未设置 Microsoft Entra 令牌,则必须处理 TokenRequired 事件,或在委托协议上实现 tokenRequired 方法。

可以通过设置事件参数上的属性来同步处理事件。

    internal func tokenRequired(_ cloudSession:ASACloudSpatialAnchorSession!, _ args:ASATokenRequiredEventArgs!) {
        args.authenticationToken = "MyAuthenticationToken"
    }

如果需要在处理程序中执行异步工作,则可以通过请求 deferral 对象然后完成该对象来延迟设置令牌,如以下示例所示。

    internal func tokenRequired(_ cloudSession:ASACloudSpatialAnchorSession!, _ args:ASATokenRequiredEventArgs!) {
        let deferral = args.getDeferral()
        myGetTokenAsync( withCompletionHandler: { (myToken: String?) in
            if (myToken != nil) {
                args.authenticationToken = myToken
            }
            deferral?.complete()
        })
    }

设置库

调用 Start() 使会话能够处理环境数据。

若要处理会话引发的事件,请将会话的 delegate 属性设置为一个对象,例如视图。 此对象必须实现 SSCCloudSpatialAnchorSessionDelegate 协议。

详细了解 start 方法。

    _cloudSession!.session = self.sceneView.session;
    _cloudSession!.delegate = self;
    _cloudSession!.start()

为会话提供帧

空间定位点会话的工作方式是映射用户周围的空间。 这样做有助于确定定位点的位置。 移动平台(iOS 和 Android)需要对相机馈送进行本机调用才能从平台的 AR 库中获取帧。 相比之下,HoloLens 一直在扫描环境,因此不需要像在移动平台上那样进行特定的调用。

详细了解 processFrame 方法。

    _cloudSession?.processFrame(self.sceneView.session.currentFrame)

向用户提供反馈

可以编写代码来处理会话的已更新事件。 每次会话改进对你周围环境的理解时,都会触发此事件。 这样做使你可以:

  • 使用 UserFeedback 类在设备移动时向用户提供反馈,而会话则会更新其环境理解。 为此,请按以下步骤操作:
  • 确定在何时有足够的已跟踪空间数据来创建空间定位点。 可以通过 ReadyForCreateProgressRecommendedForCreateProgress 来确定这一点。 一旦 ReadyForCreateProgress 超过 1,我们就会有足够的数据来保存云空间定位点,不过我们建议你等到 RecommendedForCreateProgress 大于 1 才这样做。

详细了解 sessionUpdated 协议方法。

    internal func sessionUpdated(_ cloudSession:ASACloudSpatialAnchorSession!, _ args:ASASessionUpdatedEventArgs!) {
        let status = args.status!
        if (status.userFeedback.isEmpty) {
            return
        }
        _feedback = "Feedback: \(FeedbackToString(userFeedback:status.userFeedback)) - Recommend Create=\(status.recommendedForCreateProgress * 100)"
    }

创建云空间定位点

若要创建云空间定位点,请先在平台的 AR 系统中创建一个定位点,然后创建一个云对应项。 我们使用 CreateAnchorAsync() 方法。

详细了解 ASACloudSpatialAnchor 类。

    // Create a local anchor, perhaps by hit-testing and creating an ARAnchor
    var localAnchor : ARAnchor? = nil
    let hits = self.sceneView.session.currentFrame?.hitTest(CGPoint(x:0.5, y:0.5), types: ARHitTestResult.ResultType.estimatedHorizontalPlane)
    if (hits!.count == 0) return
    // The hitTest method sorts the resulting list by increasing distance from the camera
    // The first hit result will usually be the most relevant when responding to user input
    localAnchor = ARAnchor(transform:hits![0].worldTransform)
    self.sceneView.session.add(anchor: _localAnchor!)

    // If the user is placing some application content in their environment,
    // you might show content at this anchor for a while, then save when
    // the user confirms placement.
    var cloudAnchor : ASACloudSpatialAnchor? = nil
    cloudAnchor = ASACloudSpatialAnchor()
    cloudAnchor!.localAnchor = localAnchor
    _cloudSession?.createAnchor(cloudAnchor!, withCompletionHandler: { (error: Error?) in
        if (error != nil) {
            _feedback = "Save Failed:\(error!.localizedDescription)"
            return
        }
        _feedback = "Created a cloud anchor with ID=\(cloudAnchor!.identifier!)"
    })

如前所述,在尝试创建新的云空间定位点之前,需要捕获足够的环境数据。 这意味着 ReadyForCreateProgress 必须大于 1,不过我们建议你等到 RecommendedForCreateProgress 大于 1 才这样做。

详细了解 getStatusWithCompletionHandler 方法。

    _cloudSession?.getStatusWithCompletionHandler( { (value:ASASessionStatus, error:Error?) in
        if (error != nil) {
            _feedback = "Session status error:\(error!.localizedDescription)"
            return
        }
        if (value!.recommendedForCreateProgress <> 1.0) {
            return
        }
        // Issue the creation request ...
    })

设置属性

可以选择在保存云空间定位点时添加一些属性。 例如,要保存的对象的类型,或基本属性(例如是否应允许它进行交互)。 在发现后,这样做可能会很有用:可以立即呈现用户的对象,例如包含空白内容的图片框。 然后,后台的另一下载会获取其他状态详细信息,例如,要在框架中显示的图片。

详细了解 appProperties 属性。

    var cloudAnchor : ASACloudSpatialAnchor? = nil
    cloudAnchor = ASACloudSpatialAnchor()
    cloudAnchor!.localAnchor = localAnchor
    cloudAnchor!.appProperties = [ "model-type" : "frame", "label" : "my latest picture" ]
    _cloudSession?.createAnchor(cloudAnchor!, withCompletionHandler: { (error: Error?) in
        // ...
    })

更新属性

若要更新定位点上的属性,请使用 UpdateAnchorProperties() 方法。 如果两个或更多设备同时尝试更新同一定位点的属性,我们将使用乐观并发模型。 这意味着第一个写入将会胜出。 所有其他写入将收到“并发”错误:在重试之前需要刷新属性。

详细了解 updateAnchorProperties 方法。

    var anchor : ASACloudSpatialAnchor? = /* locate your anchor */;
    anchor!.appProperties["last-user-access"] = "just now"
    _cloudSession?.updateAnchorProperties(anchor!, withCompletionHandler: { (error:Error?) in
        if (error != nil) {
            _feedback = "Updating Properties Failed:\(error!.localizedDescription)"
        }
    })

在服务上创建定位点后,无法更新定位点的位置 - 你必须创建一个新的定位点并删除旧的,才能跟踪新位置。

如果不需要定位某个定位点来更新其属性,则可以使用 GetAnchorPropertiesAsync() 方法,该方法将返回具有属性的 CloudSpatialAnchor 对象。

详细了解 getAnchorProperties 方法。

    _cloudSession?.getAnchorProperties("anchorId", withCompletionHandler: { (anchor:SCCCloudSpatialAnchor?, error:Error?) in
        if (error != nil) {
            _feedback = "Getting Properties Failed:\(error!.localizedDescription)"
        }
        if (anchor != nil) {
            anchor!.appProperties["last-user-access"] = "just now"
            _cloudSession?.updateAnchorProperties(anchor!, withCompletionHandler: { (error:Error?) in
                // ...
            })
        }
    })

设置到期时间

还可以将定位点配置为在将来的给定日期自动过期。 当定位点过期后,将不再对其进行定位或更新。 只能在创建定位点时设置过期时间,然后再将其保存到云中。 之后无法更新过期时间。 如果在创建定位点期间未设置过期时间,则定位点仅在手动删除时过期。

详细了解 expiration 属性。

    let secondsInAWeek = 60.0 * 60.0 * 24.0 * 7.0
    let oneWeekFromNow = Date(timeIntervalSinceNow: secondsInAWeek)
    cloudAnchor!.expiration = oneWeekFromNow

查找云空间定位点

查找以前保存的云空间定位点是使用 Azure 空间定位点的主要原因之一。 为此,我们将使用“观察程序”。 一次只能使用一个观察程序;不支持多个观察程序。 观察程序可以通过多种不同的方式(也称为定位点定位策略)查找云空间定位点。 每次可以使用一种策略来观察效果。

  • 按标识符查找定位点。
  • 查找已连接到以前所找到的定位点的定位点。 可在此处了解定位点关系。
  • 使用粗略重新定位查找定位点。

注意

每次当你查找定位点时,Azure 空间定位点都会尝试使用收集的环境数据来扩充定位点上的视觉对象信息。 如果在查找定位点时遇到问题,则可创建一个定位点,然后在不同角度和光照条件下多次定位,这样做可能有用。

如果按标识符查找云空间定位点,可以将云空间定位点标识符存储在应用程序的后端服务中,并使可供能够对其进行正确身份验证的所有设备访问。 有关示例,请参阅教程:跨设备共享空间定位点

实例化 AnchorLocateCriteria 对象,设置要查找的标识符,并提供 AnchorLocateCriteria 在会话中调用 CreateWatcher 方法。

详细了解 createWatcher 方法。

    let criteria = ASAAnchorLocateCriteria()!
    criteria.identifiers = [ "id1", "id2", "id3" ]
    _cloudSession!.createWatcher(criteria)

创建观察程序后,将为所请求的每个定位点触发 AnchorLocated 事件。 找到定位点或无法找到定位点时都会触发此事件。 如果发生这种情况,将在状态中说明原因。 在处理完观察程序的所有定位点(找到或未找到)后,将触发 LocateAnchorsCompleted 事件。 每个观察程序有 35 个标识符的限制。

详细了解 anchorLocated 协议方法。

    internal func anchorLocated(_ cloudSession: ASACloudSpatialAnchorSession!, _ args: ASAAnchorLocatedEventArgs!) {
        let status = args.status
        switch (status) {
        case ASALocateAnchorStatus.located:
            let foundAnchor = args.anchor
            // Go add your anchor to the scene...
            break
        case ASALocateAnchorStatus.alreadyTracked:
            // This anchor has already been reported and is being tracked
            break
        case ASALocateAnchorStatus.notLocatedAnchorDoesNotExist:
            // The anchor was deleted or never existed in the first place
            // Drop it, or show UI to ask user to anchor the content anew
            break
        case ASALocateAnchorStatus.notLocated:
            // The anchor hasn't been found given the location data
            // The user might in the wrong location, or maybe more data will help
            // Show UI to tell user to keep looking around
            break
        }
    }

删除定位点

删除不再使用的定位点是要尽早包括在开发流程和做法中的不错做法,可使 Azure 资源保持清洁状态。

详细了解 delete 方法。

    _cloudSession?.delete(cloudAnchor!, withCompletionHandler: { (error: Error?) in
        // Perform any processing you may want when delete finishes
    })

暂停、重置或停止会话

若要暂时停止会话,可以调用 Stop()。 这样做会停止所有观察程序和环境处理,即使调用 ProcessFrame() 也是如此。 然后,可以调用 Start() 以恢复处理。 恢复时,将保留已在会话中捕获的环境数据。

详细了解 stop 方法。

    _cloudSession!.stop()

若要重置已在会话中捕获的环境数据,可以调用 Reset()

详细了解 reset 方法。

    _cloudSession!.reset()

若要在会话后进行适当清理,请释放所有引用。

    _cloudSession = nil

后续步骤

本指南介绍了如何使用 Azure 空间定位点 SDK 创建和定位定位点。 若要详细了解定位点关系,请继续阅读下一指南。