Search code examples
javascriptvue.jsasynchronouspromiseopenlayers

OpenLayers synchronous execution through promise/await in a Vue serverless app


I am trying to create a for loop that at each iteration updates the params of an OpenLayers map and once it is renderedcomplete it extracts the context of the mapcanvas and adds it to a GIF object. I need these to run synchronously as to always allow the map and layers to rendered and only then for the context to be added. My hacky solution at the moment is to use a fixed time setInterval, but as suggested in this question I should use async/await/Promises. My question is how would I go about wrapping my functions in a Promise and ensure they execute in a sequence while maintainning access to the context (this) of the vue app?

My for loop would look something like :

for(let i = 0 ; i < this.dateArraySurface10.length - 1 ; i++)
{
    waterfall([
        this.setTimeSurface10(),
        this.map.renderSync(),
        this.myCallback(),
    ],    
    function(err){
        console.log("Waterfall error : ",err);
    });
}

where the functions are :

setTimeSurface10: function () {
    if (this.currentTimeSurface10 === null) {
        this.currentTimeSurface10 = this.startTimeSurface10;
    } else if (this.currentTimeSurface10 >= this.endTimeSurface10) {
        this.currentTimeSurface10 = this.startTimeSurface10;
    } else {
        this.currentTimeSurface10 = new Date(
            this.currentTimeSurface10.setMinutes(this.currentTimeSurface10.getMinutes() + 60)
        );
    }
    this.surface10.getSource().updateParams({ TIME: this.currentTimeSurface10.toISOString().split(".")[0] + "Z" });
},
myCallback: function () {
    const mapCanvas = document.createElement('canvas');
    const divElement = document.querySelector(".map");
    mapCanvas.width = divElement.offsetWidth;//size[0];
    mapCanvas.height = divElement.offsetHeight;//size[1];
    const mapContext = mapCanvas.getContext('2d');
    Array.prototype.forEach.call(
        document.querySelectorAll('.ol-layer canvas'),
        function (canvas) {
            if (canvas.width > 0) {
                const opacity = canvas.parentNode.style.opacity;
                mapContext.globalAlpha = opacity === '' ? 1 : Number(opacity);
                const transform = canvas.style.transform;
                const matrix = transform
                                        .match(/^matrix\(([^\(]*)\)$/)[1] //eslint-disable-line
                                        .split(',')
                                        .map(Number);
                CanvasRenderingContext2D.prototype.setTransform.apply(mapContext,matrix);
                mapContext.drawImage(canvas, 0, 0);
            }
        }
    );
    this.gif.addFrame(mapCanvas, {copy:true, delay: 200});
}

Solution

  • Thanks to some help from Mr. Hocevar at OpenLayers (whom I suggest you support if you can on Github Sponsor) I got an answer for anyone interested.

    async mapToCanvasList() {
        for(let i = 0 ; i < this.dateArraySurface10.length - 1 ; i++)
        {
            this.setTimeSurface10();
            await new Promise(resolve => this.map.once('rendercomplete', resolve));
            this.myCallback();
        }
        this.gif.on('finished', function(blob) {
            window.open(URL.createObjectURL(blob));
        });
        this.gif.render();
    },
    myCallback: function () {
        const mapCanvas = document.createElement('canvas');
        const divElement = document.querySelector(".map");
        mapCanvas.width = divElement.offsetWidth;//size[0];
        mapCanvas.height = divElement.offsetHeight;//size[1];
        const mapContext = mapCanvas.getContext('2d');
        Array.prototype.forEach.call(
            document.querySelectorAll('.ol-layer canvas'),
            function (canvas) {
                if (canvas.width > 0) {
                    const opacity = canvas.parentNode.style.opacity;
                    mapContext.globalAlpha = opacity === '' ? 1 : Number(opacity);
                    const transform = canvas.style.transform;
                    const matrix = transform
                                            .match(/^matrix\(([^\(]*)\)$/)[1] //eslint-disable-line
                                            .split(',')
                                            .map(Number);
                    CanvasRenderingContext2D.prototype.setTransform.apply(mapContext,matrix);
                    mapContext.drawImage(canvas, 0, 0);
                }
            }
        );
        this.gif.addFrame(mapCanvas, {copy:true, delay: 200});
    },
    

    As you can see rendering the entire method asynchronous and adding an await promise for the rendercomplete event ensures that the loop waits and executes myCallback which adds the rendered context as a frame to the GIF object.