Region Growing

Threshold:

Grow a region from a seed pixel

Click a region on the map. The computed region will be red.

This example uses a ol.source.Raster to generate data based on another source. The raster source accepts any number of input sources (tile or image based) and runs a pipeline of operations on the input data. The return from the final operation is used as the data for the output source.

In this case, a single tiled source of imagery data is used as input. The region is calculated in a single "image" operation using the "seed" pixel provided by the user clicking on the map. The "threshold" value determines whether a given contiguous pixel belongs to the "region" - the difference between a candidate pixel's RGB values and the seed values must be below the threshold.

This example also shows how an additional function can be made available to the operation.

<!DOCTYPE html>
<html>
  <head>
    <title>Region Growing</title>
    <link rel="stylesheet" href="https://openlayers.org/en/v4.6.4/css/ol.css" type="text/css">
    <!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
    <script src=""></script>
    <script src="https://openlayers.org/en/v4.6.4/build/ol.js"></script>
    <style>
      table.controls td {
        min-width: 110px;
        padding: 2px 5px;
      }
    </style>
  </head>
  <body>
    <div id="map" class="map" style="cursor: pointer"></div>
    <table class="controls">
      <tr>
        <td>Threshold: <span id="threshold-value"></span></td>
        <td><input id="threshold" type="range" min="1" max="50" value="20"></td>
      </tr>
    </table>
    <script>
      function growRegion(inputs, data) {
        var image = inputs[0];
        var seed = data.pixel;
        var delta = parseInt(data.delta);
        if (!seed) {
          return image;
        }

        seed = seed.map(Math.round);
        var width = image.width;
        var height = image.height;
        var inputData = image.data;
        var outputData = new Uint8ClampedArray(inputData);
        var seedIdx = (seed[1] * width + seed[0]) * 4;
        var seedR = inputData[seedIdx];
        var seedG = inputData[seedIdx + 1];
        var seedB = inputData[seedIdx + 2];
        var edge = [seed];
        while (edge.length) {
          var newedge = [];
          for (var i = 0, ii = edge.length; i < ii; i++) {
            // As noted in the Raster source constructor, this function is provided
            // using the `lib` option. Other functions will NOT be visible unless
            // provided using the `lib` option.
            var next = next4Edges(edge[i]);
            for (var j = 0, jj = next.length; j < jj; j++) {
              var s = next[j][0], t = next[j][1];
              if (s >= 0 && s < width && t >= 0 && t < height) {
                var ci = (t * width + s) * 4;
                var cr = inputData[ci];
                var cg = inputData[ci + 1];
                var cb = inputData[ci + 2];
                var ca = inputData[ci + 3];
                // if alpha is zero, carry on
                if (ca === 0) {
                  continue;
                }
                if (Math.abs(seedR - cr) < delta && Math.abs(seedG - cg) < delta &&
                    Math.abs(seedB - cb) < delta) {
                  outputData[ci] = 255;
                  outputData[ci + 1] = 0;
                  outputData[ci + 2] = 0;
                  outputData[ci + 3] = 255;
                  newedge.push([s, t]);
                }
                // mark as visited
                inputData[ci + 3] = 0;
              }
            }
          }
          edge = newedge;
        }
        return {data: outputData, width: width, height: height};
      }

      function next4Edges(edge) {
        var x = edge[0], y = edge[1];
        return [
          [x + 1, y],
          [x - 1, y],
          [x, y + 1],
          [x, y - 1]
        ];
      }

      var key = 'Your Bing Maps Key from http://www.bingmapsportal.com/ here';

      var imagery = new ol.layer.Tile({
        source: new ol.source.BingMaps({key: key, imagerySet: 'Aerial'})
      });

      var raster = new ol.source.Raster({
        sources: [imagery.getSource()],
        operationType: 'image',
        operation: growRegion,
        // Functions in the `lib` object will be available to the operation run in
        // the web worker.
        lib: {
          next4Edges: next4Edges
        }
      });

      var rasterImage = new ol.layer.Image({
        opacity: 0.7,
        source: raster
      });

      var map = new ol.Map({
        layers: [imagery, rasterImage],
        target: 'map',
        view: new ol.View({
          center: ol.proj.fromLonLat([-119.07, 47.65]),
          zoom: 11
        })
      });

      var coordinate;

      map.on('click', function(event) {
        coordinate = event.coordinate;
        raster.changed();
      });

      var thresholdControl = document.getElementById('threshold');

      raster.on('beforeoperations', function(event) {
        // the event.data object will be passed to operations
        var data = event.data;
        data.delta = thresholdControl.value;
        if (coordinate) {
          data.pixel = map.getPixelFromCoordinate(coordinate);
        }
      });

      function updateControlValue() {
        document.getElementById('threshold-value').innerText = thresholdControl.value;
      }
      updateControlValue();

      thresholdControl.addEventListener('input', function() {
        updateControlValue();
        raster.changed();
      });
    </script>
  </body>
</html>