How to create and locate anchors using Azure Spatial Anchors in C++/WinRT

Azure Spatial Anchors allow you to share anchors in the world between different devices. It supports several different development environments. In this article, we'll dive into how to use the Azure Spatial Anchors SDK, in C++/WinRT, to:

  • Correctly set up and manage an Azure Spatial Anchors session.
  • Create and set properties on local anchors.
  • Upload them to the cloud.
  • Locate and delete cloud spatial anchors.

Prerequisites

To complete this guide, make sure you have:

Cross Platform

Initialize the session

The main entry point for the SDK is the class representing your session. Typically you'll declare a field in the class that manages your view and native AR session.

Learn more about the CloudSpatialAnchorSession class.

    CloudSpatialAnchorSession m_cloudSession{ nullptr };

    m_cloudSession = CloudSpatialAnchorSession();

Set up authentication

To access the service, you need to provide an account key, access token, or Microsoft Entra auth token. You can also read more about this in the Authentication concept page.

Account Keys

Account Keys are a credential that allows your application to authenticate with the Azure Spatial Anchors service. The intended purpose of Account Keys is to help you get started quickly. Especially during the development phase of your application's integration with Azure Spatial Anchors. As such, you can use Account Keys by embedding them in your client applications during development. As you progress beyond development, it's highly recommended to move to an authentication mechanism that is production-level, supported by Access Tokens, or Microsoft Entra user authentication. To get an Account Key for development, visit your Azure Spatial Anchors account, and navigate to the "Keys" tab.

Learn more about the SessionConfiguration class.

    auto configuration = m_cloudSession.Configuration();
    configuration.AccountKey(LR"(MyAccountKey)");

Access Tokens

Access Tokens are a more robust method to authenticate with Azure Spatial Anchors. Especially as you prepare your application for a production deployment. The summary of this approach is to set up a back-end service that your client application can securely authenticate with. Your back-end service interfaces with AAD at runtime and with the Azure Spatial Anchors Secure Token Service to request an Access Token. This token is then delivered to the client application and used in the SDK to authenticate with Azure Spatial Anchors.

    auto configuration = m_cloudSession.Configuration();
    configuration.AccessToken(LR"(MyAccessToken)");

If an access token isn't set, you must handle the TokenRequired event, or implement the tokenRequired method on the delegate protocol.

You can handle the event synchronously by setting the property on the event arguments.

Learn more about the TokenRequiredDelegate delegate.

    m_accessTokenRequiredToken = m_cloudSession.TokenRequired(winrt::auto_revoke, [](auto&&, auto&& args) {
        args.AccessToken(LR"(MyAccessToken)");
    });

If you need to execute asynchronous work in your handler, you can defer setting the token by requesting a deferral object and then completing it, as in the following example.

    m_accessTokenRequiredToken = m_cloudSession.TokenRequired(winrt::auto_revoke, [this](auto&&, auto&& args) {
        auto deferral = args.GetDeferral();
        MyGetTokenAsync([&deferral, &args](winrt::hstring const& myToken) {
            if (!myToken.empty()) args.AccessToken(myToken);
            deferral.Complete();
        });
    });

Microsoft Entra authentication

Azure Spatial Anchors also allows applications to authenticate with user Microsoft Entra ID (Active Directory) tokens. For example, you can use Microsoft Entra tokens to integrate with Azure Spatial Anchors. If an Enterprise maintains users in Microsoft Entra ID, you can supply a user Microsoft Entra token in the Azure Spatial Anchors SDK. Doing so allows you to authenticate directly to the Azure Spatial Anchors service for an account that's part of the same Microsoft Entra tenant.

    auto configuration = m_cloudSession.Configuration();
    configuration.AuthenticationToken(LR"(MyAuthenticationToken)");

Like with access tokens, if a Microsoft Entra token isn't set, you must handle the TokenRequired event, or implement the tokenRequired method on the delegate protocol.

You can handle the event synchronously by setting the property on the event arguments.

    m_accessTokenRequiredToken = m_cloudSession.TokenRequired(winrt::auto_revoke, [](auto&&, auto&& args) {
        args.AuthenticationToken(LR"(MyAuthenticationToken)");
    });

If you need to execute asynchronous work in your handler, you can defer setting the token by requesting a deferral object and then completing it, as in the following example.

    m_accessTokenRequiredToken = m_cloudSession.TokenRequired(winrt::auto_revoke, [this](auto&&, auto&& args) {
        auto deferral = args.GetDeferral();
        MyGetTokenAsync([&deferral, &args](winrt::hstring const& myToken) {
            if (!myToken.empty()) args.AuthenticationToken(myToken);
            deferral.Complete();
        });
    });

Set up the session

Invoke Start() to enable your session to process environment data.

To handle events raised by your session, attach an event handler.

Learn more about the Start method.

    m_cloudSession.Start();

Provide frames to the session

The spatial anchor session works by mapping the space around the user. Doing so helps to determine where anchors are located. Mobile platforms (iOS & Android) require a native call to the camera feed to obtain frames from your platform's AR library. In contrast, HoloLens is constantly scanning the environment, so there's no need for a specific call like on mobile platforms.

Learn more about the ProcessFrame method.

    m_cloudSession->ProcessFrame(ar_frame_);

Provide feedback to the user

You can write code to handle the session updated event. This event fires every time the session improves its understanding of your surroundings. Doing so, allows you to:

  • Use the UserFeedback class to provide feedback to the user as the device moves and the session updates its environment understanding. To do this,
  • Determine at what point there's enough tracked spatial data to create spatial anchors. You determine this with either ReadyForCreateProgress or RecommendedForCreateProgress. Once ReadyForCreateProgress is above 1, we have enough data to save a cloud spatial anchor, though we recommend you wait until RecommendedForCreateProgress is above 1 to do so.

Learn more about the SessionUpdatedDelegate delegate.

    m_sessionUpdatedToken = m_cloudSession.SessionUpdated(winrt::auto_revoke, [this](auto&&, auto&& args)
    {
        auto status = args.Status();
        if (status.UserFeedback() == SessionUserFeedback::None) return;
        m_feedback = LR"(Feedback: )" + FeedbackToString(status.UserFeedback()) + LR"( -)" +
            LR"( Recommend Create=)" + FormatPercent(status.RecommendedForCreateProgress() * 100.f);
    });

Create a cloud spatial anchor

To create a cloud spatial anchor, you first create an anchor in your platform's AR system, and then create a cloud counterpart. You use the CreateAnchorAsync() method.

Learn more about the CloudSpatialAnchor class.

    // Initialization
    SpatialStationaryFrameOfReference m_stationaryReferenceFrame = nullptr;
    HolographicDisplay defaultHolographicDisplay = HolographicDisplay::GetDefault();
    SpatialLocator spatialLocator = defaultHolographicDisplay.SpatialLocator();
    m_stationaryReferenceFrame = spatialLocator.CreateStationaryFrameOfReferenceAtCurrentLocation();

    // Create a local anchor, perhaps by positioning a SpatialAnchor a few meters in front of the user
    SpatialAnchor localAnchor{ nullptr };
    PerceptionTimestamp timestamp = PerceptionTimestampHelper::FromHistoricalTargetTime(DateTime::clock::now());
    SpatialCoordinateSystem currentCoordinateSystem = m_attachedReferenceFrame.GetStationaryCoordinateSystemAtTimestamp(timestamp);
    SpatialPointerPose pose = SpatialPointerPose::TryGetAtTimestamp(currentCoordinateSystem, timestamp);

    // Get the gaze direction relative to the given coordinate system.
    const float3 headPosition = pose.Head().Position();
    const float3 headDirection = pose.Head().ForwardDirection();

    // The anchor is positioned two meter(s) along the user's gaze direction.
    constexpr float distanceFromUser = 2.0f; // meters
    const float3 gazeAtTwoMeters = headPosition + (distanceFromUser * headDirection);

    localAnchor = SpatialAnchor::TryCreateRelativeTo(currentCoordinateSystem, gazeAtTwoMeters);

    // 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 = CloudSpatialAnchor();
    cloudAnchor.LocalAnchor(localAnchor);
    co_await m_cloudSession.CreateAnchorAsync(cloudAnchor);
    m_feedback = LR"(Created a cloud anchor with ID=)" + cloudAnchor.Identifier();

As described earlier, you need sufficient environment data captured before trying to create a new cloud spatial anchor. That means ReadyForCreateProgress has to be above 1, though we recommend you wait until RecommendedForCreateProgress is above 1 to do so.

Learn more about the GetSessionStatusAsync method.

    SessionStatus status = co_await m_cloudSession.GetSessionStatusAsync();
    if (value.RecommendedForCreateProgress() < 1.0f) return;
    // Issue the creation request ...

Set properties

You may choose to add some properties when saving your cloud spatial anchors. Like the type of object being saved, or basic properties like whether it should be enabled for interaction. Doing so can be useful upon discovery: you can immediately render the object for the user, for example a picture frame with blank content. Then, a different download in the background gets additional state details, for example, the picture to display in the frame.

Learn more about the AppProperties method.

    CloudSpatialAnchor cloudAnchor = CloudSpatialAnchor();
    cloudAnchor.LocalAnchor(localAnchor);
    auto properties = m_cloudAnchor.AppProperties();
    properties.Insert(LR"(model-type)", LR"(frame)");
    properties.Insert(LR"(label)", LR"(my latest picture)");
    co_await m_cloudSession.CreateAnchorAsync(cloudAnchor);

Update properties

To update the properties on an anchor, you use the UpdateAnchorProperties() method. If two or more devices try to update properties for the same anchor at the same time, we use an optimistic concurrency model. Which means that the first write will win. All other writes will get a "Concurrency" error: a refresh of the properties would be needed before trying again.

Learn more about the UpdateAnchorPropertiesAsync method.

    CloudSpatialAnchor anchor = /* locate your anchor */;
    anchor.AppProperties().Insert(LR"(last-user-access)", LR"(just now)");
    co_await m_cloudSession.UpdateAnchorPropertiesAsync(anchor);

You can't update the location of an anchor once it has been created on the service - you must create a new anchor and delete the old one to track a new position.

If you don't need to locate an anchor to update its properties, you can use the GetAnchorPropertiesAsync() method, which returns a CloudSpatialAnchor object with properties.

Learn more about the GetAnchorPropertiesAsync method.

    CloudSpatialAnchor anchor = co_await m_cloudSession.GetAnchorPropertiesAsync(LR"(anchorId)");
    if (anchor != nullptr)
    {
        anchor.AppProperties().Insert(LR"(last-user-access)", LR"(just now)");
        co_await m_cloudSession.UpdateAnchorPropertiesAsync(anchor);
    }

Set expiration

It's also possible to configure your anchor to expire automatically at a given date in the future. When an anchor expires, it will no longer be located or updated. Expiration can only be set when the anchor is created, before saving it to the cloud. Updating expiration afterwards isn't possible. If no expiration is set during anchor creation, the anchor will only expire when deleted manually.

Learn more about the Expiration method.

    const int64_t oneWeekFromNowInHours = 7 * 24;
    const DateTime oneWeekFromNow = DateTime::clock::now() + std::chrono::hours(oneWeekFromNowInHours);
    cloudAnchor.Expiration(oneWeekFromNow);

Locate a cloud spatial anchor

Being able to locate a previously saved cloud spatial anchor is one of the main reasons for using Azure Spatial Anchors. For this, we're using "Watchers". You can only use one Watcher at a time; multiple Watchers are not supported. There are several different ways (also known as Anchor Locate Strategies) a Watcher can locate a cloud spatial anchor. You can use one strategy on a watcher at a time.

  • Locate anchors by identifier.
  • Locate anchors connected to a previously located anchor. You can learn about anchor relationships here.
  • Locate anchor using Coarse relocalization.

Note

Each time you locate an anchor, Azure Spatial Anchors will attempt to use the environment data collected to augment the visual information on the anchor. If you are having trouble locating an anchor, it can be useful to create an anchor, then locate it several times from different angles and lighting conditions.

If you're locating cloud spatial anchors by identifier, you can store the cloud spatial anchor identifier in your application's back-end service, and make it accessible to all devices that can properly authenticate to it. For an example of this, see Tutorial: Share Spatial Anchors across devices.

Instantiate an AnchorLocateCriteria object, set the identifiers you're looking for, and invoke the CreateWatcher method on the session by providing your AnchorLocateCriteria.

Learn more about the CreateWatcher method.

    AnchorLocateCriteria criteria = AnchorLocateCriteria();
    criteria.Identifiers({ LR"(id1)", LR"(id2)", LR"(id3)" });
    auto cloudSpatialAnchorWatcher = m_cloudSession.CreateWatcher(criteria);

After your watcher is created, the AnchorLocated event will fire for every anchor requested. This event fires when an anchor is located, or if the anchor can't be located. If this situation happens, the reason will be stated in the status. After all anchors for a watcher are processed, found or not found, then the LocateAnchorsCompleted event will fire. There is a limit of 35 identifiers per watcher.

Learn more about the AnchorLocatedDelegate delegate.

    m_anchorLocatedToken = m_cloudSession.AnchorLocated(winrt::auto_revoke, [this](auto&&, auto&& args)
    {
        switch (args.Status())
        {
            case LocateAnchorStatus::Located:
            {
                CloudSpatialAnchor foundAnchor = args.Anchor();
            }
                break;
            case LocateAnchorStatus::AlreadyTracked:
                // This anchor has already been reported and is being tracked
                break;
            case LocateAnchorStatus::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 LocateAnchorStatus::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;
        }
    });

Delete anchors

Deleting anchors when no longer used is a good practice to include early on in your development process and practices, to keep your Azure resources cleaned up.

Learn more about the DeleteAnchorAsync method.

    co_await m_cloudSession.DeleteAnchorAsync(cloudAnchor);
    // Perform any processing you may want when delete finishes

Pause, reset, or stop the session

To stop the session temporarily, you can invoke Stop(). Doing so will stop any watchers and environment processing, even if you invoke ProcessFrame(). You can then invoke Start() to resume processing. When resuming, environment data already captured in the session is maintained.

Learn more about the Stop method.

    m_cloudSession.Stop();

To reset the environment data that has been captured in your session, you can invoke Reset().

Learn more about the Reset method.

    m_cloudSession.Reset();

To clean up properly after a session release all references.

    m_cloudSession = nullptr;

Next steps

In this guide, you learned about how to create and locate anchors using the Azure Spatial Anchors SDK. To learn more about anchor relationships, continue to the next guide.