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';
|
import {Image as ImageLayer, Tile as TileLayer} from '../src/ol/layer.js';
|
||||||
|
|
||||||
const minVgi = 0;
|
const minVgi = 0;
|
||||||
const maxVgi = 0.25;
|
const maxVgi = 0.5;
|
||||||
const bins = 10;
|
const bins = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,7 +87,7 @@ const raster = new RasterSource({
|
|||||||
summarize: summarize,
|
summarize: summarize,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
raster.set('threshold', 0.1);
|
raster.set('threshold', 0.25);
|
||||||
|
|
||||||
function createCounts(min, max, num) {
|
function createCounts(min, max, num) {
|
||||||
const values = new Array(num);
|
const values = new Array(num);
|
||||||
|
|||||||
@@ -46,12 +46,21 @@ export function newImageData(data, width, height) {
|
|||||||
return imageData;
|
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 */
|
/* istanbul ignore next */
|
||||||
/**
|
/**
|
||||||
* Create a function for running operations. This function is serialized for
|
* Create a function for running operations. This function is serialized for
|
||||||
* use in a worker.
|
* use in a worker.
|
||||||
* @param {function(Array, Object):*} operation The operation.
|
* @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
|
* buffers, meta, imageOps, width, and height properties and returns an array
|
||||||
* buffer.
|
* buffer.
|
||||||
*/
|
*/
|
||||||
@@ -81,23 +90,24 @@ function createMinion(operation) {
|
|||||||
|
|
||||||
const numBuffers = buffers.length;
|
const numBuffers = buffers.length;
|
||||||
const numBytes = buffers[0].byteLength;
|
const numBytes = buffers[0].byteLength;
|
||||||
let output, b;
|
|
||||||
|
|
||||||
if (imageOps) {
|
if (imageOps) {
|
||||||
const images = new Array(numBuffers);
|
const images = new Array(numBuffers);
|
||||||
for (b = 0; b < numBuffers; ++b) {
|
for (let b = 0; b < numBuffers; ++b) {
|
||||||
images[b] = newWorkerImageData(
|
images[b] = newWorkerImageData(
|
||||||
new Uint8ClampedArray(buffers[b]),
|
new Uint8ClampedArray(buffers[b]),
|
||||||
width,
|
width,
|
||||||
height
|
height
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
output = operation(images, meta).data;
|
const output = operation(images, meta).data;
|
||||||
} else {
|
return output.buffer;
|
||||||
output = new Uint8ClampedArray(numBytes);
|
}
|
||||||
|
|
||||||
|
const output = new Uint8ClampedArray(numBytes);
|
||||||
const arrays = new Array(numBuffers);
|
const arrays = new Array(numBuffers);
|
||||||
const pixels = new Array(numBuffers);
|
const pixels = new Array(numBuffers);
|
||||||
for (b = 0; b < numBuffers; ++b) {
|
for (let b = 0; b < numBuffers; ++b) {
|
||||||
arrays[b] = new Uint8ClampedArray(buffers[b]);
|
arrays[b] = new Uint8ClampedArray(buffers[b]);
|
||||||
pixels[b] = [0, 0, 0, 0];
|
pixels[b] = [0, 0, 0, 0];
|
||||||
}
|
}
|
||||||
@@ -115,14 +125,13 @@ function createMinion(operation) {
|
|||||||
output[i + 2] = pixel[2];
|
output[i + 2] = pixel[2];
|
||||||
output[i + 3] = pixel[3];
|
output[i + 3] = pixel[3];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return output.buffer;
|
return output.buffer;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a worker for running operations.
|
* 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.
|
* @param {function(MessageEvent): void} onMessage Called with a message event.
|
||||||
* @return {Worker} The worker.
|
* @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
|
* @typedef {Object} ProcessorOptions
|
||||||
* @property {number} threads Number of workers to spawn.
|
* @property {number} threads Number of workers to spawn.
|
||||||
* @property {function(Array, Object):*} operation The operation.
|
* @property {Operation} operation The operation.
|
||||||
* @property {Object} [lib] Functions that will be made available to operations run in a worker.
|
* @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 {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.
|
* @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 {
|
} else {
|
||||||
threads = config.threads || 1;
|
threads = config.threads || 1;
|
||||||
}
|
}
|
||||||
const workers = [];
|
|
||||||
|
/**
|
||||||
|
* @type {Array<Worker>}
|
||||||
|
*/
|
||||||
|
const workers = new Array(threads);
|
||||||
if (threads) {
|
if (threads) {
|
||||||
for (let i = 0; i < threads; ++i) {
|
for (let i = 0; i < threads; ++i) {
|
||||||
workers[i] = createWorker(config, this._onWorkerMessage.bind(this, i));
|
workers[i] = createWorker(config, this._onWorkerMessage.bind(this, i));
|
||||||
@@ -218,17 +242,32 @@ export class Processor extends Disposable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this._workers = workers;
|
this._workers = workers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<Job>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
this._queue = [];
|
this._queue = [];
|
||||||
|
|
||||||
this._maxQueueLength = config.queue || Infinity;
|
this._maxQueueLength = config.queue || Infinity;
|
||||||
this._running = 0;
|
this._running = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Object<number, any>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
this._dataLookup = {};
|
this._dataLookup = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Job}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
this._job = null;
|
this._job = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run operation on input data.
|
* Run operation on input data.
|
||||||
* @param {Array<Array|ImageData>} inputs Array of pixels or image data
|
* @param {Array<ImageData>} inputs Array of image data.
|
||||||
* (depending on the operation type).
|
|
||||||
* @param {Object} meta A user data object. This is passed to all operations
|
* @param {Object} meta A user data object. This is passed to all operations
|
||||||
* and must be serializable.
|
* and must be serializable.
|
||||||
* @param {function(Error, ImageData, Object): void} callback Called when work
|
* @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.
|
* Add a job to the queue.
|
||||||
* @param {Object} job The job.
|
* @param {Job} job The job.
|
||||||
*/
|
*/
|
||||||
_enqueue(job) {
|
_enqueue(job) {
|
||||||
this._queue.push(job);
|
this._queue.push(job);
|
||||||
@@ -259,7 +298,10 @@ export class Processor extends Disposable {
|
|||||||
* Dispatch a job.
|
* Dispatch a job.
|
||||||
*/
|
*/
|
||||||
_dispatch() {
|
_dispatch() {
|
||||||
if (this._running === 0 && this._queue.length > 0) {
|
if (this._running || this._queue.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const job = this._queue.shift();
|
const job = this._queue.shift();
|
||||||
this._job = job;
|
this._job = job;
|
||||||
const width = job.inputs[0].width;
|
const width = job.inputs[0].width;
|
||||||
@@ -280,7 +322,9 @@ export class Processor extends Disposable {
|
|||||||
},
|
},
|
||||||
buffers
|
buffers
|
||||||
);
|
);
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const length = job.inputs[0].data.length;
|
const length = job.inputs[0].data.length;
|
||||||
const segmentLength = 4 * Math.ceil(length / 4 / threads);
|
const segmentLength = 4 * Math.ceil(length / 4 / threads);
|
||||||
for (let i = 0; i < threads; ++i) {
|
for (let i = 0; i < threads; ++i) {
|
||||||
@@ -301,8 +345,6 @@ export class Processor extends Disposable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle messages from the worker.
|
* Handle messages from the worker.
|
||||||
@@ -334,7 +376,7 @@ export class Processor extends Disposable {
|
|||||||
} else {
|
} else {
|
||||||
const length = job.inputs[0].data.length;
|
const length = job.inputs[0].data.length;
|
||||||
data = new Uint8ClampedArray(length);
|
data = new Uint8ClampedArray(length);
|
||||||
meta = new Array(length);
|
meta = new Array(threads);
|
||||||
const segmentLength = 4 * Math.ceil(length / 4 / threads);
|
const segmentLength = 4 * Math.ceil(length / 4 / threads);
|
||||||
for (let i = 0; i < threads; ++i) {
|
for (let i = 0; i < threads; ++i) {
|
||||||
const buffer = this._dataLookup[i]['buffer'];
|
const buffer = this._dataLookup[i]['buffer'];
|
||||||
@@ -388,14 +430,17 @@ export class Processor extends Disposable {
|
|||||||
*/
|
*/
|
||||||
const RasterEventType = {
|
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
|
* @event module:ol/source/Raster.RasterSourceEvent#beforeoperations
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
BEFOREOPERATIONS: 'beforeoperations',
|
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
|
* @event module:ol/source/Raster.RasterSourceEvent#afteroperations
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
@@ -424,7 +469,8 @@ export class RasterSourceEvent extends Event {
|
|||||||
/**
|
/**
|
||||||
* @param {string} type Type.
|
* @param {string} type Type.
|
||||||
* @param {import("../PluggableMap.js").FrameState} frameState The frame state.
|
* @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) {
|
constructor(type, frameState, data) {
|
||||||
super(type);
|
super(type);
|
||||||
@@ -776,7 +822,7 @@ class RasterSource extends ImageSource {
|
|||||||
* @param {import("../PluggableMap.js").FrameState} frameState The frame state.
|
* @param {import("../PluggableMap.js").FrameState} frameState The frame state.
|
||||||
* @param {Error} err Any error during processing.
|
* @param {Error} err Any error during processing.
|
||||||
* @param {ImageData} output The output image data.
|
* @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
|
* @private
|
||||||
*/
|
*/
|
||||||
onWorkerComplete_(frameState, err, output, data) {
|
onWorkerComplete_(frameState, err, output, data) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const green =
|
|||||||
'AABAAEAAAICRAEAOw==';
|
'AABAAEAAAICRAEAOw==';
|
||||||
|
|
||||||
where('Uint8ClampedArray').describe('ol.source.Raster', function () {
|
where('Uint8ClampedArray').describe('ol.source.Raster', function () {
|
||||||
let map, target, redSource, greenSource, blueSource, raster;
|
let map, target, redSource, greenSource, blueSource, layer, raster;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
target = document.createElement('div');
|
target = document.createElement('div');
|
||||||
@@ -73,6 +73,10 @@ where('Uint8ClampedArray').describe('ol.source.Raster', function () {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
layer = new ImageLayer({
|
||||||
|
source: raster,
|
||||||
|
});
|
||||||
|
|
||||||
map = new Map({
|
map = new Map({
|
||||||
target: target,
|
target: target,
|
||||||
view: new View({
|
view: new View({
|
||||||
@@ -83,11 +87,7 @@ where('Uint8ClampedArray').describe('ol.source.Raster', function () {
|
|||||||
extent: extent,
|
extent: extent,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
layers: [
|
layers: [layer],
|
||||||
new ImageLayer({
|
|
||||||
source: raster,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -366,6 +366,32 @@ where('Uint8ClampedArray').describe('ol.source.Raster', function () {
|
|||||||
view.setCenter([0, 0]);
|
view.setCenter([0, 0]);
|
||||||
view.setZoom(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 () {
|
describe('tile loading', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user