如何在 Java 中使用 Azure Spatial Anchors 建立和尋找錨點

Azure Spatial Anchors 可讓您在不同裝置之間共用世界錨點。 它支持數個不同的開發環境。 在本文中,我們將探討如何使用 Java 中的 Azure Spatial Anchors SDK 來:

  • 正確設定和管理 Azure Spatial Anchors 會話。
  • 在本機錨點上建立和設定屬性。
  • 將它們上傳至雲端。
  • 找出並刪除雲端空間錨點。

必要條件

若要完成本指南,請確定您:

Cross Platform

初始化會話

SDK 的主要進入點是代表您工作階段的類別。 您通常會在類別中宣告一個字段,以管理您的檢視和原生AR會話。

深入瞭解 CloudSpatialAnchorSession 類別。

    private CloudSpatialAnchorSession mCloudSession;
    // In your view handler
    mCloudSession = new CloudSpatialAnchorSession();

設定驗證

若要存取服務,您必須提供帳戶密鑰、存取令牌或 Microsoft Entra 驗證令牌。 您也可以在 [驗證概念] 頁面中深入瞭解這一點。

帳戶金鑰

帳戶金鑰是一種認證,可讓您的應用程式向 Azure Spatial Anchors 服務進行驗證。 帳戶金鑰的用途是協助您快速開始使用。 特別是在應用程式與 Azure Spatial Anchors 整合的開發階段。 因此,您可以在開發期間將其內嵌在用戶端應用程式中,以使用帳戶密鑰。 在開發之後進行時,強烈建議您移至生產層級、存取令牌支援或 Microsoft Entra 使用者驗證的驗證機制。 若要取得用於開發的帳戶密鑰,請流覽您的 Azure Spatial Anchors 帳戶,並流覽至 [金鑰] 索引卷標。

深入瞭解 SessionConfiguration 類別。

    mCloudSession.getConfiguration().setAccountKey("MyAccountKey");

存取權杖

存取令牌是使用 Azure Spatial Anchors 進行驗證的更健全方法。 特別是當您準備應用程式以進行生產環境部署時。 此方法的摘要是設定用戶端應用程式可以安全地進行驗證的後端服務。 您的後端服務介面在運行時間與 AAD,以及 Azure Spatial Anchors Secure Token Service 來要求存取令牌。 此令牌接著會傳遞至用戶端應用程式,並在 SDK 中用來向 Azure Spatial Anchors 進行驗證。

    mCloudSession.getConfiguration().setAccessToken("MyAccessToken");

如果未設定存取令牌,您必須處理 TokenRequired 事件,或在委派通訊協議上實 tokenRequired 作 方法。

您可以在事件自變數上設定 屬性,以同步處理事件。

深入瞭解 TokenRequiredListener 介面。

    mCloudSession.addTokenRequiredListener(args -> {
        args.setAccessToken("MyAccessToken");
    });

如果您需要在處理程式中執行異步工作,您可以藉由要求 deferral 對象,然後完成令牌來延遲設定令牌,如下列範例所示。

    mCloudSession.addTokenRequiredListener(args -> {
        CloudSpatialAnchorSessionDeferral deferral = args.getDeferral();
        MyGetTokenAsync(myToken -> {
            if (myToken != null) args.setAccessToken(myToken);
            deferral.complete();
        });
    });

Microsoft Entra 驗證

Azure Spatial Anchors 也允許應用程式向使用者 Microsoft Entra ID (Active Directory) 令牌進行驗證。 例如,您可以使用 Microsoft Entra 令牌與 Azure Spatial Anchors 整合。 如果 Enterprise 在 Microsoft Entra ID 中維護使用者,您可以在 Azure Spatial Anchors SDK 中提供使用者 Microsoft Entra 令牌。 這麼做可讓您直接向屬於相同 Microsoft Entra 租使用者一部分的帳戶驗證 Azure Spatial Anchors 服務。

    mCloudSession.getConfiguration().setAuthenticationToken("MyAuthenticationToken");

如同存取令牌,如果未設定 Microsoft Entra 令牌,您必須處理 TokenRequired 事件,或在委派通訊協定上實作 tokenRequired 方法。

您可以在事件自變數上設定 屬性,以同步處理事件。

    mCloudSession.addTokenRequiredListener(args -> {
        args.setAuthenticationToken("MyAuthenticationToken");
    });

如果您需要在處理程式中執行異步工作,您可以藉由要求 deferral 對象,然後完成令牌來延遲設定令牌,如下列範例所示。

    mCloudSession.addTokenRequiredListener(args -> {
        CloudSpatialAnchorSessionDeferral deferral = args.getDeferral();
        MyGetTokenAsync(myToken -> {
            if (myToken != null) args.setAuthenticationToken(myToken);
            deferral.complete();
        });
    });

設定會話

Start() 用 以讓您的工作階段處理環境數據。

若要處理會話所引發的事件,請附加事件處理程式。

    mCloudSession.setSession(mSession);
    mCloudSession.start();

提供會話的畫面格

空間錨點會話的運作方式是對應用戶周圍的空間。 這樣做有助於判斷錨點所在的位置。 行動平臺 (iOS 和 Android) 需要原生呼叫相機摘要,才能從您平臺的 AR 連結庫取得畫面。 相反地,HoloLens 會持續掃描環境,因此不需要像行動平臺上這樣的特定呼叫。

    mCloudSession.processFrame(mSession.update());

提供意見反應給使用者

您可以撰寫程式代碼來處理工作階段更新的事件。 每次會話改善您對周圍環境的理解時,就會引發此事件。 這樣做可讓您:

  • 使用 類別 UserFeedback ,在裝置移動時向使用者提供意見反應,而會話會更新其環境瞭解。 若要這樣做:
  • 判斷哪些時間點有足夠的追蹤空間數據來建立空間錨點。 您可以使用 或 RecommendedForCreateProgress來判斷這一ReadyForCreateProgress點。 一旦 ReadyForCreateProgress 超過 1,我們有足夠的數據可儲存雲端空間錨點,不過我們建議您等 RecommendedForCreateProgress 到 1 以上才能這麼做。

深入瞭解 SessionUpdatedListener 介面。

    mCloudSession.addSessionUpdatedListener(args -> {
        auto status = args->Status();
        if (status->UserFeedback() == SessionUserFeedback::None) return;
        NumberFormat percentFormat = NumberFormat.getPercentInstance();
        percentFormat.setMaximumFractionDigits(1);
        mFeedback = String.format("Feedback: %s - Recommend Create=%s",
            FeedbackToString(status.getUserFeedback()),
            percentFormat.format(status.getRecommendedForCreateProgress()));
    });

建立雲端空間錨點

若要建立雲端空間錨點,請先在平臺的AR系統中建立錨點,然後建立雲端對應專案。 您可以使用 CreateAnchorAsync() 方法。

深入瞭解 CloudSpatialAnchor 類別。

    // Create a local anchor, perhaps by hit-testing and creating an ARAnchor
    Anchor localAnchor = null;
    List<HitResult> hitResults = mSession.update().hitTest(0.5f, 0.5f);
    for (HitResult hit : hitResults) {
        Trackable trackable = hit.getTrackable();
        if (trackable instanceof Plane) {
            if (((Plane) trackable).isPoseInPolygon(hit.getHitPose())) {
                localAnchor = hit.createAnchor();
                break;
            }
        }
    }

    // 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.
    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(localAnchor);
    Future createAnchorFuture = mCloudSession.createAnchorAsync(cloudAnchor);
    CheckForCompletion(createAnchorFuture, cloudAnchor);

    // ...

    private void CheckForCompletion(Future createAnchorFuture, CloudSpatialAnchor cloudAnchor) {
        new android.os.Handler().postDelayed(() -> {
            if (createAnchorFuture.isDone()) {
                try {
                    createAnchorFuture.get();
                    mFeedback = String.format("Created a cloud anchor with ID=%s", cloudAnchor.getIdentifier());
                }
                catch(InterruptedException e) {
                    mFeedback = String.format("Save Failed:%s", e.getMessage());
                }
                catch(ExecutionException e) {
                    mFeedback = String.format("Save Failed:%s", e.getMessage());
                }
            }
            else {
                CheckForCompletion(createAnchorFuture, cloudAnchor);
            }
        }, 500);
    }

如先前所述,您必須先擷取足夠的環境數據,才能嘗試建立新的雲端空間錨點。 這表示 ReadyForCreateProgress 必須高於 1,不過我們建議您等到 RecommendedForCreateProgress 1 以上才能這麼做。

    Future<SessionStatus> sessionStatusFuture = mCloudSession.getSessionStatusAsync();
    CheckForCompletion(sessionStatusFuture);

    // ...

    private void CheckForCompletion(Future<SessionStatus> sessionStatusFuture) {
        new android.os.Handler().postDelayed(() -> {
            if (sessionStatusFuture.isDone()) {
                try {
                    SessionStatus value = sessionStatusFuture.get();
                    if (value.getRecommendedForCreateProgress() < 1.0f) return;
                    // Issue the creation request...
                }
                catch(InterruptedException e) {
                    mFeedback = String.format("Session status error:%s", e.getMessage());
                }
                catch(ExecutionException e) {
                    mFeedback = String.format("Session status error:%s", e.getMessage());
                }
            }
            else {
                CheckForCompletion(sessionStatusFuture);
            }
        }, 500);
    }

設定屬性

當您儲存雲端空間錨點時,您可以選擇新增一些屬性。 如同要儲存的物件類型,或基本屬性,例如是否應該啟用互動。 這樣做在探索時很有用:您可以立即為用戶轉譯物件,例如具有空白內容的圖片框。 然後,背景中的不同下載會取得其他狀態詳細數據,例如要顯示在框架中的圖片。

    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(localAnchor);
    Map<String,String> properties = cloudAnchor.getAppProperties();
    properties.put("model-type", "frame");
    properties.put("label", "my latest picture");
    Future createAnchorFuture = mCloudSession.createAnchorAsync(cloudAnchor);
    // ...

更新屬性

若要更新錨點上的屬性,您可以使用 UpdateAnchorProperties() 方法。 如果兩個或多個裝置同時嘗試更新相同錨點的屬性,我們會使用開放式並行存取模型。 這表示第一次寫入將會獲勝。 所有其他寫入都會收到「並行」錯誤:需要重新整理屬性,然後再試一次。

    CloudSpatialAnchor anchor = /* locate your anchor */;
    anchor.getAppProperties().put("last-user-access", "just now");
    Future updateAnchorPropertiesFuture = mCloudSession.updateAnchorPropertiesAsync(anchor);
    CheckForCompletion(updateAnchorPropertiesFuture);

    // ...

    private void CheckForCompletion(Future updateAnchorPropertiesFuture) {
        new android.os.Handler().postDelayed(() -> {
            if (updateAnchorPropertiesFuture.isDone()) {
                try {
                    updateAnchorPropertiesFuture.get();
                }
                catch(InterruptedException e) {
                    mFeedback = String.format("Updating Properties Failed:%s", e.getMessage());
                }
                catch(ExecutionException e) {
                    mFeedback = String.format("Updating Properties Failed:%s", e.getMessage());
                }
            }
            else {
                CheckForCompletion1(updateAnchorPropertiesFuture);
            }
        }, 500);
    }

在服務上建立錨點之後,您無法更新錨點的位置 - 您必須建立新的錨點,並刪除舊的錨點以追蹤新位置。

如果您不需要找到錨點來更新其屬性,您可以使用 GetAnchorPropertiesAsync() 方法,這個方法會 CloudSpatialAnchor 傳回具有屬性的物件。

    Future<CloudSpatialAnchor> getAnchorPropertiesFuture = mCloudSession.getAnchorPropertiesAsync("anchorId");
    CheckForCompletion(getAnchorPropertiesFuture);

    // ...

    private void CheckForCompletion(Future<CloudSpatialAnchor> getAnchorPropertiesFuture) {
        new android.os.Handler().postDelayed(() -> {
            if (getAnchorPropertiesFuture.isDone()) {
                try {
                    CloudSpatialAnchor anchor = getAnchorPropertiesFuture.get();
                    if (anchor != null) {
                        anchor.getAppProperties().put("last-user-access", "just now");
                        Future updateAnchorPropertiesFuture = mCloudSession.updateAnchorPropertiesAsync(anchor);
                        // ...
                    }
                } catch (InterruptedException e) {
                    mFeedback = String.format("Getting Properties Failed:%s", e.getMessage());
                } catch (ExecutionException e) {
                    mFeedback = String.format("Getting Properties Failed:%s", e.getMessage());
                }
            } else {
                CheckForCompletion(getAnchorPropertiesFuture);
            }
        }, 500);
    }

設定到期日

您也可以將錨點設定為在未來的指定日期自動到期。 當錨點到期時,它將不再找到或更新。 只有在建立錨點時,才能設定到期日,再將它儲存至雲端。 之後無法更新到期日。 如果在建立錨點期間未設定到期,錨點只會在手動刪除時到期。

    Date now = new Date();
    Calendar cal = Calendar.getInstance();
    cal.setTime(now);
    cal.add(Calendar.DATE, 7);
    Date oneWeekFromNow = cal.getTime();
    cloudAnchor.setExpiration(oneWeekFromNow);

找出雲端空間錨點

能夠找出先前儲存的雲端空間錨點,是使用 Azure Spatial Anchors 的主要原因之一。 為此,我們使用「監看員」。 您一次只能使用一個監看員;不支援多個監看員。 監看員可以找到雲端空間錨點,有數種不同的方式(也稱為 錨點尋找策略)。 您可以一次在監看員上使用一個策略。

注意

每次找到錨點時,Azure Spatial Anchors 都會嘗試使用收集的環境數據來增強錨點上的視覺資訊。 如果您在尋找錨點時遇到問題,建立錨點可能會很有用,然後從不同的角度和光源條件找出它數次。

如果您要依標識碼尋找雲端空間錨點,您可以將雲端空間錨點標識符儲存在應用程式的後端服務中,並讓所有可正確向它驗證的裝置存取。 如需此範例,請參閱 教學課程:跨裝置共享空間錨點。

具現化AnchorLocateCriteria對象、設定您要尋找的標識碼,並提供 您的 AnchorLocateCriteria,並在會話上叫CreateWatcher用 方法。

    AnchorLocateCriteria criteria = new AnchorLocateCriteria();
    criteria.setIdentifiers(new String[] { "id1", "id2", "id3" });
    mCloudSession.createWatcher(criteria);

建立監看員之後, AnchorLocated 每個要求的錨點都會引發事件。 當錨點位於或錨點找不到時,就會引發此事件。 如果發生這種情況,則會在狀態中說明原因。 處理監看員的所有錨點、找到或找不到之後,事件 LocateAnchorsCompleted 就會引發。 每個監看員的限制為35個標識碼。

深入瞭解 AnchorLocatedListener 介面。

    mCloudSession.addAnchorLocatedListener(args -> {
        switch (args.getStatus()) {
            case Located:
                CloudSpatialAnchor foundAnchor = args.getAnchor();
                // Go add your anchor to the scene...
                break;
            case AlreadyTracked:
                // This anchor has already been reported and is being tracked
                break;
            case 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 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 資源保持清除狀態。

    Future deleteAnchorFuture = mCloudSession.deleteAnchorAsync(cloudAnchor);
    // Perform any processing you may want when delete finishes (deleteAnchorFuture is done)

暫停、重設或停止會話

若要暫時停止工作階段,您可以叫用 Stop()。 這樣做會停止任何監看員和環境處理,即使您叫用 ProcessFrame()。 然後,您可以叫 Start() 用 以繼續處理。 繼續時,會維護會話中已擷取的環境數據。

    mCloudSession.stop();

若要重設工作階段中擷取的環境數據,您可以叫用 Reset()

    mCloudSession.reset();

若要在工作階段之後正確清除,請叫用 close()

    mCloudSession.close();

下一步

在本指南中,您已瞭解如何使用 Azure Spatial Anchors SDK 建立和尋找錨點。 若要深入瞭解錨點關聯性,請繼續進行下一個指南。