worker
maptalks支持用户自定义worker 来处理密集型的数据或者计算
maptalks的worker和我们平时的直接
js
var myWorker = new Worker("worker.js");
还是有点不同的,maptalks的自定义worker其实自定义 Snippets
(代码片段)
在maptalks内部:
- 有个worker线程池
- 线程池对worker进行任务调度
- 我们注入的
Snippets
其实是个片段函数,maptalks内部根据worker的key,将属于这个worker的任务发给他
使用方法
- 写个代码函数
- 注入worker
- 创建
Actor
Actor
发送数据
js
//worker code
const fun = function () {
//worker init
exports.initialize = function () {
console.log("tileimagebitmap init");
};
//recive message
exports.onmessage = function (msg, postResponse) {
//do some things
postResponse(
null,
{ image },
[image], //[data] Transferable object
);
};
};
const workerKey = "tileimagebitmap";
//注入worker代码片段
maptalks.registerWorkerAdapter(workerKey, fun1);
const actor = new maptalks.worker.Actor(workerKey);
//向worker里发送数据
actor.send(
{ url: maptalks.Util.getAbsoluteURL(url) }, //send data
null, //[data] Transferable object
(error, message) => {
callback(message.image);
},
);
TIP
worker 数据传输时支持 Transferable object
WARNING
worker不支持动态引入(improtScript)第三方包的,如果在worker里你需要引入第三包你可以:
- 不复杂的包直接手动粘贴进行
- 利用打包工具,动态构造worker的代码
- 比如 maptalks.three里就使用了
rollup
打的包,然后把打包的结果动态的注入到worker
maptalks.three worker code
演示内容
TIP
例子用到了个颜色插值的库colorin
这里我们演示在worker
请求地形数据瓦片,然后根据地形的高度自自动配置不同的颜色值,从而形成一个简单的色斑图
准备worker代码
WARNING
颜色插值的库colorin代码我是手动粘贴进来的,如果你的worker 很复杂应该使用打包工具
js
const fun1 = function (exports) {
/*!
* colorin v0.6.0
*/
(function (global, factory) {
typeof exports === "object" && typeof module !== "undefined"
? factory(exports)
: typeof define === "function" && define.amd
? define(["exports"], factory)
: ((global = global || self),
factory((global.colorin = global.colorin || {})));
})(this, function (exports) {
"use strict";
let canvas;
const OPTIONS = {
width: 100,
height: 10,
};
// const ISNODE = typeof global === 'object';
let offscreenCanvas = false;
try {
const canvas = new OffscreenCanvas(1, 1);
const ctx = canvas.getContext("2d");
ctx.fillText("hello", 0, 0);
offscreenCanvas = true;
} catch (err) {
offscreenCanvas = false;
}
function getCanvas() {
if (!canvas) {
const { width, height } = OPTIONS;
if (offscreenCanvas) {
canvas = new OffscreenCanvas(width, height);
} else {
canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
}
}
return canvas;
}
function registerCanvas(canvasInstance) {
if (canvasInstance) {
canvas = canvasInstance;
}
}
class ColorIn {
constructor(colors, options = {}) {
if (!Array.isArray(colors)) {
console.error("colors is not array");
return;
}
if (colors.length < 2) {
console.error("colors.length should >1");
return;
}
this.colors = colors;
let min = Infinity,
max = -Infinity;
for (let i = 0, len = colors.length; i < len; i++) {
const value = colors[i][0];
min = Math.min(value, min);
max = Math.max(value, max);
}
this.min = min;
this.max = max;
this.valueOffset = this.max - this.min;
this.options = Object.assign({}, OPTIONS, options);
this._initImgData();
}
getImageData() {
return this.imgData;
}
_initImgData() {
const canvas = getCanvas();
const { width, height } = this.options;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
const gradient = ctx.createLinearGradient(
0,
0,
canvas.width,
0,
);
const { colors, valueOffset } = this;
for (let i = 0, len = colors.length; i < len; i++) {
const [stop, color] = colors[i];
const s = (stop - this.min) / valueOffset;
gradient.addColorStop(s, color);
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
this.imgData = ctx.getImageData(
0,
0,
canvas.width,
canvas.height,
);
}
getColor(stop) {
stop = Math.max(this.min, stop);
stop = Math.min(stop, this.max);
const s = (stop - this.min) / this.valueOffset;
let x = Math.round(s * this.imgData.width);
x = Math.min(x, this.imgData.width - 1);
const idx = x * 4;
const r = this.imgData.data[idx];
const g = this.imgData.data[idx + 1];
const b = this.imgData.data[idx + 2];
const a = this.imgData.data[idx + 3];
return [r, g, b, a];
}
}
exports.registerCanvas = registerCanvas;
exports.ColorIn = ColorIn;
Object.defineProperty(exports, "__esModule", { value: true });
});
//# sourceMappingURL=colorin.js.map
exports.initialize = function () {
console.log("tileimagebitmap init");
};
const colors1 = [
[0, "#FFF7ED"],
[50, "#FEE9C9"],
[100, "#FDD59F"],
[150, "#FDBC85"],
[200, "#FC8E58"],
[240, "#EF6546"],
[300, "#D82C19"],
[400, "#B40000"],
[500, "#800000"],
];
const ci = new colorin.ColorIn(colors1);
var canvas;
var tempCanvas;
var notfindImage;
const TILESIZE = 256;
function get404Image() {
if (notfindImage) {
return notfindImage;
}
if (!notfindImage) {
canvas = createCanvas();
canvas.width = TILESIZE;
canvas.height = TILESIZE;
}
var ctx = canvas.getContext("2d");
ctx.font = "bold 48px serif";
ctx.textAlign = "center";
ctx.strokeStyle = "gray";
ctx.fillText("404", canvas.width / 2, canvas.height / 2);
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.stroke();
notfindImage = canvas.transferToImageBitmap();
return notfindImage;
}
function createCanvas() {
return new OffscreenCanvas(1, 1);
}
function clearCanvas(ctx) {
const canvas = ctx.canvas;
const { width, height } = canvas;
ctx.clearRect(0, 0, width, height);
}
function colorTerrain(imgdata) {
const data = imgdata.data;
for (let i = 0, len = data.length; i < len; i += 4) {
const R = data[i],
G = data[i + 1],
B = data[i + 2],
A = data[i + 3];
let height = 0;
//地形解码
height = -10000 + (R * 256 * 256 + G * 256 + B) * 0.1;
height = Math.max(height, 0);
const [r, g, b] = ci.getColor(height);
//根据不同的高度设置不同的颜色
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
data[i + 3] = 255;
}
}
exports.onmessage = function (msg, postResponse) {
const url = msg.data.url;
tempCanvas = tempCanvas || createCanvas();
tempCanvas.width = TILESIZE;
tempCanvas.height = TILESIZE;
const tempCtx = tempCanvas.getContext("2d");
clearCanvas(tempCtx);
//fetch image
fetch(url)
.then((res) => res.arrayBuffer())
.then((arrayBuffer) => {
const blob = new Blob([arrayBuffer]);
createImageBitmap(blob)
.then((bitmap) => {
const { width, height } = bitmap;
tempCtx.drawImage(bitmap, 0, 0);
const imgdata = tempCtx.getImageData(
0,
0,
width,
height,
);
colorTerrain(imgdata);
canvas = canvas || createCanvas();
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
clearCanvas(ctx);
ctx.putImageData(imgdata, 0, 0);
const image = canvas.transferToImageBitmap();
postResponse(null, { image }, [image]);
})
.catch((error) => {
createImageBitmap(get404Image()).then((image) => {
postResponse(null, { image }, [image]);
});
});
})
.catch((error) => {
createImageBitmap(get404Image()).then((image) => {
postResponse(null, { image }, [image]);
});
});
};
};
准备主线程的代码
js
const workerKey = "tileimagebitmap";
maptalks.registerWorkerAdapter(workerKey, fun1);
const actor = new maptalks.worker.Actor(workerKey);
const baseLayer = new maptalks.TileLayer("base", {
urlTemplate: "./../assets/data/tile-rgb/{z}/{x}/{y}.png",
subdomains: ["a", "b", "c", "d"],
bufferPixel: 1,
});
baseLayer.on("renderercreate", function (e) {
//load tile image
// img(Image): an Image object
// url(String): the url of the tile
e.renderer.loadTileBitmap = function (url, tile, callback) {
actor.send(
{ url: maptalks.Util.getAbsoluteURL(url) },
null,
(error, message) => {
callback(message.image);
},
);
};
});
var map = new maptalks.Map("map", {
center: [120.05409411, 30.16489579],
zoom: 12.809955844160632,
pitch: 0,
bearing: 0,
baseLayer,
});
WARNING
该例子依赖 OffscreenCanvas 注意必要的兼容性
总结
自定义worker一般业务开发是用不到,其属于高级功能,面向的是:
- 插件开发者
- 追求极致性能和体验的开发者
- 业务比较特殊,里面涉及到大量的密集型计算等
思考?
如果将这个效果和地形结合会产生怎样的效果呢?即改变地形的纹理图片?