Skip to content
目录

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打的包,然后把打包的结果动态的注入到workermaptalks.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一般业务开发是用不到,其属于高级功能,面向的是:

  • 插件开发者
  • 追求极致性能和体验的开发者
  • 业务比较特殊,里面涉及到大量的密集型计算等

思考?

如果将这个效果和地形结合会产生怎样的效果呢?即改变地形的纹理图片?

This document is generated by mdpress