For this example, we will render the country layer by styling each country based on income level by associating its country code to income level data provided by the world bank. There are quite a few brackets; so, we've simplified it to four levels: high, medium, low, and poor. We'll draw each country in a color associated with its income level based on these brackets. Let's start from the previous example.
<script> tag, before anything else, we will define colors for the four brackets. Use any colours you like:var high = [64,196,64,1]; var mid = [108,152,64,1]; var low = [152,108,64,1]; var poor = [196,32,32,1];
var incomeLevels = {
'HIC': high, // high income
'OEC': high, // high income OECD
'NOC': high, // high income, non-OECD
'UMC': mid, // upper middle income
'MIC': mid, // middle income
'LMC': mid, // lower middle income
'LIC': low, // low income
'LMY': low, // low and middle income
'HPC': poor // heavily indebted poor country
};var defaultStyle = new ol.style.Style({
fill: new ol.style.Fill({
color: [250,250,250,1]
}),
stroke: new ol.style.Stroke({
color: [220,220,220,1],
width: 1
})
});var styleCache = {};
function styleFunction(feature, resolution) {
var level = feature.get('incomeLevel');
if (!level || !incomeLevels[level]) {
return [defaultStyle];
}
if (!styleCache[level]) {
styleCache[level] = new ol.style.Style({
fill: new ol.style.Fill({
color: incomeLevels[level]
}),
stroke: defaultStyle.stroke
});
}
return [styleCache[level]];
}var source = new ol.source.GeoJSON({
projection: 'EPSG:3857',
url: '../assets/data/countries.geojson'
});
var countries = new ol.layer.Vector({
source: source,
style: styleFunction
});var key = source.on('change', function(event) {
if (event.target.getState() == 'ready') {
source.unByKey(key);
$.ajax('../assets/data/income_levels.json').done(function(data) {
source.forEachFeature(function(feature) {
var code = feature.get('iso_a2');
if (data[code]) {
feature.set('incomeLevel', data[code]);
}
});
});
}
});
Let's review what is happening in this example. We are using a standard setup for vector layers, and combining some extra data into our features dynamically. The data is used to style, or classify, the country polygons by the income level as recorded by the world bank. Some interesting things are happening in this example; so we'll look at each step and highlight what's going on.
In step 1, we created some colors to be used to fill countries that fall into one of the four income categories. The number of categories is arbitrary—you can create colors for each of the income levels, or decide to group them differently.
In step 2, we created an object that we can use to look up a color based on an income level, and in step 3, we defined a default style. It's usually good practice when dealing with data that can change to have some kind of fallback so that your code doesn't break. We'll use the default style if we can't find a income level in our lookup object.
In step 4, we created our style function. Another best practice is to reuse style objects as much as possible. Since several countries might be drawn in the same style, we created an empty object (the styleCache) to store previously created styles. The actual function comes next. When the style function is called, it gets a reference to the feature being styled and the current resolution of the view on which it is being rendered. We aren't using the resolution in this example. We are using the feature, however. The feature should have a property called incomeLevel that matches the values in our lookup tables, so we grab that value and assign it to a variable. If the income level wasn't set or if it doesn't exist in the lookup table, we'll return the default style. Otherwise, we can check to see whether the styleCache object already has a style for this income level. If it doesn't, we need to create a new style using the color from our lookup table. In this case, we are using the default style's stroke for everything, but you could easily change the stroke for each feature too. Because we've assigned the new style to the correct slot in the cache, we can then return it directly.
Note that in both cases, we return an array containing the style. This is required for a very important reason: performance! The Style functions are executed a lot of times when rendering vector features and if OpenLayers had to check the return type of each call to see if it was a style object or an array of style objects, this would add significant overhead to the rendering pipeline. For individual features, the difference is so tiny that it's probably not measurable even with the best of tools. For many features, this tiny difference adds up to a lot and it is measurable. The OpenLayers developers have put a lot of effort into this kind of detail and it shows!
In step 5, we modified the layer to use the style function we just created and defined a separate variable for the source. This was to make the next step a little easier.
In step 6, we loaded the income level data. The goal of the code in this step is to load the income data and associate it with the appropriate country feature. To do this, we need to make sure that the country features have been loaded. Once the countries are loaded, then, we need to load the income data. Then we can create a new property on each country feature with the appropriate income level. There are a few important things happening in this step, so we'll review each line:
ol.Observable and so they provide the on() method for this. Recall from Chapter 2, Key Concepts in OpenLayers, that the on() method returns a key that can be used later to deregister an event handler (using the unByKey() method)—we'll need it in just a moment. The change event on a vector source is triggered when the source changes state.unByKey() method so that the handler doesn't get called again. This is very important. A source triggers the change event when its state changes, but also when any of its features change. This means that our event handler will get called again when we add properties to the feature. Since the state of the source will already be ready, our code will try to load the income data again and will create an infinite loop.ajax() method. The data loaded from this file is passed to the function we register using done().forEachFeature() and providing a function that will be called with each feature.iso_a2 property of the feature, which is a two letter code associated with each country. The income data in our file is organized by this code so we can use this code to get the income level for each country.These eight lines of code accomplish quite a bit and combine concepts from other chapters of this book, including Chapter 2, Key Concepts in OpenLayers (event registration and deregistration) and Chapter 5, Using Vector Layers (vector layers, vector sources, and feature properties).