maplibre-gl-leaflet icon indicating copy to clipboard operation
maplibre-gl-leaflet copied to clipboard

working with vue 3

Open MicahDavid opened this issue 2 years ago • 5 comments

Hi,

I'd like to use this leaflet plugin, but I"m having trouble trying to figure out how to set this up in Vue3 components.

I import the Leaflet plugin like this: import L from "leaflet"

I have added maplibre-gl to the project, but i'm not sure how to import this and the maplibre-gl-leaflet plugin in my component.

I tried the standard approach of adding the cdn package sin the

, but that wasn't working for me. Any suggestions here?

MicahDavid avatar Dec 05 '22 15:12 MicahDavid

This is not currently supported due to how the plugin is structured. I think it's a bit problematic, but I haven't had the chance to look into fixing it.

The issue itself is actually that it doesn't support any system that uses any kind of build system vs overwriting the global L.

lseelenbinder avatar Jan 02 '23 17:01 lseelenbinder

So this library does not work with Vue3 ?

koufopoulosf avatar Apr 21 '23 14:04 koufopoulosf

We use this in vue3, currently going well

zachzhao1984 avatar Oct 13 '23 09:10 zachzhao1984

We use this in vue3, currently going well

Awesome, thanks for letting me know! 🙏

koufopoulosf avatar Oct 13 '23 09:10 koufopoulosf

Here is a typescript ESM version I use with vite build system:

MaplibreLayer.ts:

import { Layer, setOptions, DomEvent, DomUtil, latLngBounds, Util, extend, Point, LatLngBounds } from 'leaflet';
import mapLibre, { Map, MapOptions } from 'maplibre-gl';

type LeafletMaplibreGLOptions = Omit<MapOptions, "container">;

interface MaplibreGL extends Layer {
    getMaplibreMap(): Map
    getCanvas(): HTMLCanvasElement
    getSize(): Point
    getBounds(): LatLngBounds
    getContainer(): HTMLDivElement
    getPaneName(): string
}

const MaplibreLayer  = Layer.extend({
        options: {
        updateInterval: 32,
        // How much to extend the overlay view (relative to map size)
        // e.g. 0.1 would be 10% of map view in each direction
        padding: 0.1,
        // whether or not to register the mouse and keyboard
        // events on the maplibre overlay
        interactive: false,
        // set the tilepane as the default pane to draw gl tiles
        pane: 'tilePane'
    },

    initialize: function (options: any) {
        setOptions(this, options);

        // setup throttling the update event when panning
        this._throttledUpdate = Util.throttle(this._update, this.options.updateInterval, this);
    },

    onAdd: function (map: any) {
        if (!this._container) {
            this._initContainer();
        }

        var paneName = this.getPaneName();
        map.getPane(paneName)!.appendChild(this._container);

        this._initGL();

        this._offset = this._map.containerPointToLayerPoint([0, 0]);

        // work around https://github.com/mapbox/mapbox-gl-leaflet/issues/47
        if (map.options.zoomAnimation) {
            DomEvent.on(map._proxy, DomUtil.TRANSITION_END, this._transitionEnd, this);
        }
    },

    onRemove: function (map: any) {
        if (this._map._proxy && this._map.options.zoomAnimation) {
            DomEvent.off(this._map._proxy, DomUtil.TRANSITION_END, this._transitionEnd, this);
        }
        var paneName = this.getPaneName();
        map.getPane(paneName).removeChild(this._container);

        this._glMap.remove();
        this._glMap = null;
    },

    getEvents: function () {
        return {
            move: this._throttledUpdate, // sensibly throttle updating while panning
            zoomanim: this._animateZoom, // applys the zoom animation to the <canvas>
            zoom: this._pinchZoom, // animate every zoom event for smoother pinch-zooming
            zoomstart: this._zoomStart, // flag starting a zoom to disable panning
            zoomend: this._zoomEnd,
            resize: this._resize
        };
    },

    getMaplibreMap: function () {
        return this._glMap;
    },

    getCanvas: function () {
        return this._glMap.getCanvas();
    },

    getSize: function () {
        return this._map.getSize().multiplyBy(1 + this.options.padding * 2);
    },

    getBounds: function () {
        var halfSize = this.getSize().multiplyBy(0.5);
        var center = this._map.latLngToContainerPoint(this._map.getCenter());
        return latLngBounds(
            this._map.containerPointToLatLng(center.subtract(halfSize)),
            this._map.containerPointToLatLng(center.add(halfSize))
        );
    },

    getContainer: function () {
        return this._container;
    },

    // returns the pane name set in options if it is a valid pane, defaults to tilePane
    getPaneName: function () {
        return this._map.getPane(this.options.pane) ? this.options.pane : 'tilePane';
    },

    _roundPoint: function(p:any) {
        return {x: Math.round(p.x), y: Math.round(p.y)};
    },

    _initContainer: function () {
        var container = this._container = DomUtil.create('div', 'leaflet-gl-layer');

        var size = this.getSize();
        var offset = this._map.getSize().multiplyBy(this.options.padding);
        container.style.width  = size.x + 'px';
        container.style.height = size.y + 'px';

        var topLeft = this._map.containerPointToLayerPoint([0, 0]).subtract(offset);

        DomUtil.setPosition(container, this._roundPoint(topLeft));
    },

    _initGL: function () {
        var center = this._map.getCenter();

        var options = extend({}, this.options, {
            container: this._container,
            center: [center.lng, center.lat],
            zoom: this._map.getZoom() - 1,
            attributionControl: false
        });

        this._glMap = new mapLibre.Map(options);

        // allow GL base map to pan beyond min/max latitudes
        this._glMap.transform.latRange = null;
        this._glMap.transform.maxValidLatitude = Infinity;

        this._transformGL(this._glMap);

        if (this._glMap._canvas.canvas) {
            // older versions of mapbox-gl surfaced the canvas differently
            this._glMap._actualCanvas = this._glMap._canvas.canvas;
        } else {
            this._glMap._actualCanvas = this._glMap._canvas;
        }

        // treat child <canvas> element like L.ImageOverlay
        var canvas = this._glMap._actualCanvas;
        DomUtil.addClass(canvas, 'leaflet-image-layer');
        DomUtil.addClass(canvas, 'leaflet-zoom-animated');
        if (this.options.interactive) {
            DomUtil.addClass(canvas, 'leaflet-interactive');
        }
        if (this.options.className) {
            DomUtil.addClass(canvas, this.options.className);
        }
    },

    _update: function (e:any) {
        // update the offset so we can correct for it later when we zoom
        this._offset = this._map.containerPointToLayerPoint([0, 0]);

        if (this._zooming) {
            return;
        }

        var size = this.getSize(),
            container = this._container,
            gl = this._glMap,
            offset = this._map.getSize().multiplyBy(this.options.padding),
            topLeft = this._map.containerPointToLayerPoint([0, 0]).subtract(offset);

        DomUtil.setPosition(container, this._roundPoint(topLeft));

        this._transformGL(gl);

        if (gl.transform.width !== size.x || gl.transform.height !== size.y) {
            container.style.width  = size.x + 'px';
            container.style.height = size.y + 'px';
            if (gl._resize !== null && gl._resize !== undefined){
                gl._resize();
            } else {
                gl.resize();
            }
        } else {
            // older versions of mapbox-gl surfaced update publicly
            if (gl._update !== null && gl._update !== undefined){
                gl._update();
            } else {
                gl.update();
            }
        }
    },

    _transformGL: function (gl:any) {
        var center = this._map.getCenter();

        // gl.setView([center.lat, center.lng], this._map.getZoom() - 1, 0);
        // calling setView directly causes sync issues because it uses requestAnimFrame

        var tr = gl.transform;
        tr.center = mapLibre.LngLat.convert([center.lng, center.lat]);
        tr.zoom = this._map.getZoom() - 1;
    },

    // update the map constantly during a pinch zoom
    _pinchZoom: function (e:any) {
        this._glMap.jumpTo({
            zoom: this._map.getZoom() - 1,
            center: this._map.getCenter()
        });
    },

    // borrowed from L.ImageOverlay
    // https://github.com/Leaflet/Leaflet/blob/master/src/layer/ImageOverlay.js#L139-L144
    _animateZoom: function (e:any) {
        var scale = this._map.getZoomScale(e.zoom);
        var padding = this._map.getSize().multiplyBy(this.options.padding * scale);
        var viewHalf = this.getSize()._divideBy(2);
        // corrections for padding (scaled), adapted from
        // https://github.com/Leaflet/Leaflet/blob/master/src/map/Map.js#L1490-L1508
        var topLeft = this._map.project(e.center, e.zoom)
            ._subtract(viewHalf)
            ._add(this._map._getMapPanePos()
            .add(padding))._round();
        var offset = this._map.project(this._map.getBounds().getNorthWest(), e.zoom)
            ._subtract(topLeft);

        DomUtil.setTransform(
            this._glMap._actualCanvas,
            offset.subtract(this._offset),
            scale
        );
    },

    _zoomStart: function (e:any) {
        this._zooming = true;
    },

    _zoomEnd: function () {
        var scale = this._map.getZoomScale(this._map.getZoom());

        DomUtil.setTransform(
            this._glMap._actualCanvas,
            // https://github.com/mapbox/mapbox-gl-leaflet/pull/130
            new Point(0, 0),
            scale
        );

        this._zooming = false;

        this._update();
    },

    _transitionEnd: function (e:any) {
        Util.requestAnimFrame(function (this: any) {
            var zoom = this._map.getZoom();
            var center = this._map.getCenter();
            var offset = this._map.latLngToContainerPoint(
                this._map.getBounds().getNorthWest()
            );

            // reset the scale and offset
            DomUtil.setTransform(this._glMap._actualCanvas, offset, 1);

            // enable panning once the gl map is ready again
            this._glMap.once('moveend', Util.bind(function (this: any) {
                this._zoomEnd();
            }, this));

            // update the map position
            this._glMap.jumpTo({
                center: center,
                zoom: zoom - 1
            });
        }, this);
    },

    _resize: function (e:any) {
        this._transitionEnd(e);
    }
});

export function maplibreLayer(options: LeafletMaplibreGLOptions) : MaplibreGL{
    return new (MaplibreLayer as any)(options);
}

Import and use like:

import { maplibreLayer } from './MaplibreLayer';
maplibreLayer({ 
        style: `http://....`,
}).addTo(map)

duswie avatar Jan 31 '24 16:01 duswie