diff --git a/Makefile b/Makefile index ffe3604869..69b5e653a5 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ BUILD_HOSTED := build/hosted/$(BRANCH) BUILD_HOSTED_EXAMPLES := $(addprefix $(BUILD_HOSTED)/,$(EXAMPLES)) BUILD_HOSTED_EXAMPLES_JS := $(addprefix $(BUILD_HOSTED)/,$(EXAMPLES_JS)) -UNPHANTOMABLE_EXAMPLES = examples/shaded-relief.html examples/raster.html +UNPHANTOMABLE_EXAMPLES = examples/shaded-relief.html examples/raster.html examples/region-growing.html CHECK_EXAMPLE_TIMESTAMPS = $(patsubst examples/%.html,build/timestamps/check-%-timestamp,$(filter-out $(UNPHANTOMABLE_EXAMPLES),$(EXAMPLES_HTML))) TASKS_JS := $(shell find tasks -name '*.js') diff --git a/examples/region-growing.html b/examples/region-growing.html new file mode 100644 index 0000000000..adcc843c27 --- /dev/null +++ b/examples/region-growing.html @@ -0,0 +1,43 @@ +--- +template: example.html +title: Region Growing +shortdesc: Grow a region from a seed pixel +docs: > +

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. +

+tags: "raster, region growing" +--- +
+
+
+
+
+
+
+ Threshold: +
+
+ +
+
+ +
+
diff --git a/examples/region-growing.js b/examples/region-growing.js new file mode 100644 index 0000000000..266c019751 --- /dev/null +++ b/examples/region-growing.js @@ -0,0 +1,134 @@ +// NOCOMPILE +goog.require('ol.Map'); +goog.require('ol.View'); +goog.require('ol.layer.Image'); +goog.require('ol.layer.Tile'); +goog.require('ol.proj'); +goog.require('ol.source.BingMaps'); +goog.require('ol.source.Raster'); + +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 = nextEdges(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 [new ImageData(outputData, width, 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 = 'Ak-dzM4wZjSqTlzveKz5u0d4IQ4bRzVI309GxmkgSVr1ewS6iPSrOvOKhA-CJlm3'; + +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', + operations: [growRegion], + // the contents of `lib` option will be available to the operation(s) run in + // the web worker. + lib: { + nextEdges: next4Edges + } +}); + +var rasterImage = new ol.layer.Image({ + opacity: 0.8, + source: raster +}); + +var map = new ol.Map({ + layers: [imagery, rasterImage], + target: 'map', + view: new ol.View({ + center: ol.proj.fromLonLat([-120, 50]), + zoom: 6 + }) +}); + +var pixel; + +map.getView().on('change', function() { + pixel = null; +}); + +map.on('click', function(ev) { + pixel = map.getPixelFromCoordinate(ev.coordinate); + raster.changed(); +}); + +raster.on('beforeoperations', function(event) { + // the event.data object will be passed to operations + var data = event.data; + data.delta = thresholdControl.value; + data.pixel = pixel; +}); + +var thresholdControl = document.getElementById('threshold'); + +function updateControlValue() { + document.getElementById('threshold-value').innerText = thresholdControl.value; +} +updateControlValue(); + +thresholdControl.addEventListener('input', function() { + updateControlValue(); + raster.changed(); +});