Search code examples
javascriptvue.jsopenlayersopenlayers-3openlayers-6

OpenLayers 3 postcompose to OpenLayers 6 event in Vue SPA


I have made this codepen where I manipulate the context of the basemap of a OpenLayers map on postcompose. The problem I have is translating this working demo to a Vue SPA which uses OpenLayers 6 where I would like to link the dark mode map effect to a toggle. The postcompose event is not fired the same way for OL6 as it did for OL3 so I tried hooking onto prerender.

import TileLayer from 'ol/layer/Tile';
...
osm: new TileLayer({source: new OSM()})

In OpenLayers 3 the following code gets me a permanently dark mode map :

OSM_LAYER.on('postcompose', function (evt) {
    evt.context.globalCompositeOperation = 'color';
    evt.context.fillStyle = 'rgba(0,0,0,' + 1.0 + ')';
    evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
    evt.context.globalCompositeOperation = 'overlay';
    evt.context.fillStyle = 'rgb(' + [200,200,200].toString() + ')';
    evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
    evt.context.globalCompositeOperation = 'source-over';
    document.querySelector('canvas').style.filter="invert(99%)";
});

Currently I am trying to use getVectorContext as per this answer but I am not understanding something since my code does not give me the correct dark mode :

OSM_LAYER.on('prerender', function (evt) {
    var ctx = getVectorContext(evt).context_
    ctx.globalCompositeOperation = ‘color’;
    ctx.fillStyle = ‘rgba(0,0,0,’ + 1.0 + ‘)’;
    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    ctx.globalCompositeOperation = ‘overlay’;
    ctx.fillStyle = ‘rgb(‘ + [200,200,200].toString() + ‘)’;
    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    ctx.globalCompositeOperation = ‘source-over’;
    document.querySelector('canvas').style.filter="invert(99%)";
});

EDIT : Thanks to Mr. Mike's answer I am able to replicate the look of the OL3 dark mode. The problem now is that I actually use this basemap to create an animation where I update the time parameters of all layers in a for loop and then extract the map canvas using :

getMapCanvas () {
    var mapCanvas = document.createElement('canvas');
    var divElement = document.querySelector(".map");
    mapCanvas.width = divElement.offsetWidth;//size[0];
    mapCanvas.height = divElement.offsetHeight;//size[1];
    var 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);
            }
        }
    );
    return mapCanvas;
},

which is called only after a rendercomplete promise is resolved. Using Mr. Mike's method I get a weird white hue instead of the dark mode OSM and I think it is because of how I am exporting the canvas in the getMapCanvas function. Can someone help me out debug this?


Solution

  • Style filters only affect how the canvas appears on screen. To invert what is drawn needs a difference globalCompositeOperation with white fill.

    this.map.getLayers().getArray()[0].on(['postrender'], (evt) => {
        if ( this.darkOSM !== null && this.darkOSM === false ) {
            evt.context.globalCompositeOperation = 'color';
            evt.context.fillStyle = 'rgba(0,0,0,' + 1.0 + ')';
            evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
            evt.context.globalCompositeOperation = 'overlay';
            evt.context.fillStyle = 'rgb(' + [200,200,200].toString() + ')';
            evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
            evt.context.globalCompositeOperation = 'difference';
            evt.context.fillStyle = 'rgba(255,255,255,' + 0.9 + ')';
            evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
            evt.context.globalCompositeOperation = 'source-over';
        } 
    });
    

    To use on and un you would need:

    this.myFunction = (evt) => {
        if ( this.darkOSM !== null && this.darkOSM === false ) {
            evt.context.globalCompositeOperation = 'color';
            evt.context.fillStyle = 'rgba(0,0,0,' + 1.0 + ')';
            evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
            evt.context.globalCompositeOperation = 'overlay';
            evt.context.fillStyle = 'rgb(' + [200,200,200].toString() + ')';
            evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
            evt.context.globalCompositeOperation = 'difference';
            evt.context.fillStyle = 'rgba(255,255,255,' + 0.9 + ')';
            evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
            evt.context.globalCompositeOperation = 'source-over';
        } 
    };
    this.map.getLayers().getArray()[0].on(['postrender'], this.myFunction);
    this.map.getLayers().getArray()[0].un(['postrender'], this.myFunction);
    

    If you save a key from on it must be used with unByKey https://openlayers.org/en/latest/apidoc/module-ol_Observable.html#.unByKey