diff --git a/examples/raster.html b/examples/raster.html index b16689882f..01126e9c9f 100644 --- a/examples/raster.html +++ b/examples/raster.html @@ -12,8 +12,8 @@ docs: >

In this case, a single tiled source of imagery is used as input. - For each pixel, the Triangular Greenness Index - (TGI) + For each pixel, the Vegetaion Greenness Index + (VGI) is calculated from the input pixels. A second operation colors those pixels based on a threshold value (values above the threshold are green and those below are transparent). diff --git a/examples/raster.js b/examples/raster.js index 33630dce2f..a400a63450 100644 --- a/examples/raster.js +++ b/examples/raster.js @@ -7,68 +7,96 @@ goog.require('ol.layer.Tile'); goog.require('ol.source.BingMaps'); goog.require('ol.source.Raster'); -var minTgi = 0; -var maxTgi = 25; +var minVgi = 0; +var maxVgi = 0.25; +var bins = 10; -function tgi(pixels, data) { - var pixel = pixels[0]; + +/** + * Calculate the Vegetation Greenness Index (VGI) from an input pixel. This + * is a rough estimate assuming that pixel values correspond to reflectance. + * @param {ol.raster.Pixel} pixel An array of [R, G, B, A] values. + * @return {number} The VGI value for the given pixel. + */ +function vgi(pixel) { var r = pixel[0] / 255; var g = pixel[1] / 255; var b = pixel[2] / 255; - var value = (120 * (r - b) - (190 * (r - g))) / 2; - pixel[0] = value; - return pixels; + return (2 * g - r - b) / (2 * g + r + b); } -function summarize(pixels, data) { - var value = Math.floor(pixels[0][0]); - var counts = data.counts; - if (value >= counts.min && value < counts.max) { - counts.values[value - counts.min] += 1; - } - return pixels; -} -function color(pixels, data) { - var pixel = pixels[0]; - var value = pixel[0]; - if (value > data.threshold) { - pixel[0] = 0; - pixel[1] = 255; - pixel[2] = 0; - pixel[3] = 128; +/** + * Summarize values for a histogram. + * @param {numver} value A VGI value. + * @param {Object} counts An object for keeping track of VGI counts. + */ +function summarize(value, counts) { + var min = counts.min; + var max = counts.max; + var num = counts.values.length; + if (value < min) { + // do nothing + } else if (value >= max) { + counts.values[num - 1] += 1; } else { - pixel[3] = 0; + var index = Math.floor((value - min) / counts.delta); + counts.values[index] += 1; } - return pixels; } + +/** + * Use aerial imagery as the input data for the raster source. + */ var bing = new ol.source.BingMaps({ key: 'Ak-dzM4wZjSqTlzveKz5u0d4IQ4bRzVI309GxmkgSVr1ewS6iPSrOvOKhA-CJlm3', imagerySet: 'Aerial' }); + +/** + * Create a raster source where pixels with VGI values above a threshold will + * be colored green. + */ var raster = new ol.source.Raster({ sources: [bing], - operations: [tgi, summarize, color] + operation: function(pixels, data) { + var pixel = pixels[0]; + var value = vgi(pixel); + summarize(value, data.counts); + if (value >= data.threshold) { + pixel[0] = 0; + pixel[1] = 255; + pixel[2] = 0; + pixel[3] = 128; + } else { + pixel[3] = 0; + } + return pixel; + }, + lib: { + vgi: vgi, + summarize: summarize + } }); -raster.set('threshold', 10); +raster.set('threshold', 0.1); -function createCounts(min, max) { - var len = max - min; - var values = new Array(len); - for (var i = 0; i < len; ++i) { +function createCounts(min, max, num) { + var values = new Array(num); + for (var i = 0; i < num; ++i) { values[i] = 0; } return { min: min, max: max, - values: values + values: values, + delta: (max - min) / num }; } raster.on('beforeoperations', function(event) { - event.data.counts = createCounts(minTgi, maxTgi); + event.data.counts = createCounts(minVgi, maxVgi, bins); event.data.threshold = raster.get('threshold'); }); @@ -107,7 +135,7 @@ function schedulePlot(resolution, counts, threshold) { var barWidth = 15; var plotHeight = 150; var chart = d3.select('#plot').append('svg') - .attr('width', barWidth * (maxTgi - minTgi)) + .attr('width', barWidth * bins) .attr('height', plotHeight); var chartRect = chart[0][0].getBoundingClientRect(); @@ -124,35 +152,32 @@ function plot(resolution, counts, threshold) { bar.enter().append('rect'); - bar.attr('class', function(value, index) { - return 'bar' + (index - counts.min >= threshold ? ' selected' : ''); + bar.attr('class', function(count, index) { + var value = counts.min + (index * counts.delta); + return 'bar' + (value >= threshold ? ' selected' : ''); }) .attr('width', barWidth - 2); - bar.transition() - .attr('transform', function(value, index) { - return 'translate(' + (index * barWidth) + ', ' + - (plotHeight - yScale(value)) + ')'; - }) - .attr('height', yScale); + bar.transition().attr('transform', function(value, index) { + return 'translate(' + (index * barWidth) + ', ' + + (plotHeight - yScale(value)) + ')'; + }) + .attr('height', yScale); - bar.on('mousemove', function() { - var old = threshold; - threshold = counts.min + - Math.floor((d3.event.pageX - chartRect.left) / barWidth); - if (old !== threshold) { + bar.on('mousemove', function(count, index) { + var threshold = counts.min + (index * counts.delta); + if (raster.get('threshold') !== threshold) { raster.set('threshold', threshold); raster.changed(); } }); - bar.on('mouseover', function() { - var index = Math.floor((d3.event.pageX - chartRect.left) / barWidth); + bar.on('mouseover', function(count, index) { var area = 0; for (var i = counts.values.length - 1; i >= index; --i) { area += resolution * resolution * counts.values[i]; } - tip.html(message(index + counts.min, area)); + tip.html(message(counts.min + (index * counts.delta), area)); tip.style('display', 'block'); tip.transition().style({ left: (chartRect.left + (index * barWidth) + (barWidth / 2)) + 'px', @@ -171,5 +196,5 @@ function plot(resolution, counts, threshold) { function message(value, area) { var acres = (area / 4046.86).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ','); - return acres + ' acres at
' + value + ' TGI or above'; + return acres + ' acres at
' + value.toFixed(2) + ' VGI or above'; } diff --git a/examples/region-growing.html b/examples/region-growing.html index adcc843c27..eff326d577 100644 --- a/examples/region-growing.html +++ b/examples/region-growing.html @@ -27,7 +27,7 @@ tags: "raster, region growing" ---

-
+
diff --git a/examples/region-growing.js b/examples/region-growing.js index a393de64c3..b5ce834c4d 100644 --- a/examples/region-growing.js +++ b/examples/region-growing.js @@ -12,7 +12,7 @@ function growRegion(inputs, data) { var seed = data.pixel; var delta = parseInt(data.delta); if (!seed) { - return [image]; + return image; } seed = seed.map(Math.round); @@ -59,7 +59,7 @@ function growRegion(inputs, data) { } edge = newedge; } - return [new ImageData(outputData, width, height)]; + return new ImageData(outputData, width, height); } function next4Edges(edge) { @@ -81,8 +81,8 @@ var imagery = new ol.layer.Tile({ 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 + operation: growRegion, + // Functions in the `lib` object will be available to the operation run in // the web worker. lib: { nextEdges: next4Edges @@ -90,7 +90,7 @@ var raster = new ol.source.Raster({ }); var rasterImage = new ol.layer.Image({ - opacity: 0.8, + opacity: 0.7, source: raster }); @@ -98,8 +98,8 @@ var map = new ol.Map({ layers: [imagery, rasterImage], target: 'map', view: new ol.View({ - center: ol.proj.fromLonLat([-120, 50]), - zoom: 6 + center: ol.proj.fromLonLat([-119.07, 47.65]), + zoom: 11 }) }); diff --git a/examples/shaded-relief.js b/examples/shaded-relief.js index 175102140b..b4e276741a 100644 --- a/examples/shaded-relief.js +++ b/examples/shaded-relief.js @@ -81,7 +81,7 @@ function shade(inputs, data) { aspect = Math.atan2(dzdy, -dzdx); if (aspect < 0) { aspect = halfPi - aspect; - } else if (aspect > Math.PI / 2) { + } else if (aspect > halfPi) { aspect = twoPi - aspect + halfPi; } else { aspect = halfPi - aspect; @@ -99,7 +99,7 @@ function shade(inputs, data) { } } - return [new ImageData(shadeData, width, height)]; + return new ImageData(shadeData, width, height); } var elevation = new ol.source.XYZ({ @@ -110,7 +110,7 @@ var elevation = new ol.source.XYZ({ var raster = new ol.source.Raster({ sources: [elevation], operationType: 'image', - operations: [shade] + operation: shade }); var map = new ol.Map({ diff --git a/externs/olx.js b/externs/olx.js index c65ba8247b..2b1ad5f597 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4500,7 +4500,7 @@ olx.source.ImageVectorOptions.prototype.style; /** * @typedef {{sources: Array., - * operations: (Array.|undefined), + * operation: (ol.raster.Operation|undefined), * lib: (Object|undefined), * threads: (number|undefined), * operationType: (ol.raster.OperationType|undefined)}} @@ -4518,12 +4518,12 @@ olx.source.RasterOptions.prototype.sources; /** - * Pixel operations. Operations will be called with data from input sources - * and the final output will be assigned to the raster source. - * @type {Array.|undefined} + * Raster operation. The operation will be called with data from input sources + * and the output will be assigned to the raster source. + * @type {ol.raster.Operation|undefined} * @api */ -olx.source.RasterOptions.prototype.operations; +olx.source.RasterOptions.prototype.operation; /** diff --git a/package.json b/package.json index a0dafaeda5..f038a0b987 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "metalsmith": "1.6.0", "metalsmith-templates": "0.7.0", "nomnom": "1.8.0", - "pixelworks": "^0.11.0", + "pixelworks": "1.0.0", "rbush": "1.3.5", "temp": "0.8.1", "walk": "2.3.4", diff --git a/src/ol/source/rastersource.js b/src/ol/source/rastersource.js index c13a4c66d3..7fff3d1b3a 100644 --- a/src/ol/source/rastersource.js +++ b/src/ol/source/rastersource.js @@ -46,8 +46,8 @@ ol.source.Raster = function(options) { this.operationType_ = goog.isDef(options.operationType) ? options.operationType : ol.raster.OperationType.PIXEL; - var operations = goog.isDef(options.operations) ? - options.operations : [ol.raster.IdentityOp]; + var operation = goog.isDef(options.operation) ? + options.operation : ol.raster.IdentityOp; /** * @private @@ -59,7 +59,7 @@ ol.source.Raster = function(options) { * @private * @type {*} */ - this.worker_ = this.createWorker_(operations, options.lib, this.threads_); + this.worker_ = this.createWorker_(operation, options.lib, this.threads_); /** * @private @@ -146,16 +146,16 @@ goog.inherits(ol.source.Raster, ol.source.Image); /** * Create a worker. - * @param {Array.} operations The operations. + * @param {ol.raster.Operation} operation The operation. * @param {Object=} opt_lib Optional lib functions. * @param {number=} opt_threads Number of threads. * @return {*} The worker. * @private */ ol.source.Raster.prototype.createWorker_ = - function(operations, opt_lib, opt_threads) { + function(operation, opt_lib, opt_threads) { return new ol.ext.pixelworks.Processor({ - operations: operations, + operation: operation, imageOps: this.operationType_ === ol.raster.OperationType.IMAGE, queue: 1, lib: opt_lib, @@ -165,14 +165,14 @@ ol.source.Raster.prototype.createWorker_ = /** - * Reset the operations. - * @param {Array.} operations New operations. + * Reset the operation. + * @param {ol.raster.Operation} operation New operation. * @param {Object=} opt_lib Functions that will be available to operations run * in a worker. * @api */ -ol.source.Raster.prototype.setOperations = function(operations, opt_lib) { - this.worker_ = this.createWorker_(operations, opt_lib, this.threads_); +ol.source.Raster.prototype.setOperation = function(operation, opt_lib) { + this.worker_ = this.createWorker_(operation, opt_lib, this.threads_); this.changed(); }; diff --git a/test/spec/ol/source/rastersource.test.js b/test/spec/ol/source/rastersource.test.js index 69e8f71914..0aa680f75f 100644 --- a/test/spec/ol/source/rastersource.test.js +++ b/test/spec/ol/source/rastersource.test.js @@ -52,9 +52,9 @@ describe('ol.source.Raster', function() { raster = new ol.source.Raster({ threads: 0, sources: [redSource, greenSource, blueSource], - operations: [function(inputs) { - return inputs; - }] + operation: function(inputs) { + return inputs[0]; + } }); map = new ol.Map({ @@ -91,17 +91,17 @@ describe('ol.source.Raster', function() { expect(source).to.be.a(ol.source.Raster); }); - itNoPhantom('defaults to "pixel" operations', function(done) { + itNoPhantom('defaults to "pixel" operation', function(done) { var log = []; raster = new ol.source.Raster({ threads: 0, sources: [redSource, greenSource, blueSource], - operations: [function(inputs) { + operation: function(inputs) { log.push(inputs); - return inputs; - }] + return inputs[0]; + } }); raster.on('afteroperations', function() { @@ -127,10 +127,10 @@ describe('ol.source.Raster', function() { operationType: ol.raster.OperationType.IMAGE, threads: 0, sources: [redSource, greenSource, blueSource], - operations: [function(inputs) { + operation: function(inputs) { log.push(inputs); - return inputs; - }] + return inputs[0]; + } }); raster.on('afteroperations', function() { @@ -149,12 +149,12 @@ describe('ol.source.Raster', function() { }); - describe('#setOperations()', function() { + describe('#setOperation()', function() { - itNoPhantom('allows operations to be set', function(done) { + itNoPhantom('allows operation to be set', function(done) { var count = 0; - raster.setOperations([function(pixels) { + raster.setOperation(function(pixels) { ++count; var redPixel = pixels[0]; var greenPixel = pixels[1]; @@ -162,8 +162,8 @@ describe('ol.source.Raster', function() { expect(redPixel).to.eql([255, 0, 0, 255]); expect(greenPixel).to.eql([0, 255, 0, 255]); expect(bluePixel).to.eql([0, 0, 255, 255]); - return pixels; - }]); + return pixels[0]; + }); var view = map.getView(); view.setCenter([0, 0]); @@ -176,7 +176,7 @@ describe('ol.source.Raster', function() { }); - itNoPhantom('updates and re-runs the operations', function(done) { + itNoPhantom('updates and re-runs the operation', function(done) { var view = map.getView(); view.setCenter([0, 0]); @@ -186,9 +186,9 @@ describe('ol.source.Raster', function() { raster.on('afteroperations', function(event) { ++count; if (count === 1) { - raster.setOperations([function(inputs) { - return inputs; - }]); + raster.setOperation(function(inputs) { + return inputs[0]; + }); } else { done(); } @@ -203,10 +203,10 @@ describe('ol.source.Raster', function() { itNoPhantom('gets called before operations are run', function(done) { var count = 0; - raster.setOperations([function(inputs) { + raster.setOperation(function(inputs) { ++count; - return inputs; - }]); + return inputs[0]; + }); raster.on('beforeoperations', function(event) { expect(count).to.equal(0); @@ -224,12 +224,12 @@ describe('ol.source.Raster', function() { }); - itNoPhantom('allows data to be set for the operations', function(done) { + itNoPhantom('allows data to be set for the operation', function(done) { - raster.setOperations([function(inputs, data) { + raster.setOperation(function(inputs, data) { ++data.count; - return inputs; - }]); + return inputs[0]; + }); raster.on('beforeoperations', function(event) { event.data.count = 0; @@ -253,10 +253,10 @@ describe('ol.source.Raster', function() { itNoPhantom('gets called after operations are run', function(done) { var count = 0; - raster.setOperations([function(inputs) { + raster.setOperation(function(inputs) { ++count; - return inputs; - }]); + return inputs[0]; + }); raster.on('afteroperations', function(event) { expect(count).to.equal(4); @@ -273,12 +273,12 @@ describe('ol.source.Raster', function() { }); - itNoPhantom('receives data set by the operations', function(done) { + itNoPhantom('receives data set by the operation', function(done) { - raster.setOperations([function(inputs, data) { + raster.setOperation(function(inputs, data) { data.message = 'hello world'; - return inputs; - }]); + return inputs[0]; + }); raster.on('afteroperations', function(event) { expect(event.data.message).to.equal('hello world');