首页 > 解决方案 > Ionic Angular Leaflet - 许多 svg 标记的高性能渲染

问题描述

我想在我的地图上渲染许多自定义(svg)标记。在我最初的研究之后,我发现了几种方法,但这些方法似乎都不适用于我的情况。我正在使用ionic/angular 5.0.0和传单1.7.1

这就是我所拥有的:

displayMarkers(foos: Foo[]) {
    // Transform foos into clickable markers
    this.markers = foos.map((foo) => {
        const i = icon({
            iconUrl: '/assets/img/icon.svg', // I'd like to use this svg for my markers
            iconSize: [20, 20], // size of the icon
        });
        const marker = circleMarker([foo.lat, foo.long]); // ADD ICON HERE

        return marker.on('click', () => this.onSelectMarker(foo, marker));
    });
    // Filter markers only in bounds of the map
    this.markers = this.markers.filter(m => this.map.getBounds().contains(m.getLatLng()));
    // Draw the markers onto the map
    this.markers.forEach(marker=> marker.addTo(this.map));
}

我想leaflet circleMarker用我的替换或自定义,svg或者找到一种高性能的方式在我的地图(数千个)中呈现大量 svg 元素。

我知道,我可以markers用来 display svg icons,但是一旦你达到几百个标记,应用程序的性能就会受到极大的影响。

我希望可以像这样初始化地图:

new Map('mapId', {preferCanvas: true})

或者能够使用自定义渲染器,如下所示:

const marker = circleMarker([foo.lat, foo.long], {renderer: canvas()});

这样,标记将被绘制到画布上,而不是被视为单个 DOM 元素。

我试图实现这个解决方案,但我无法将它正确地集成到我的 angular-typescript 应用程序中。

我还查看了这个问题并安装并测试了所有建议的库。然而,这个问题太开放了,图书馆对我来说并不满意,似乎只是为了一个简约的目的。也许我只是愚蠢地正确整合它们(不过我不想失去角度和打字稿的好处)......

我觉得这里必须有一个简单的解决方案,但我似乎找不到它。我在这里错过了什么吗?

任何帮助是极大的赞赏。谢谢!

标签: javascriptangulartypescriptleaflet

解决方案


好的,经过数小时的反复试验,我最终弄明白了。我使用并更改了几个答案和示例中的代码以适合我的特定用例。因此,如果有人对我的所作所为感到好奇,那就去吧……

为方便起见,我将所有代码放入一个文件中。

map.page.ts

@Component({
selector: 'app-map',
templateUrl: './map.page.html',
styleUrls: ['./map.page.scss'],
})
export class MapPage implements OnInit {
    map: Map; // Leaflet map
    userLocation: Marker; // Leaflet marker
    foos$: Observable<Foo[]>; // Your data
    // Some other variables ...

    constructor(
        private geocoder: NativeGeocoder,
        private fooStore: Store<fromFoo.FooState>,
        //... 
    ) {}

    ionViewDidEnter() {
        this.map = this.getInitialMap(); // Init map
        this.fooStore.dispatch(...); // Load foos to display
        this.foos$ = this.fooStore.pipe(select(fromFoo.getFoos));
        this.foos$.subscribe(foos => {
            if (foos && foos.length > 0) {
                this.displayFoos(foos);
            }
        });
        // Some more stuff here...
    }

    getInitialMap() {
        const layer = tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
            maxZoom: 19
        });
        return new Map('mapId', {
            zoomControl: !Browser.mobile,
            layers: [layer],
            center: [???, ???], // Define arbitrary location
            zoom: 19,
        });
    }

   
    displayFoos(foos: Foo[]) {
        const renderer = new Canvas(); // Important! use a canvas to render your data
        // Map the foos (your data) to leaflet markers
        const fooMarkers = foos.map((foo) => 
              new CustomMarker([foo.lat, foo.long], {renderer})
        ); // Note the CustomMarker here (See below for implementation)
        // Draw the markers onto the map
        fooMarkers.forEach(fooMarker => fooMarker.addTo(this.map));
    }

    // More functions here...
}

// This is important!
// Create a class for your custom markers that extend the CircleMarker from Leaflet
class CustomMarker extends CircleMarker {
    _updatePath() { // Make sure to name it "_updatePath()"
        // @ts-ignore
        this._renderer._displayCustomSVG(this); // Call the _renderer, which
        // to my understanding is a property on a circle marker that
        // refers to a canvas. You can extend the Canvas by your 
        // own custom render function (see below)
    }
}

const imageBitmap = new Image(); // Create a bitmap. Found on another answer
// I defined the new image outside the _displayCustomSVG to increase performance.
// That way the image instance is only created once, rather than foo.length times.

// Include ("extend") the leaflet canvas by your custom render function
Canvas.include({
    _displayCustomSVG(layer) {
        if (!this._drawing || layer._empty()) {
            return;
        }
        const p = layer._point;
        // Insert your own svg as string. (I stripped the svg here)
        const svgStr = `<svg width="10px" height="10px" viewBox="0.0 0.0 100.0 113.75853018372703" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"></svg>`;
        // (I used an online editor to upload my svg and turn it
        // into an svg string.)
        // You might want to change the width or height of your svg
  
        imageBitmap.src = 'data:image/svg+xml;base64,' + window.btoa(svgStr);

        const ctx = this._ctx;
        imageBitmap.onload = ctx.drawImage(imageBitmap, p.x, p.y);
    },
});

这是结果: 在此处输入图像描述

这对我有用,但是我不知道是否有更高性能或更好的方法来做到这一点。无论如何,我希望它有所帮助。

编辑

我意识到,如果你把它放在const imageBitmap = new Image();外面,_displayCustomSVG()你可能会遇到与drawImage传单中的功能不一致的地方。


推荐阅读