16

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%' }} />
    );
  }
}
Alex Netkachov
  • 13,172
  • 6
  • 53
  • 85

3 Answers3

10

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

Vadim Gremyachev
  • 57,952
  • 20
  • 129
  • 193
  • 2
    That preferCanvas is a great thing. Thanks for letting us know. – Anant Lalchandani Jun 10 '20 at 14:08
  • Hi, do you think having a custom marker, and update it's size depending on zoom level would bring similar performances? CircleMarker takes a "radius" input, which makes it automatically resized but it's hard to achieve with a custom shape. Basically I wonder if your solution scales if a rerender of the marker is needed at each pan or at least zoom. – Eric Burel Jul 16 '20 at 12:13
  • Unfortunately this solution (including the stackblitz demo) is terribly laggy, especially as you zoom out and pan around. One reason is that every time you fire map `moveend`, you return a new array of markers through the use of `filter` and `map`, and react-leaflet will draw all those markers again. This is the case even with react 18 and react-leaflet 4. Memoizing each rendered marker according to a unique id helps, but even so, the react handlers that wrap the leaflet L.marker seem to slow down the rendering significantly, and I still don't understand why – Seth Lutske Dec 19 '22 at 23:45
1

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.

Scott Daniel
  • 1,087
  • 12
  • 18
0

How I solved it was by overriding shouldComponentUpdate lifeCycle method, The issue with a component like maps are they are usually too heavy. So if you have a lot of markers and maps lee[pm rerendering you should override the shouldComponent lifecycle method. This way you ensure that the map is re-rendered(expensive operation) only when the necessary properties are changed. It will help.

shouldComponentUpdate(prevProps) {
 return !areEqual(this.props, prevProps);
}
  • 1
    Hi Ishaan, I am specifically using your awesome enhanced markers. The problem is that you may have legitimate prop change, like updating the dimension of the marker depending on the zoom level – Eric Burel Jul 16 '20 at 12:02