Languages: English简体中文繁體中文

SensorThings API - Visualizing More Locations

In Part 2 of this series, we:

  • Retrieved the most recent bike availability Observation for each Location
  • Displayed each Location on a map, with its color set by the number of available bikes
However, in Parts 1 and 2, we've only been using a subset of the Locations. In this part, we are going to show you how to retrieve and show all of the Locations in the SensorThings server.

It's Getting Crowded

Let's take another look at the Locations we received from SensorThings.

GET {{staBaseUrl}}/Locations?$expand=Things/Datastreams($filter=ObservedProperty/name eq 'available_bikes'),Things/Datastreams/Observations($orderby=phenomenonTime desc;$top=1)

If you look at the top of the response, you'll see that there is an @iot.count property indicating the total number of Locations that match the query. There is also an @iot.nextLink property telling you how to retrieve the next page of results.

If we retrieve all the results, this is what the map looks like.

See the code on SensorThings Share

It's a bit congested, no? In any case, let's take a look at what we've done to retrieve all the Locations.

First, we've wrapped our call to /Locations with a followNextLink function.
  axios.get('', {
    params: {
      '$expand': "Things/Datastreams($filter=ObservedProperty/name eq 'available_bikes'),Things/Datastreams/Observations($orderby=phenomenonTime desc;$top=1)"

The followNextLink function implementation, which should be added to the script, follows all @iot.nextLink links, and concatenates all the values so that the complete results are returned in one array. If you're curious about the nuts-and-bolts of this function, you'll need an understanding of recursion and JavaScript Promises. It's interesting, but a bit outside the scope of this tutorial.

function followNextLink(responsePromise) {
  return responsePromise.then(function(lastSuccess) {
    if(['@iot.nextLink']) {
      return followNextLink(axios.get(['@iot.nextLink'])).then(function(nextLinkSuccess) { =;
        return nextLinkSuccess;
    else {
      return lastSuccess;


One pretty standard approach for dealing with cluttered markers on a map is clustering. There is a popular plugin for Leaflet called Leaflet.markercluster. Here's what happens if we apply it (with default functionality) to our map.
See the code on SensorThings Share

It definitely reduces our clutter, but now we can't see which stations are running out of available bikes! Of course, this information is still available if we zoom in, but it's useful to be able to see our data at a glance, even when viewing our map from "high in the air."

Sometimes it's the Simple Solutions

In some cases, there are so many data points that some sort of clustering is pretty much essential. For our data set, however, shrinking the markers is sufficient to make the data presentable.
See the code on SensorThings Share
By setting the radius and weight (stroke width) of our circle markers, we can shrink the markers to better suit our data.
return L.circleMarker(latlng, {
  color: availableBikes > 5 ? enoughBikeColor : fewBikesColor,
  radius: 5,
  weight: 2

What's Next

So far, we've been showing the latest bike availability data from the server. However, it's often useful to be able to show historical data. Were there bikes available an hour ago? Are there usually bikes available on Fridays at 9pm? What is the average number of bikes available in winter vs summer? With the right data, SensorThings can help you answer all of those questions, but let's start with the basics. In the next tutorial, we're going to show you how to query for bike availability data before a specific date/time.

So, let's go time traveling.

Proceed to Part 4 - Time Travel