Table of Contents for
OpenLayers 3.x Cookbook - Second Edition

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition OpenLayers 3.x Cookbook - Second Edition by Antonio Santiago Perez Published by Packt Publishing, 2016
  1. Cover
  2. Table of Contents
  3. OpenLayers 3.x Cookbook Second Edition
  4. OpenLayers 3.x Cookbook Second Edition
  5. Credits
  6. About the Authors
  7. About the Reviewer
  8. www.PacktPub.com
  9. Preface
  10. What you need for this book
  11. Who this book is for
  12. Sections
  13. Conventions
  14. Reader feedback
  15. Customer support
  16. 1. Web Mapping Basics
  17. Creating a simple fullscreen map
  18. Playing with the map's options
  19. Managing the map's stack layers
  20. Managing the map's controls
  21. Moving around the map view
  22. Restricting the map's extent
  23. 2. Adding Raster Layers
  24. Using Bing imagery
  25. Using OpenStreetMap imagery
  26. Adding WMS layers
  27. Changing the zoom effect
  28. Changing layer opacity
  29. Buffering the layer data to improve map navigation
  30. Creating an image layer
  31. Setting the tile size in WMS layers
  32. 3. Working with Vector Layers
  33. Adding a GML layer
  34. Adding a KML layer
  35. Creating features programmatically
  36. Exporting features as GeoJSON
  37. Reading and creating features from a WKT
  38. Using point features as markers
  39. Removing or cloning features using overlays
  40. Zooming to the extent of a layer
  41. Adding text labels to geometry points
  42. Adding features from a WFS server
  43. Using the cluster strategy
  44. Reading features directly using AJAX
  45. Creating a heat map
  46. 4. Working with Events
  47. Creating a side-by-side map comparator
  48. Implementing a work-in-progress indicator for map layers
  49. Listening for the vector layer features' events
  50. Listening for mouse or touch events
  51. Using the keyboard to pan or zoom
  52. 5. Adding Controls
  53. Adding and removing controls
  54. Working with geolocation
  55. Placing controls outside the map
  56. Drawing features across multiple vector layers
  57. Modifying features
  58. Measuring distances and areas
  59. Getting feature information from a data source
  60. Getting information from a WMS server
  61. 6. Styling Features
  62. Styling layers
  63. Styling features based on geometry type
  64. Styling based on feature attributes
  65. Styling interaction render intents
  66. Styling clustered features
  67. 7. Beyond the Basics
  68. Working with projections
  69. Creating a custom control
  70. Selecting features by dragging out a selection area
  71. Transitioning between weather forecast imagery
  72. Using the custom OpenLayers library build
  73. Drawing in freehand mode
  74. Modifying layer appearance
  75. Adding features to the vector layer by dragging and dropping them
  76. Making use of map permalinks
  77. Index

Measuring distances and areas

The ability to measure distances or areas is an important feature for many GIS applications. For example, a user may wish to draw out a path along some roads to figure out the distance of a journey. Or you may want to draw a polygon around your neighbor's back garden to see how many acres they have!

OpenLayers 2 conveniently came with a measuring control straight out of the box. However, at the time of writing this book, OpenLayers 3 doesn't offer this convenience. However, you'll be pleased to know that this can be cobbled together with the methods that are available to us without much fuss.

The application will contain a sidebar with radio inputs in order to select a measurement type, either area or distance. The user will be able to draw anywhere on the map and see a live update of the measurement in the sidebar as they go. For mobile, the updates will be seen after every new point has been placed on the map.

The source code can be found in ch05/ch05-measuring, and this will look like the following screenshot:

Measuring distances and areas

To see a live demonstration of this recipe, visit https://jsfiddle.net/pjlangley/Ln8wz7ty/.

How to do it…

To create a measuring tool for distances and areas, use the following instructions:

  1. Create an HTML file and add the OpenLayers dependencies, the jQuery library, and div to hold the map. In particular, add the following essential markup that we'll be interacting with in the JavaScript:
    <form>
    <label>
    <input type="radio" name="measurement" value="Polygon" checked>
       Area
    </label>
    <label>
    <input type="radio" name="measurement" value="LineString">
        Distance
    </label>
    </form>
    <samp id="js-result">n/a</samp>
  2. Create a custom JavaScript file and store a vector layer with source in a variable, and also instantiate map with view and layers:
    var vectorLayer = new ol.layer.Vector({
      source: new ol.source.Vector()
    });
    
    var map = new ol.Map({
      view: new ol.View({
        zoom: 15, center: [-13610530, 4555374]
      }),
      target: 'js-map',
      layers: [
        new ol.layer.Tile({ source: new ol.source.Stamen({
          layer: 'terrain'
        })}), vectorLayer
      ]
    });
  3. Cache some DOM elements and set up a global variable to hold the reference of the measuring tool:
    var measurementRadios = $('[type=radio]');
    var resultElement = $('#js-result');
    var measuringTool;
  4. Implement the method to enable the measuring tool:
    var enableMeasuringTool = function() {
      map.removeInteraction(measuringTool);
    
      var geometryType = measurementRadios.filter(':checked').val();
      var html = geometryType === 'Polygon' ? '<sup>2</sup>' : '';
    
      measuringTool = new ol.interaction.Draw({
        type: geometryType,
        source: vectorLayer.getSource()
      });
    
      measuringTool.on('drawstart', function(event) {
        vectorLayer.getSource().clear();
    
        event.feature.on('change', function(event) {
          var measurement = geometryType === 'Polygon'
            ? event.target.getGeometry().getArea()
            : event.target.getGeometry().getLength();
    
          var measurementFormatted = measurement > 100
            ? (measurement / 1000).toFixed(2) + 'km'
            : measurement.toFixed(2) + 'm';
    
          resultElement.html(measurementFormatted + html);
        });
      });
      map.addInteraction(measuringTool);
    };
  5. Subscribe to the change event on the radio inputs with a handler to update the measuring control. Finally, enable the measuring tool by default:
    measurementRadios.on('change', enableMeasuringTool);
    enableMeasuringTool();

How it works…

We've omitted much of the HTML and all the CSS for brevity. For a complete look at how the styling and structure is achieved, please view the source code, where you'll see our use of the Bootstrap CSS framework.

Let's dive into the core of this recipe where we build the measuring control:

var enableMeasuringTool = function() {
  map.removeInteraction(measuringTool);

When this method is called, it's sourced from either a change in the radio input selection or at page load. We want to ensure that the measuring tool (draw interaction) is set up with the correct configuration, so the old copy (if it exists) is removed from the map first.

var geometryType = measurementRadios.filter(':checked').val();
var html = geometryType === 'Polygon' ? '<sup>2</sup>' : '';

Next, we store a reference of the geometry type. The filter jQuery method is used to identify the checked radio input, and the val jQuery method extracts the value.

The html variable is what we append onto the measurement result in the sidebar. If this is of type Polygon, then it's an area measurement and, so, we add 2 to represent squared; otherwise, it's set to an empty string. We've used a ternary operator to conditionally assign a value to a variable, a technique that we used throughout this recipe.

  measuringTool = new ol.interaction.Draw({
    type: geometryType,
    source: vectorLayer.getSource()
  });

As you can see, our custom measuring tool is simply an instance of the draw interaction behind the scenes. The type property is assigned the geometry type, accordingly.

measuringTool.on('drawstart', function(event) {
  vectorLayer.getSource().clear();

We subscribe to the drawstart event that's emitted from the draw interaction. When the user places the first point of their drawing on the map, we clear the vector source of any existing features with the clear method.

  event.feature.on('change', function(event) {

When a feature's geometry changes, it publishes a change event that we can subscribe to on the ol.Feature instance. This means that we can update the sidebar with the latest measurement while the user is still drawing. Potentially, this event can be fired at a high frequency, so you could consider a throttling technique here for performance (refer to https://remysharp.com/2010/07/21/throttling-function-calls for more details).

    var measurement = geometryType === 'Polygon'
      ? event.target.getGeometry().getArea()
      : event.target.getGeometry().getLength();

Within the geometry change event, we use the event target (an instance of ol.Feature) to retrieve the latest geometry. If it's a Polygon drawing interaction, then we want to calculate the area. The ol.geom.Polygon class has the getArea method for this. Alternatively, ol.geom.LineString has the getLength method to provide the distance. The result is stored in the measurement variable.

      var measurementFormatted = measurement > 100
        ? (measurement / 1000).toFixed(2) + 'km'
        : measurement.toFixed(2) + 'm';

As a default, the measurement is provided in meters by OpenLayers. In the case where the measurement exceeds 100 meters, we convert it to kilometers, as this is probably more useful. There are 1000 meters in a kilometer, so for example, if we have 200 meters, 200 divided by 1000 = 0.2 kilometers. We also round the value to two decimal places with the toFixed JavaScript method. The units of measurement is concatenated on to the end of the number.

      resultElement.html(measurementFormatted + html);
    });
  });
  map.addInteraction(measuringTool);

Finally, the sidebar element which displays the measurement is populated with the formatted version and concatenated with the contents of the html variable (which could contain a 2 for area or a blank string for distance) via the jQuery html method. The last part of this function adds the completed interaction to the map.

There's more…

The calculations for the measurements are represented in a simple projected plane without the Earth's curvature taken into account. A more involved geodesic calculation can be achieved through OpenLayers, but this is a bit more advanced and won't be covered in this recipe.

This official example from OpenLayers demonstrates how to provide measurements in geodesic metrics at http://openlayers.org/en/v3.13.1/examples/measure.html. Microsoft also has an interesting article on the topic at https://msdn.microsoft.com/en-GB/library/aa940990.aspx.

See also

  • The Working with geolocation recipe
  • The Drawing features across multiple vector layers recipe
  • The Modifying features recipe