Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructing a lot of markers on react-leaflet map is too slow

I have a dataset of 4360 geomarkers that I want to display on the Leaflet map. CircleMarker works just fine and the performance of the constructed map is ok. However, constructing the map takes too much time (around 20 seconds). Without react it takes a fraction of second to construct the markers. Is there a some performance hint or trick that can be used to make it construct the map faster?

import * as React from 'react';
import { Component } from 'react';
import { LatLng } from 'leaflet';
import { Map, TileLayer, CircleMarker, Popup } from 'react-leaflet';

export default class Editor extends Component {
    state = {
        lat: 51.505,
        lng: -0.09,
        zoom: 13,
        markers : [ ]
    }

    componentDidMount() {
        // data.csv contains several thousands of items
        fetch('data.csv')
            .then(res => res.json())
            .then(data => this.setState({ markers: data.items.map(v => new LatLng(v.lat, v.lng)) }));
    }

    render() {
        const markers = this.state.markers.map((v, i) =>
            <CircleMarker key={i} center={v} radius={3} />);
        return (
            <Map center={new LatLng(51.505, -0.09)} zoom={this.state.zoom}>
                <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
                {markers}
            </Map>
        )
    }
}

Direct DOM manipulation does it in a fraction of a second:

export default class ItemsMap extends React.Component {
  state = { items : [ ] };
  map : L.Map;

  componentDidUpdate(prevProps : any, prevState : any) {
    this.renderItems(this.state.items);
  }

  componentDidMount() {
    const node : any = ReactDOM.findDOMNode(this);
    this.map = L.map(node).setView([51.505, -0.09], 13);
    L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18 }).addTo(this.map);

    fetch('data.csv')
      .then(res => res.json())
      .then(data => this.setState({ items: data.items.map(v => new LatLng(v.lat, v.lng)) }));
  }

  renderItems(items : Array<any>) {        
    items.forEach(item => {
      L.circleMarker(
        [ item.lat, item.lng ],
        { radius : 3 }
      ).addTo(this.map);
    });
  }

  render() {
    return (
      <div id="mapid" style={{ height: '100%' }} />
    );
  }
}
like image 260
Alex Netkachov Avatar asked Oct 03 '18 05:10

Alex Netkachov


People also ask

Can I add a Leaflet map to my react website?

Now, let’s say you have a React-based website ( check out this starter I set up for you on Stack Blitz ), and you want to add a Leaflet map to showcase your mapping skills. There is actually a React binding for Leaflet, React-Leaflet.

How slow is the Leaflet map?

The map is produced using Leaflet, which I want to publish on my blogdown site. However, with 60,000 points, the map is understandably quite slow. Here's a simplified example of what my code and map look like:

What is react-leaflet?

From the React-Leaflet docs: React Leaflet provides bindings between React and Leaflet. It does not replace Leaflet, but leverages it to abstract Leaflet layers as React components. As such, it can behave differently from how other React components work. So, let’s go over how to build a Leaflet map component inside of a React application!

How to tell if leaflet is using canvas or raster renderer?

I've been doing a big project with Leaflet over the last few months and started hitting performance problems with raster layers, so I'm interested to see how you go. You can verify whether you're actually using the Canvas renderer by right-clicking on the map. If it gives you image-related menu options, you're working with Canvas.


2 Answers

One technique to consider would be to render only a subset of markers within a given map boundary, it can dramatically reduce the time it takes to re-render the components as well as the number of DOM nodes created:

 componentDidMount() {
    fetch('data.csv')
   .then(res => res.json())
   .then(data => {
       this.allMarkers = data.items.map(v => new LatLng(v.lat, v.lng));
       displayMarkers();
    });
 }

where

displayMarkers() {
   const map = this.mapRef.current.leafletElement;
   const markers = this.allMarkers.filter(m =>
      map.getBounds().contains(m)
   );

   this.setState({
       markers: markers
   });
}

Demo

Another optimization (leaflet specific) would be to set preferCanvas to true to render markers on canvas instead of SVG:

Whether Paths should be rendered on a Canvas renderer. By default, all Paths are rendered in a SVG renderer.

<Map
    preferCanvas={true}
    center={new LatLng(51.505, -0.09)}
    zoom={this.state.zoom}
  >
    ... 
  </Map>

The following demo demonstrates how to render 20k markers via react-leaflet

like image 192
Vadim Gremyachev Avatar answered Sep 28 '22 11:09

Vadim Gremyachev


My problem was slightly different in that the points rendered in an acceptable period of time, but the pan and zoom are deathly slow. My native javascript application works fine. None of the suggestions here helped.

Why is Leaflet so slow at pan and zoom inside React?

I solved the problem by putting my native javascript map application inside an iframe on the react page. It is not an ideal solution and my hope is that a better one presents itself, but my guess is that it will require an update to either leaflet or react.

like image 39
Scott Daniel Avatar answered Sep 28 '22 12:09

Scott Daniel