Integrate Azure Digital Twins data into an Azure Maps indoor map

This article shows how to use Azure Digital Twins data to update information displayed on an indoor map from Azure Maps. Because Azure Digital Twins stores a graph of your IoT device relationships and routes telemetry to different endpoints, it's a great service for updating informational overlays on maps.

This guide covers the following information:

  1. Configuring your Azure Digital Twins instance to send twin update events to a function in Azure Functions.
  2. Creating a function to update an Azure Maps indoor maps feature stateset.
  3. Storing your maps ID and feature stateset ID in the Azure Digital Twins graph.

Get started

This section sets additional context for the information in this article.

Prerequisites

Before proceeding with this article, start by setting up your individual Azure Digital Twins and Azure Maps resources.

  • For Azure Digital Twins: Follow the instructions in Connect an end-to-end solution to set up an Azure Digital Twins instance with a sample twin graph and simulated data flow.
    • In this article, you'll extend that solution with another endpoint and route. You'll also add another function to the function app from that tutorial.
  • For Azure Maps: Follow the instructions in Use Creator to create indoor maps and Create a feature stateset to create an Azure Maps indoor map with a feature stateset.
    • Feature statesets are collections of dynamic properties (states) assigned to dataset features such as rooms or equipment. In the Azure Maps instructions above, the feature stateset stores room status that you'll be displaying on a map.
    • You'll need your Azure Maps subscription key, feature stateset ID, and mapConfiguration.

Topology

The image below illustrates where the indoor maps integration elements in this tutorial fit into a larger, end-to-end Azure Digital Twins scenario.

Diagram of Azure services in an end-to-end scenario, highlighting the Indoor Maps Integration piece.

Route twin update notifications from Azure Digital Twins

Azure Digital Twins instances can emit twin update events whenever a twin's state is updated. The Azure Digital Twins Connect an end-to-end solution linked above walks through a scenario where a thermometer is used to update a temperature attribute attached to a room's twin. This tutorial extends that solution by subscribing an Azure function to update notifications from twins, and using that function to update your maps.

This pattern reads from the room twin directly, rather than the IoT device, which gives you the flexibility to change the underlying data source for temperature without needing to update your mapping logic. For example, you can add multiple thermometers or set this room to share a thermometer with another room, all without needing to update your map logic.

First, you'll create a route in Azure Digital Twins to forward all twin update events to an Event Grid topic.

  1. Create an Event Grid topic, which will receive events from your Azure Digital Twins instance, using the CLI command below:

    az eventgrid topic create --resource-group <your-resource-group-name> --name <your-topic-name> --location <region>
    
  2. Create an endpoint to link your Event Grid topic to Azure Digital Twins, using the CLI command below:

    az dt endpoint create eventgrid --endpoint-name <Event-Grid-endpoint-name> --eventgrid-resource-group <Event-Grid-resource-group-name> --eventgrid-topic <your-Event-Grid-topic-name> --dt-name <your-Azure-Digital-Twins-instance-name>
    
  3. Create a route in Azure Digital Twins to send twin update events to your endpoint, using the CLI command below. For the Azure Digital Twins instance name placeholder in this command, you can use the friendly name or the host name for a boost in performance.

    Note

    There is currently a known issue in Cloud Shell affecting these command groups: az dt route, az dt model, az dt twin.

    To resolve, either run az login in Cloud Shell prior to running the command, or use the local CLI instead of Cloud Shell. For more detail on this, see Azure Digital Twins known issues.

    az dt route create --dt-name <your-Azure-Digital-Twins-instance-hostname-or-name> --endpoint-name <Event-Grid-endpoint-name> --route-name <my-route> --filter "type = 'Microsoft.DigitalTwins.Twin.Update'"
    

Create an Azure function to receive events and update maps

In this section, you'll create a function that listens for events sent to the Event Grid topic. The function will read those update notifications and send corresponding updates to an Azure Maps feature stateset, to update the temperature of one room.

In the Azure Digital Twins tutorial prerequisite, you created a function app to store Azure functions Azure Digital Twins. Now, create a new Event Grid-triggered Azure function inside the function app.

Replace the function code with the following code. It will filter out only updates to space twins, read the updated temperature, and send that information to Azure Maps.

using System;
using System.Threading.Tasks;
using System.Net.Http;
using Microsoft.Azure.EventGrid.Models;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.EventGrid;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace updateMaps
{
    public static class ProcessDTUpdatetoMaps
    {
        // Read maps credentials from application settings on function startup
        private static string statesetID = Environment.GetEnvironmentVariable("statesetID");
        private static string subscriptionKey = Environment.GetEnvironmentVariable("subscription-key");
        private static HttpClient httpClient = new HttpClient();

        [FunctionName("ProcessDTUpdatetoMaps")]
        public static async Task Run([EventGridTrigger]EventGridEvent eventGridEvent, ILogger log)
        {
            JObject message = (JObject)JsonConvert.DeserializeObject(eventGridEvent.Data.ToString());
            log.LogInformation($"Reading event from twinID: {eventGridEvent.Subject}: {eventGridEvent.EventType}: {message["data"]}");

            //Parse updates to "space" twins
            if (message["data"]["modelId"].ToString() == "dtmi:contosocom:DigitalTwins:Space;1")
            {
                // Set the ID of the room to be updated in your map.
                // Replace this line with your logic for retrieving featureID.
                string featureID = "UNIT103";

                // Iterate through the properties that have changed
                foreach (var operation in message["data"]["patch"])
                {
                    if (operation["op"].ToString() == "replace" && operation["path"].ToString() == "/Temperature")
                    {
                        // Update the maps feature stateset
                        var postcontent = new JObject(
                            new JProperty(
                                "States",
                                new JArray(
                                    new JObject(
                                        new JProperty("keyName", "temperature"),
                                        new JProperty("value", operation["value"].ToString()),
                                        new JProperty("eventTimestamp", DateTime.UtcNow.ToString("s"))))));

                        var response = await httpClient.PutAsync(
                            $"https://us.atlas.microsoft.com/featurestatesets/{statesetID}/featureStates/{featureID}?api-version=2.0&subscription-key={subscriptionKey}",
                            new StringContent(postcontent.ToString()));


                        log.LogInformation(await response.Content.ReadAsStringAsync());
                    }
                }
            }
        }
    }
}

You'll need to set two environment variables in your function app. One is your Azure Maps primary subscription key, and one is your Azure Maps stateset ID.

az functionapp config appsettings set --name <your-function-app-name> --resource-group <your-resource-group> --settings "subscription-key=<your-Azure-Maps-primary-subscription-key>"
az functionapp config appsettings set --name <your-function-app-name>  --resource-group <your-resource-group> --settings "statesetID=<your-Azure-Maps-stateset-ID>"

View live updates in the map

To see live-updating temperature, follow the steps below:

  1. Begin sending simulated IoT data by running the DeviceSimulator project from the Azure Digital Twins Connect an end-to-end solution. The instructions for this process are in the Configure and run the simulation section.
  2. Use the Azure Maps Indoor module to render your indoor maps created in Azure Maps Creator.
    1. Copy the example indoor map HTML file from Example: Custom Styling: Consume map configuration in WebSDK (Preview).
    2. Replace the subscription key, mapConfiguration, statesetID, and region in the local HTML file with your values.
    3. Open that file in your browser.

Both samples send temperature in a compatible range, so you should see the color of room 121 update on the map about every 30 seconds.

Screenshot of an office map showing room 121 colored orange.

Store map information in Azure Digital Twins

Now that you have a hardcoded solution to updating your maps information, you can use the Azure Digital Twins graph to store all of the information necessary for updating your indoor map. This information would include the stateset ID, maps subscription ID, and feature ID of each map and location respectively.

A solution for this specific example would involve updating each top-level space to have a stateset ID and maps subscription ID attribute, and updating each room to have a feature ID. You would need to set these values once when initializing the twin graph, then query those values for each twin update event.

Depending on the configuration of your topology, storing these three attributes at different levels correlating to the granularity of your map will be possible.

Next steps

To read more about managing, upgrading, and retrieving information from the twins graph, see the following references: