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
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.
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);
}
});
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.
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.
The request above may seem a bit intimidating, but it's actually not that complicated.
-
/Locations
: A request to theLocations
endpoint. This is the same as our previous requests. -
$expand=Things/Datastreams($filter=ObservedProperty/name eq 'available_bikes')
: We're expanding theThings
and associatedDatastreams
of thatLocation
, and we are filtering thoseDatastreams
so that only those with anObservedProperty
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 remainingDatastreams
not filtered out above, expand theObservations
, but only take the first one when ordered byphenomenonTime
. That is, only return the newestObservation
.
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!)
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.
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 = success.data.value.map(function(location) {
return {
type: 'Feature',
geometry: location.location,
properties: location
};
});
#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 = geoJsonPoint.properties.Things[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.