Additional docs and type checking for raster source
This commit is contained in:
@@ -5,7 +5,7 @@ import XYZ from '../src/ol/source/XYZ.js';
|
||||
import {Image as ImageLayer, Tile as TileLayer} from '../src/ol/layer.js';
|
||||
|
||||
const minVgi = 0;
|
||||
const maxVgi = 0.25;
|
||||
const maxVgi = 0.5;
|
||||
const bins = 10;
|
||||
|
||||
/**
|
||||
@@ -87,7 +87,7 @@ const raster = new RasterSource({
|
||||
summarize: summarize,
|
||||
},
|
||||
});
|
||||
raster.set('threshold', 0.1);
|
||||
raster.set('threshold', 0.25);
|
||||
|
||||
function createCounts(min, max, num) {
|
||||
const values = new Array(num);
|
||||
|
||||
@@ -46,12 +46,21 @@ export function newImageData(data, width, height) {
|
||||
return imageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} MinionData
|
||||
* @property {Array<ArrayBuffer>} buffers Array of buffers.
|
||||
* @property {Object} meta Operation metadata.
|
||||
* @property {boolean} imageOps The operation is an image operation.
|
||||
* @property {number} width The width of the image.
|
||||
* @property {number} height The height of the image.
|
||||
*/
|
||||
|
||||
/* istanbul ignore next */
|
||||
/**
|
||||
* Create a function for running operations. This function is serialized for
|
||||
* use in a worker.
|
||||
* @param {function(Array, Object):*} operation The operation.
|
||||
* @return {function(Object):ArrayBuffer} A function that takes an object with
|
||||
* @return {function(MinionData):ArrayBuffer} A function that takes an object with
|
||||
* buffers, meta, imageOps, width, and height properties and returns an array
|
||||
* buffer.
|
||||
*/
|
||||
@@ -81,40 +90,40 @@ function createMinion(operation) {
|
||||
|
||||
const numBuffers = buffers.length;
|
||||
const numBytes = buffers[0].byteLength;
|
||||
let output, b;
|
||||
|
||||
if (imageOps) {
|
||||
const images = new Array(numBuffers);
|
||||
for (b = 0; b < numBuffers; ++b) {
|
||||
for (let b = 0; b < numBuffers; ++b) {
|
||||
images[b] = newWorkerImageData(
|
||||
new Uint8ClampedArray(buffers[b]),
|
||||
width,
|
||||
height
|
||||
);
|
||||
}
|
||||
output = operation(images, meta).data;
|
||||
} else {
|
||||
output = new Uint8ClampedArray(numBytes);
|
||||
const arrays = new Array(numBuffers);
|
||||
const pixels = new Array(numBuffers);
|
||||
for (b = 0; b < numBuffers; ++b) {
|
||||
arrays[b] = new Uint8ClampedArray(buffers[b]);
|
||||
pixels[b] = [0, 0, 0, 0];
|
||||
}
|
||||
for (let i = 0; i < numBytes; i += 4) {
|
||||
for (let j = 0; j < numBuffers; ++j) {
|
||||
const array = arrays[j];
|
||||
pixels[j][0] = array[i];
|
||||
pixels[j][1] = array[i + 1];
|
||||
pixels[j][2] = array[i + 2];
|
||||
pixels[j][3] = array[i + 3];
|
||||
}
|
||||
const pixel = operation(pixels, meta);
|
||||
output[i] = pixel[0];
|
||||
output[i + 1] = pixel[1];
|
||||
output[i + 2] = pixel[2];
|
||||
output[i + 3] = pixel[3];
|
||||
const output = operation(images, meta).data;
|
||||
return output.buffer;
|
||||
}
|
||||
|
||||
const output = new Uint8ClampedArray(numBytes);
|
||||
const arrays = new Array(numBuffers);
|
||||
const pixels = new Array(numBuffers);
|
||||
for (let b = 0; b < numBuffers; ++b) {
|
||||
arrays[b] = new Uint8ClampedArray(buffers[b]);
|
||||
pixels[b] = [0, 0, 0, 0];
|
||||
}
|
||||
for (let i = 0; i < numBytes; i += 4) {
|
||||
for (let j = 0; j < numBuffers; ++j) {
|
||||
const array = arrays[j];
|
||||
pixels[j][0] = array[i];
|
||||
pixels[j][1] = array[i + 1];
|
||||
pixels[j][2] = array[i + 2];
|
||||
pixels[j][3] = array[i + 3];
|
||||
}
|
||||
const pixel = operation(pixels, meta);
|
||||
output[i] = pixel[0];
|
||||
output[i + 1] = pixel[1];
|
||||
output[i + 2] = pixel[2];
|
||||
output[i + 3] = pixel[3];
|
||||
}
|
||||
return output.buffer;
|
||||
};
|
||||
@@ -122,7 +131,7 @@ function createMinion(operation) {
|
||||
|
||||
/**
|
||||
* Create a worker for running operations.
|
||||
* @param {Object} config Configuration.
|
||||
* @param {ProcessorOptions} config Processor options.
|
||||
* @param {function(MessageEvent): void} onMessage Called with a message event.
|
||||
* @return {Worker} The worker.
|
||||
*/
|
||||
@@ -177,11 +186,22 @@ function createFauxWorker(config, onMessage) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {function(Error, ImageData, (Object|Array<Object>)): void} JobCallback
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Job
|
||||
* @property {Object} meta Job metadata.
|
||||
* @property {Array<ImageData>} inputs Array of input data.
|
||||
* @property {JobCallback} callback Called when the job is complete.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ProcessorOptions
|
||||
* @property {number} threads Number of workers to spawn.
|
||||
* @property {function(Array, Object):*} operation The operation.
|
||||
* @property {Object} [lib] Functions that will be made available to operations run in a worker.
|
||||
* @property {Operation} operation The operation.
|
||||
* @property {Object<string, Function>} [lib] Functions that will be made available to operations run in a worker.
|
||||
* @property {number} queue The number of queued jobs to allow.
|
||||
* @property {boolean} [imageOps=false] Pass all the image data to the operation instead of a single pixel.
|
||||
*/
|
||||
@@ -206,7 +226,11 @@ export class Processor extends Disposable {
|
||||
} else {
|
||||
threads = config.threads || 1;
|
||||
}
|
||||
const workers = [];
|
||||
|
||||
/**
|
||||
* @type {Array<Worker>}
|
||||
*/
|
||||
const workers = new Array(threads);
|
||||
if (threads) {
|
||||
for (let i = 0; i < threads; ++i) {
|
||||
workers[i] = createWorker(config, this._onWorkerMessage.bind(this, i));
|
||||
@@ -218,17 +242,32 @@ export class Processor extends Disposable {
|
||||
);
|
||||
}
|
||||
this._workers = workers;
|
||||
|
||||
/**
|
||||
* @type {Array<Job>}
|
||||
* @private
|
||||
*/
|
||||
this._queue = [];
|
||||
|
||||
this._maxQueueLength = config.queue || Infinity;
|
||||
this._running = 0;
|
||||
|
||||
/**
|
||||
* @type {Object<number, any>}
|
||||
* @private
|
||||
*/
|
||||
this._dataLookup = {};
|
||||
|
||||
/**
|
||||
* @type {Job}
|
||||
* @private
|
||||
*/
|
||||
this._job = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run operation on input data.
|
||||
* @param {Array<Array|ImageData>} inputs Array of pixels or image data
|
||||
* (depending on the operation type).
|
||||
* @param {Array<ImageData>} inputs Array of image data.
|
||||
* @param {Object} meta A user data object. This is passed to all operations
|
||||
* and must be serializable.
|
||||
* @param {function(Error, ImageData, Object): void} callback Called when work
|
||||
@@ -246,7 +285,7 @@ export class Processor extends Disposable {
|
||||
|
||||
/**
|
||||
* Add a job to the queue.
|
||||
* @param {Object} job The job.
|
||||
* @param {Job} job The job.
|
||||
*/
|
||||
_enqueue(job) {
|
||||
this._queue.push(job);
|
||||
@@ -259,48 +298,51 @@ export class Processor extends Disposable {
|
||||
* Dispatch a job.
|
||||
*/
|
||||
_dispatch() {
|
||||
if (this._running === 0 && this._queue.length > 0) {
|
||||
const job = this._queue.shift();
|
||||
this._job = job;
|
||||
const width = job.inputs[0].width;
|
||||
const height = job.inputs[0].height;
|
||||
const buffers = job.inputs.map(function (input) {
|
||||
return input.data.buffer;
|
||||
});
|
||||
const threads = this._workers.length;
|
||||
this._running = threads;
|
||||
if (threads === 1) {
|
||||
this._workers[0].postMessage(
|
||||
{
|
||||
buffers: buffers,
|
||||
meta: job.meta,
|
||||
imageOps: this._imageOps,
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
buffers
|
||||
);
|
||||
} else {
|
||||
const length = job.inputs[0].data.length;
|
||||
const segmentLength = 4 * Math.ceil(length / 4 / threads);
|
||||
for (let i = 0; i < threads; ++i) {
|
||||
const offset = i * segmentLength;
|
||||
const slices = [];
|
||||
for (let j = 0, jj = buffers.length; j < jj; ++j) {
|
||||
slices.push(buffers[j].slice(offset, offset + segmentLength));
|
||||
}
|
||||
this._workers[i].postMessage(
|
||||
{
|
||||
buffers: slices,
|
||||
meta: job.meta,
|
||||
imageOps: this._imageOps,
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
slices
|
||||
);
|
||||
}
|
||||
if (this._running || this._queue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const job = this._queue.shift();
|
||||
this._job = job;
|
||||
const width = job.inputs[0].width;
|
||||
const height = job.inputs[0].height;
|
||||
const buffers = job.inputs.map(function (input) {
|
||||
return input.data.buffer;
|
||||
});
|
||||
const threads = this._workers.length;
|
||||
this._running = threads;
|
||||
if (threads === 1) {
|
||||
this._workers[0].postMessage(
|
||||
{
|
||||
buffers: buffers,
|
||||
meta: job.meta,
|
||||
imageOps: this._imageOps,
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
buffers
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const length = job.inputs[0].data.length;
|
||||
const segmentLength = 4 * Math.ceil(length / 4 / threads);
|
||||
for (let i = 0; i < threads; ++i) {
|
||||
const offset = i * segmentLength;
|
||||
const slices = [];
|
||||
for (let j = 0, jj = buffers.length; j < jj; ++j) {
|
||||
slices.push(buffers[j].slice(offset, offset + segmentLength));
|
||||
}
|
||||
this._workers[i].postMessage(
|
||||
{
|
||||
buffers: slices,
|
||||
meta: job.meta,
|
||||
imageOps: this._imageOps,
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
slices
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +376,7 @@ export class Processor extends Disposable {
|
||||
} else {
|
||||
const length = job.inputs[0].data.length;
|
||||
data = new Uint8ClampedArray(length);
|
||||
meta = new Array(length);
|
||||
meta = new Array(threads);
|
||||
const segmentLength = 4 * Math.ceil(length / 4 / threads);
|
||||
for (let i = 0; i < threads; ++i) {
|
||||
const buffer = this._dataLookup[i]['buffer'];
|
||||
@@ -388,14 +430,17 @@ export class Processor extends Disposable {
|
||||
*/
|
||||
const RasterEventType = {
|
||||
/**
|
||||
* Triggered before operations are run.
|
||||
* Triggered before operations are run. Listeners will receive an event object with
|
||||
* a `data` property that can be used to make data available to operations.
|
||||
* @event module:ol/source/Raster.RasterSourceEvent#beforeoperations
|
||||
* @api
|
||||
*/
|
||||
BEFOREOPERATIONS: 'beforeoperations',
|
||||
|
||||
/**
|
||||
* Triggered after operations are run.
|
||||
* Triggered after operations are run. Listeners will receive an event object with
|
||||
* a `data` property. If more than one thread is used, `data` will be an array of
|
||||
* objects. If a single thread is used, `data` will be a single object.
|
||||
* @event module:ol/source/Raster.RasterSourceEvent#afteroperations
|
||||
* @api
|
||||
*/
|
||||
@@ -424,7 +469,8 @@ export class RasterSourceEvent extends Event {
|
||||
/**
|
||||
* @param {string} type Type.
|
||||
* @param {import("../PluggableMap.js").FrameState} frameState The frame state.
|
||||
* @param {Object} data An object made available to operations.
|
||||
* @param {Object|Array<Object>} data An object made available to operations. For "afteroperations" evenets
|
||||
* this will be an array of objects if more than one thread is used.
|
||||
*/
|
||||
constructor(type, frameState, data) {
|
||||
super(type);
|
||||
@@ -776,7 +822,7 @@ class RasterSource extends ImageSource {
|
||||
* @param {import("../PluggableMap.js").FrameState} frameState The frame state.
|
||||
* @param {Error} err Any error during processing.
|
||||
* @param {ImageData} output The output image data.
|
||||
* @param {Object} data The user data.
|
||||
* @param {Object|Array<Object>} data The user data (or an array if more than one thread).
|
||||
* @private
|
||||
*/
|
||||
onWorkerComplete_(frameState, err, output, data) {
|
||||
|
||||
@@ -26,7 +26,7 @@ const green =
|
||||
'AABAAEAAAICRAEAOw==';
|
||||
|
||||
where('Uint8ClampedArray').describe('ol.source.Raster', function () {
|
||||
let map, target, redSource, greenSource, blueSource, raster;
|
||||
let map, target, redSource, greenSource, blueSource, layer, raster;
|
||||
|
||||
beforeEach(function () {
|
||||
target = document.createElement('div');
|
||||
@@ -73,6 +73,10 @@ where('Uint8ClampedArray').describe('ol.source.Raster', function () {
|
||||
},
|
||||
});
|
||||
|
||||
layer = new ImageLayer({
|
||||
source: raster,
|
||||
});
|
||||
|
||||
map = new Map({
|
||||
target: target,
|
||||
view: new View({
|
||||
@@ -83,11 +87,7 @@ where('Uint8ClampedArray').describe('ol.source.Raster', function () {
|
||||
extent: extent,
|
||||
}),
|
||||
}),
|
||||
layers: [
|
||||
new ImageLayer({
|
||||
source: raster,
|
||||
}),
|
||||
],
|
||||
layers: [layer],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -366,6 +366,32 @@ where('Uint8ClampedArray').describe('ol.source.Raster', function () {
|
||||
view.setCenter([0, 0]);
|
||||
view.setZoom(0);
|
||||
});
|
||||
|
||||
it('is passed an array of data if more than one thread', function (done) {
|
||||
const threads = 3;
|
||||
|
||||
raster = new RasterSource({
|
||||
threads: threads,
|
||||
sources: [redSource, greenSource, blueSource],
|
||||
operation: function (inputs, data) {
|
||||
data.prop = 'value';
|
||||
return inputs[0];
|
||||
},
|
||||
});
|
||||
|
||||
layer.setSource(raster);
|
||||
|
||||
raster.once('afteroperations', function (event) {
|
||||
expect(event.data).to.be.an(Array);
|
||||
expect(event.data).to.have.length(threads);
|
||||
expect(event.data[0].prop).to.equal('value');
|
||||
done();
|
||||
});
|
||||
|
||||
const view = map.getView();
|
||||
view.setCenter([0, 0]);
|
||||
view.setZoom(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tile loading', function () {
|
||||
|
||||
Reference in New Issue
Block a user