Languages: English简体中文繁體中文

SensorThings API - Data-Driven Styling

In Part 1 of this series, we:

  • Made a simple map with Leaflet
  • Populated it with the Locations of bike sharing stations in Toronto
In this part, we are going to show you how to style the map based on actual bike sharing data retrieved from the SensorThings server. Specifically, we're going to show you how to make a map that has color-coded markers indicating the number of bikes available at a given location.

First, Some Housekeeping

Before we dive into the nitty-gritty details of data-driven styling, let's make a few small adjustments to the map.

  • Change the markers from pins to circles
  • To allow the user to understand the source of the data, we're going to add a line to the attribution indicating that the map contains open data from the city of Toronto.

See the code on SensorThings Share
The pointToLayer option now includes a function that returns a circle marker, instead of the default pin.
var geoJsonLayerGroup = L.geoJSON(geoJsonFeatures, {

  // Change the default marker to a circle
  pointToLayer: function(geoJsonPoint, latlng) {
    return L.circleMarker(latlng);

There's now a line providing an attribution to Toronto open data.
map.attributionControl.addAttribution("Contains information licensed under the Open Government Licence – Toronto.");

Where Have All the Bikes Gone?

Up to this point, we've been putting dots on a map. It's kinda neat, but, really, nothing special. While it's useful to know where the bike sharing stations are, it would be much more useful to see how many bikes are available at each one. And that's exactly what we'll do.

In SensorThings, a piece of information like "available bikes" is called an ObservedProperty. The SensorThings server for this tutorial contains information for two different ObservedProperties.

  • Available bikes
  • Available docks

Full detail is shown below.

GET {{staBaseUrl}}/ObservedProperties

While both available bikes and available docks are useful pieces of information, for the purposes of this part, we'll focus on showing available bikes.

What we're looking to do is find out how many bikes are available at a given Location. In SensorThings, that information is stored in an Observation. In order to get that Observation, we have to navigate through a number of other entities. Specifically, we need to get the Things that are at a Location, then we have to get the Datastreams that correspond to the Thing (but only the Datastreams that contain information about available bikes), then we want to get the Observations for that Datastream (but we only need the most recent one). This may seem like a lot of detail, but each of these entities allows you to make powerful and specific queries regarding the data you want to retrieve.

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

The request above may seem a bit intimidating, but it's actually not that complicated.

  • /Locations: A request to the Locations endpoint. This is the same as our previous requests.
  • $expand=Things/Datastreams($filter=ObservedProperty/name eq 'available_bikes'): We're expanding the Things and associated Datastreams of that Location, and we are filtering those Datastreams so that only those with an ObservedProperty named 'available_bikes' are included in the result.
  • Things/Datastreams/Observations($orderby=phenomenonTime desc;$top=1): This is the second part of the $expand parameter. For the remaining Datastreams not filtered out above, expand the Observations, but only take the first one when ordered by phenomenonTime. That is, only return the newest Observation.

The response contains, among other things:

  • The longitude/latitude coordinates of the Location (which we've retrieved before)
  • The (most recent) number of available bikes at that Location (which is new!)
We won't go over the results line-by-line, but it's a good idea to peruse the response to get a feel for the data that is available, and its structure. After all, data is the raw material from which a user interface is built!

Assembling the Map

Now that we know how to retrieve the number of available bikes at each location, we're ready to build our map.

See the code on SensorThings Share
We've added the properties: location line to the GeoJSON Features so that Leaflet has access to the Things, Datastreams, and Observations that are now included in the response from SensorThings.
var geoJsonFeatures = {
  return {
    type: 'Feature',
    geometry: location.location,
    properties: location
Finally, we've changed the color of the markers to distinguish between "enough" available bikes (shown in blue, or hex color #38f) and "few" available bikes (shown in red). We've arbitrarily set the cut-off so that greater than five bikes is "enough", and anything else is "few".
pointToLayer: function(geoJsonPoint, latlng) {
  var enoughBikeColor = "#38f";
  var fewBikesColor = "red";
  var availableBikes =[0].Datastreams[0].Observations[0].result;
  return L.circleMarker(latlng, {
    color: availableBikes > 5 ? enoughBikeColor : fewBikesColor

What's Next

Were you the type of kid who raised their hand in class to tell their teachers they were wrong? If you were, your hand should be raised, in fact, you should be waving it frantically to get attention. That's because if you look carefully at the /Locations response, there is a property that says "@iot.count": 199, but only 100 Locations were returned. You may also have noticed that there was a suggestive @iot.nextLink property.

We haven't been showing all the bike share locations. Not even close.

And that's just not good enough. So, on we go to Part 3.

Proceed to Part 3 - Visualizing More Locations