Table of Contents for
Mastering OpenLayers 3

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Mastering OpenLayers 3 by Gábor Farkas Published by Packt Publishing, 2016
  1. Cover
  2. Table of Contents
  3. Mastering OpenLayers 3
  4. Mastering OpenLayers 3
  5. Credits
  6. About the Author
  7. About the Reviewer
  8. www.PacktPub.com
  9. Preface
  10. What you need for this book
  11. Who this book is for
  12. Conventions
  13. Reader feedback
  14. Customer support
  15. 1. Creating Simple Maps with OpenLayers 3
  16. Structure of OpenLayers 3
  17. Building the layout
  18. Using the API documentation
  19. Debugging the code
  20. Summary
  21. 2. Applying Custom Styles
  22. Customizing the default appearance
  23. Styling vector layers
  24. Customizing the appearance with JavaScript
  25. Creating a WebGIS client layout
  26. Summary
  27. 3. Working with Layers
  28. Building a layer tree
  29. Adding layers dynamically
  30. Adding vector layers with the File API
  31. Adding vector layers with a library
  32. Removing layers dynamically
  33. Changing layer attributes
  34. Changing the layer order with the Drag and Drop API
  35. Clearing the message bar
  36. Summary
  37. 4. Using Vector Data
  38. Accessing attributes
  39. Setting attributes
  40. Validating attributes
  41. Creating thematic layers
  42. Saving vector data
  43. Saving with WFS-T
  44. Modifying the geometry
  45. Summary
  46. 5. Creating Responsive Applications with Interactions and Controls
  47. Building the toolbar
  48. Mapping interactions to controls
  49. Building a set of feature selection controls
  50. Adding new vector layers
  51. Building a set of drawing tools
  52. Modifying and snapping to features
  53. Creating new interactions
  54. Building a measuring control
  55. Summary
  56. 6. Controlling the Map – View and Projection
  57. Customizing a view
  58. Constraining a view
  59. Creating a navigation history
  60. Working with extents
  61. Rotating a view
  62. Changing the map's projection
  63. Creating custom animations
  64. Summary
  65. 7. Mastering Renderers
  66. Using different renderers
  67. Creating a WebGL map
  68. Drawing lines and polygons with WebGL
  69. Blending layers
  70. Clipping layers
  71. Exporting a map
  72. Creating a raster calculator
  73. Creating a convolution matrix
  74. Clipping a layer with WebGL
  75. Summary
  76. 8. OpenLayers 3 for Mobile
  77. Responsive styling with CSS
  78. Generating geocaches
  79. Adding device-dependent controls
  80. Vectorizing the mobile version
  81. Making the mobile application interactive
  82. Summary
  83. 9. Tools of the Trade – Integrating Third-Party Applications
  84. Exporting a QGIS project
  85. Importing shapefiles
  86. Spatial analysis with Turf
  87. Spatial analysis with JSTS
  88. 3D rendering with Cesium
  89. Summary
  90. 10. Compiling Custom Builds with Closure
  91. Configuring Node JS
  92. Compiling OpenLayers 3
  93. Bundling an application with OpenLayers 3
  94. Extending OpenLayers 3
  95. Creating rich documentation with JSDoc
  96. Summary
  97. Index

Creating thematic layers

In this example, called ch04_thematic, we will create an automatic solution for thematic mapping. It will include graduated and categorized symbology. To keep the example as simple as possible, we will make some generally incorrect assumptions. Firstly, we assume that we only have to style polygon layers. For graduated symbology, we hard code the intervals to five and also the color ramp starting from a beige tone and ending in a burgundy color. We will implement the whole code into our layer tree.

Extending the layer tree

We create the required GUI options in the layer tree. Firstly, we check against the origin of the layer. If it is a vector layer, we create an empty list and three buttons for the different styling options. Then, we save the default style in the layer, as it will enable us to restore the original styling. Finally, we register an event listener to the layer object. If the headers are changed, we rebuild the attribute list in the layer tree:

this.createRegistry = function (layer, buffer) {
    […]
    if (layer instanceof ol.layer.Vector) {
        layerControls.appendChild(document.createElement('br'));
        var attributeOptions = document.createElement('select');
        layerControls.appendChild(this.stopPropagationOnEvent(attributeOptions, 'click'));
        layerControls.appendChild(document.createElement('br'));
        var defaultStyle = this.createButton('stylelayer', 'Default', 'stylelayer', layer);
        layerControls.appendChild(this.stopPropagationOnEvent(defaultStyle, 'click'));
        var graduatedStyle = this.createButton('stylelayer', 'Graduated', 'stylelayer', layer);
        layerControls.appendChild(this.stopPropagationOnEvent(graduatedStyle, 'click'));
        var categorizedStyle = this.createButton('stylelayer', 'Categorized', 'stylelayer', layer);
        layerControls.appendChild(this.stopPropagationOnEvent(categorizedStyle, 'click'));
        layer.set('style', layer.getStyle());
        layer.on('propertychange', function (evt) {
            if (evt.key === 'headers') {
                this.removeContent(attributeOptions);
                var headers = layer.get('headers');
                for (var i in headers) {
                    attributeOptions.appendChild(this.createOption(i));
                }
            }
        }, this);
    }
    […]

Tip

In prior versions of OpenLayers 3, there was an undocumented method for firing custom events called dispatchEvent. From Version 3.8.0, this feature is gone; therefore, you must listen to existing events instead of creating new ones.

As we are building on the existing functionality, we extend our createButton method to support styling buttons. For this, we need to pass the layer object to it, obligating us to add a new argument to it:

layerTree.prototype.createButton = function (elemName, elemTitle, elemType, layer) {
    […]
        case 'stylelayer':
            var _this = this;
            buttonElem.textContent = elemTitle;
            if (elemTitle === 'Default') {
                buttonElem.addEventListener('click', function () {
                    layer.setStyle(layer.get('style'));
                });
            } else {
                var styleFunction = elemTitle === 'Graduated' ? this.styleGraduated : this.styleCategorized;
                buttonElem.addEventListener('click', function () {
                    var attribute = buttonElem.parentNode.querySelector('select').value;
                    styleFunction.call(_this, layer, attribute);
                });
            }
            return buttonElem;
        […]
};

We shape our default styling button to its final form, restoring the default style stored in the layer object. The other buttons invoke the appropriate styling method. As we grab those methods out of their default context, we have to manually provide them with the JavaScript's call method and the reference to our layer tree.

Tip

Extending the createButton method slowly leads to creating a good object. Creating a method that has too much and too diverse a functionality often leads to complicated, unmanageable code. Always try to keep your methods clean and write quality code.

Creating choropleth maps

In this step, we need to calculate the color ramps for the classes first. We calculate a linear transition between two arbitrary colors. To calculate the colors for the different classes, we use an RGB color generator:

layerTree.prototype.graduatedColorFactory = function (classNum, rgb1, rgb2) {
    var colors = [];
    var steps = classNum - 1;
    var redStep = (rgb2[0] - rgb1[0]) / steps;
    var greenStep = (rgb2[1] - rgb1[1]) / steps;
    var blueStep = (rgb2[2] - rgb1[2]) / steps;
    for (var i = 0; i < steps; i += 1) {
        var red = Math.ceil(rgb1[0] + redStep * i);
        var green = Math.ceil(rgb1[1] + greenStep * i);
        var blue = Math.ceil(rgb1[2] + blueStep * i);
        colors.push([red, green, blue, 1]);
    }
    colors.push([rgb2[0], rgb2[1], rgb2[2], 1]);
    return colors;
};

As we have colors for the first and the last class already, we only have to partition the intervals of the color components to one part less than the number of classes we need:

Creating choropleth maps

Next, we can start to write our styling function. Firstly, we calculate some statistics based on the input attribute and generate a linear gradient accordingly:

layerTree.prototype.styleGraduated = function (layer, attribute) {
    if (layer.get('headers')[attribute] === 'string') {
        this.messages.textContent = 'A numeric column is required for graduated symbology.';
    } else {
        var attributeArray = [];
        layer.getSource().forEachFeature(function (feat) {
            attributeArray.push(feat.get(attribute));
        });
        var max = Math.max.apply(null, attributeArray);
        var min = Math.min.apply(null, attributeArray);
        var step = (max - min) / 5;
        var colors = this.graduatedColorFactory(5, [254, 240, 217], [179, 0, 0]);

Tip

You can use JavaScript's apply method with its Math.min and Math.max methods to calculate the minimum and the maximum value of an array. The apply method calls a function with a context and also an array of arguments.

Finally, we build a style function based on the generated colors and calculated intervals:

        layer.setStyle(function (feature, res) {
            var property = feature.get(attribute);
            var color = property < min + step * 1 ? colors[0] :
                property < min + step * 2 ? colors[1] :
                property < min + step * 3 ? colors[2] :
                property < min + step * 4 ? colors[3] : colors[4];
            var style = new ol.style.Style({
                stroke: new ol.style.Stroke({
                    color: [0, 0, 0, 1],
                    width: 1
                }),
                fill: new ol.style.Fill({
                    color: color
                })
            });
            return [style];
        });
    }
};

Remember our assumptions: we can only style polygons with this method with five intervals and a predefined start and end color. In order to make a general styling method, you have to make a lot of checks and validations. If you test this part of the application, you can create choropleth maps with the numeric attributes:

Creating choropleth maps

Note

The simple equal interval symbology described above shows representative results only with data that has a nearly normal distribution. For a decent GIS software, more advanced functions are needed for graduated symbology.

Creating categorized maps

For a categorized symbology, we only need to collect the different attribute values from the features and assign random colors to them. For this purpose, we use a random color generator function:

layerTree.prototype.randomHexColor = function() {
    return '#' + Math.floor(Math.random() * 16777215).toString(16);
};

Note

This simple method can work because the Math.random method returns a floating point number between 0 and 1. The biggest hexadecimal value for a color is 0xFFFFFF. Its decimal value is 16777215. Finally, the Number.prototype.toString method accepts a radix argument to support conversions between different numeral systems.

Next, we can collect the different attribute values and generate a random color for each of them:

layerTree.prototype.styleCategorized = function (layer, attribute) {
    var attributeArray = [];
    var colorArray = [];
    var randomColor;
    layer.getSource().forEachFeature(function (feat) {
        var property = feat.get(attribute).toString();
        if (attributeArray.indexOf(property) === -1) {
            attributeArray.push(property);
            do {
                randomColor = this.randomHexColor();
            } while (colorArray.indexOf(randomColor) !== -1);
            colorArray.push(randomColor);
        }
    }, this);

Our method builds two consequent arrays, where the indices of the attribute values and the colors match. This way, we can easily check whether a newly generated color already exists. If it does, the method just simply generates a new color and checks again. Now that we have the attribute and color arrays, we can build the style function:

    layer.setStyle(function (feature, res) {
        var index = attributeArray.indexOf(feature.get(attribute).toString());
        var style = new ol.style.Style({
            stroke: new ol.style.Stroke({
                color: [0, 0, 0, 1],
                width: 1
            }),
            fill: new ol.style.Fill({
                color: colorArray[index]
            })
        });
        return [style];
    });
};

In the style function, we check every feature's corresponding attribute value against the attribute array, and assign a color based on the returned index number. If you save the code and load it up, you can test our new styling functionality. If you choose the continent column for the categorized styling, you will get the following result:

Creating categorized maps

Tip

Note that features from a KML source use internal styling (as KML data can store styles); therefore, these styling functions won't work on them.