Have you every wanted to create a site that is similar to Flightradar24? If so, you have come to the right place.

Microsoft recently announced a free tier version of their NoSql offering, Azure Cosmos DB. With Cosmos DB being of interest to me and having seen another Flight Tracker project, it seemed the perfect opportunity to try out the free tier. Along with Azure Functions and SignalR, giving the functionality to build real-time applications and that Cosmos DB also provides another great feature called Change Feed, the Flight Tracker seemed an obvious decision.

signalr-cosmosdb-functions
An example of how to use the SignalR Service bindings is using Azure Functions to integrate with Azure Cosmos DB and SignalR Service to send real-time messages when new events appear on a Cosmos DB change feed – Build real-time Apps with Azure Functions and Azure SignalR Service

A key component of this solution is the flight data. There is a non-profit association based in Switzerland called the OpenSky Network and they provide a live API that lets you retrieve live airspace information for research and non-commercial purposes. An overview of the steps involved in this solution looks like this:

  1. An Azure Function polls the OpenSky Network API every 5 seconds to retrieve flight data and persists the returned results in the Cosmos DB collection.
  2. The change event from each document is propagated to the Cosmos DB change feed.
  3. An Azure Functions is triggered by the change event using the Cosmos DB trigger.
  4. The SignalR Service output binding publishes a message containing the flight data to SignalR Service.
  5. SignalR Service publishes the message to all connected clients.

Retrieving the data from the OpenSky Network

The OpenSky Network provides a live REST API and documentation to go along with it. For this, a HttpClient can be used for a GET request to retrieve data within a certain bounding box. In this case, the UK.

await client.GetAsync("https://opensky-network.org/api/states/all?lamin=49.9599&lomin=-7.5721&lamax=58.6350&lomax=1.6815

To write each element to the Cosmos DB, you could in code create a CosmosClient, then get the database followed by the collection. Azure Functions though provide an Azure Cosmos DB trigger and binding which does a lot of the work for you. Just simply create an output binding like the following:

[CosmosDB(
    databaseName: "flighttracker",
    collectionName: "flights",
    ConnectionStringSetting = "CosmosDbConnectionString")] IAsyncCollector<Flight> documents

This binding takes care of all the code needed to interact with the Cosmos DB collection. So to create a document in the “flights” collection, it is a very simple line of code as follows where “flight” is an individual object contains information from the OpenSky API:

await documents.AddAsync(flight);

Cosmos DB Change Feed

Change feed support in Azure Cosmos DB works by listening to an Azure Cosmos container for any changes. It then outputs the sorted list of documents that were changed in the order in which they were modified. The changes are persisted, can be processed asynchronously and incrementally, and the output can be distributed across one or more consumers for parallel processing.

https://docs.microsoft.com/en-us/azure/cosmos-db/change-feed

A new Azure Function is used to process the documents from the Change Feed and will use the Azure Cosmos DB trigger.

[CosmosDBTrigger(
    databaseName: "flighttracker",
    collectionName: "flights",
    ConnectionStringSetting = "CosmosDbConnectionString",
    LeaseCollectionName = "leases",
    CreateLeaseCollectionIfNotExists = true)] IReadOnlyList<Document> documents

SignalR

SignalR allows you to broadcast messages to all connected clients. A “hub” is created to which clients connected to, and when a message is placed on the hub, it is received by all clients. This is what gives the real-time element. Given the list of documents received from the Cosmos DB Change Feed, the list can be iterated through and a new message placed on the hub using the Signal R output binding for Azure Functions.

[SignalR(HubName = "flightdata")] IAsyncCollector<SignalRMessage> signalRMessages
foreach (var document in documents)
{
    await signalRMessages.AddAsync(
    new SignalRMessage
    {
        Target = "newFlightData",
        Arguments = new[] { document }
    });
}

Displaying the Data

All that is left to do is display the data to the users. Azure Maps was used but that is probably a post on it’s own as we are concentrating on the real-time aspect here. The first time to do, is get the connection information that will give a connection to the SignalR service. This is done via a call to an Azure Function to ‘negotiate’ the connection. This function returns a service access token and an endpoint.

function getConnectionInfo() {
    return axios.get(baseUrl + '/api/negotiate')
        .then(response => { return response.data })
        .catch(console.error)
}

With the access toke and endpoint is returned, a connection object can be created followed by starting the connection.

const connection = new signalR.HubConnectionBuilder()
    .withUrl(info.url, options)
    .configureLogging(signalR.LogLevel.Information)
    .build()

connection.start()
    .then(() => { console.log('connected!') })
    .catch(error => {
        console.error(error)
        setTimeout(() => { startConnection(connection) }, 2000)
})

To receive messages from the hub, define a method using the on method of the HubConnection.

connection.on('newFlightData', processFlightData)

If you remember from the Azure Function that places messages on the Hub, a Target property was included. So whenever the client receives a message with a target of “newFlightData”, the “processFlightData” function will be called and it will update the map.

Security – Key Vault, CORS & Managed Identity

Just a short not on security. You may be thinking “Well, can someone not just call my Azure Functions or get my SignalR access token?”. The answer would be “No”. While this post will not go into detail, here is a short overview.

All secrets are held in a Key Vault. Access to the Key Vault is controlled via Managed Identity, so any other resources will not have access to it to be able to get connection strings etc. The Azure Maps key can be found on the page, but authentication with Azure Active Directory is an option instead along with OAuth.

CORS has also been set up so that the web application running on one domain cannot connect to another on a different domain. Therefore, as the website runs on a different domain to SignalR, the ARM Template adds a rule to allow this. So unless someone else spoofs the domain that SignalR allows, other sites on other domains won’t be able to connect.

The Final Result

The final result will look like the following. It has been speeded up as the Function that queries the OpenSky Network triggers every 5 seconds, and at the time of writing, there were not that many flights.

map-with-real-time-flight-details-updates

The Code

All of the code needed to try it out yourself is on my GitHub page. This includes the ARM Template to create all of the resources and the C# code. There is a link to deploy the ARM Template and you can easily deploy the code through VS Code. If you are debugging locally, just don’t forget to setup the CORS rules to allow localhost.

https://github.com/stuartleaver/flight-tracker-cosmosdb-functions-serverless

Credits

Photo by HAL9001 on Unsplash