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

Validating attributes

Object-oriented databases lack one very important thing that relational databases have: consistency. As consistent data handling and relational database support is a very important consideration in GIS software (at least to support a server-side spatial database), we need to implement some restrictions in our data management system. In this example, called ch04_validation, we will extend our application with typed attributes and validation.

Tip

One possible solution for easy RDBMS support is using the Web SQL Database API. However, this API is deprecated and not supported by any of the Firefox and Microsoft browsers. In supported browsers, it uses an SQLite backend.

Adjusting the styles

As we are using a form for attribute management, we can harness its validating capability as we use typed attributes. For numeric fields, we will use a number input, which does not process strings. In non-Microsoft browsers, numeric fields also prevent us from submitting the form if it detects a string in such an input. As a first step, we modify our CSS to extend our rules to numeric inputs, too:

.popup input[type=text], .popup input[type=number] {
    width: 60%;
    box-sizing: border-box;
}

Building headers

An easy way to store the types associated with the attributes is using headers like relational databases do. In our implementation, headers are objects stored in the layer object that contain the attribute names as keys and the types as values. For added convenience, we extend the OpenLayers 3 vector layer with a header builder method outside the init function:

ol.layer.Vector.prototype.buildHeaders = function () {
    var headers = this.get('headers') || {};
    var features = this.getSource().getFeatures();
    for (var i = 0; i < features.length; i += 1) {
        var attributes = features[i].getProperties();
        for (var j in attributes) {
            if (typeof attributes[j] !== 'object' && !(j in headers)) {
                headers[j] = 'string';
            }
        }
    }
    this.set('headers', headers);
    return this;
};

It simply grabs the headers property of the layer object, if it has one, and checks every feature's attributes; if it finds a missing attribute from the header object, it defaults the attribute's type to the string. This way, we have to provide every numeric attribute at layer construction.

Note

It would be more logical to store the headers in the source object. However, unlike layer objects, source objects do not accept extra properties at construction.

Writing the code

This is one of the rare cases when we head from a complicated to a more simple example. As we manage our attributes at a higher level now, we cannot do local changes to them.

Tip

To keep up the consistency of our data, we do not add or remove attributes at a feature level. Instead, you can build an attribute table and export those functions there.

In this step, we modify our click event on the map. We have to alter our createRow function and the forEachFeatureAtPixel method's callback function:

map.on('click', function (evt) {
    […]
    function createRow (attributeName, attributeValue, type) {
        var rowElem = document.createElement('div');
        var attributeSpan = document.createElement('span');
        attributeSpan.textContent = attributeName + ': ';
        rowElem.appendChild(attributeSpan);
        var attributeInput = document.createElement('input');
        attributeInput.name = attributeName;
        attributeInput.type = 'text';
        if (type !== 'string') {
            attributeInput.type = 'number';
            attributeInput.step = (type === 'float') ? 1e-6 : 1;
        }
        attributeInput.value = attributeValue;
        rowElem.appendChild(attributeInput);
        return rowElem;
    }

In this rewritten function, we don't have to deal with new attributes. We only check the type of the attribute that we have to create the row for. If it is not a string, we create a numeric input element for it and check further. If it is a float, we set the precision to six decimal places. If it is an integer, we set the step attribute to 1.

Tip

You can create more complex attribute types with additional parameters. For example, if you want to implement a byte type, you can set the input's min parameter to 0, its max parameter to 255, and step to 1.

Next, we modify our main function to add validating capabilities:

    this.forEachFeatureAtPixel(pixel, function (feature, layer) {
        […]
            for (var i in attributes) {
                if (typeof attributes[i] !== 'object' && i in headers) {
                    attributeForm.appendChild(createRow(i, attributes[i], headers[i]));
                }
            }
            […]
                attributeForm.addEventListener('submit', function (evt) {
                    evt.preventDefault();
                    var attributeList = {};
                    var inputList = [].slice.call(this.querySelectorAll('input[type=text], input[type=number]'));
                    for (var i = 0; i < inputList.length; i += 1) {
                        switch (headers[inputList[i].name]) {
                            case 'string':
                                attributeList[inputList[i].name] = inputList[i].value.toString();
                                break;
                            case 'integer':
                                attributeList[inputList[i].name] = parseInt(inputList[i].value);
                                break;
                            case 'float':
                                attributeList[inputList[i].name] = parseFloat(inputList[i].value);
                                break;
                        }
                    }
                    […]
});

The first part of the modification only maps out attributes that are present in the headers object of the layer. The second part is a lot more interesting. It collects all the form elements that can represent an attribute in an array. Based on the type of the attribute stored in the headers object, it parses the attribute accordingly. As inputs propagate their values as strings, converting them to numbers is a necessity. Converting them to text when we have to deal with a string type is just an extra layer of defense. Finally, we add some extra parameters and an event listener to invoke our new mechanism correctly:

var map = new ol.Map({
    […]
        new ol.layer.Vector({
            […]
            headers: {
               pop_est: 'integer',
                gdp_md_est: 'integer'
            }
        })
    […]
});
[…]
map.getLayers().item(1).getSource().on('change', function (evt) {
    if (this.getState() === 'ready') {
        map.getLayers().item(1).buildHeaders();
    }
});

Note

As you can see, the event is just a hack for our logic to work. In a product, it should be implemented into the layer tree's most appropriate section. Furthermore, the manual initialization of the headers object is also hacky; however, doing it right would expand the code far beyond the scope of this book.

If you save the code and load it up, you will see our validating mechanism in action. Note that the numeric input's UI widgets do not work in Microsoft browsers:

Writing the code