Slow Performance для фильтрации маркеров в реактивном листе - PullRequest
0 голосов
/ 11 декабря 2018

Мне нужен совет для порта листовки с реактивными листами.Я генерирую маркеры на карте и использую кластеризацию маркеров с помощью response-leaflet-markercluster.Каждые маркерные данные связаны с некоторыми данными.Я хочу отфильтровать эти данные на основе маркеров в области просмотра.

Моя идея: получить границы карты и перепроверить с каждым маркером.Да, это работает.Но производительность крайне низкая (> 4,5 секунды для расчета) при добавлении более 500 маркеров.

Что можно сделать, чтобы увеличить производительность?

Вот мой код:

import React, { Component, Fragment } from 'react';
import CustomMarkers from './components/CustomMarkers';
import { Map, TileLayer } from 'react-leaflet';
import ImageContainer from './components/ImageContainer';
import { checkIfMarkerOnMap, createSampleData } from './utils/helpers';
import L from 'leaflet';

class App extends Component {
  constructor(props){
    super(props);
    this.state = {
      viewport: {
        width: '100%',
        height: '400px',
        latitude: 40.00,
        longitude: 20.00,
        zoom: 5
      },
      visibleMarkers: {},
      markers : {},
    }
  }

  componentDidMount = () => {
    const sampleData = createSampleData(1000);
    this.setState({ markers: sampleData, visibleMarkers: sampleData });
    const mapBoundaries = this.mapRef.contextValue.map.getBounds();
    this.setState({ mapBoundaries });
  }

  getMapBoundaries = () => {
    // Get map boundaries
    const mapBoundaries = this.mapRef.contextValue.map.getBounds();
    if(this.state.mapBoundaries !== mapBoundaries){
      console.log("different");
      this.setState({ mapBoundaries } );
    } else return;
  }

  checkVisibleMarkers = () => {
    console.time("checkVisibleMarkers");
    const { markers, mapBoundaries } = this.state;
    let visibleMarkers = Object.keys(markers)
      .filter(key => (L.latLngBounds([[mapBoundaries._southWest.lat, mapBoundaries._southWest.lng], [mapBoundaries._northEast.lat, mapBoundaries._northEast.lng]]).contains([markers[key].coordinates.latitude,markers[key].coordinates.longitude])))
      .map(key => { return { [key] : markers[key] } });
    visibleMarkers = Object.assign({}, ...visibleMarkers);
    console.log("visibleMarkers", visibleMarkers);
    // this.setState({ visibleMarkers })
    console.timeEnd("checkVisibleMarkers");
  }

  handleViewportChanged = () => {
    this.getMapBoundaries();
    this.checkVisibleMarkers();
  }

  render() {
    console.log("this.mapRef", this.mapRef);
    const { viewport, markers, visibleMarkers } = this.state;
    const position = [viewport.latitude, viewport.longitude]
     return (
       <Fragment> 
        <Map 
          ref={(ref) => { this.mapRef = ref }} 
          center={position} 
          zoom={viewport.zoom}
          maxZoom={15}
          onViewportChanged={() => this.handleViewportChanged()} 
          style={{ height: '400px' }}>
          <TileLayer
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
            attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
          />
            <CustomMarkers visibleMarkers={markers} />
        </Map>
        {/* <ImageContainer visibleMarkers={visibleMarkers} /> */}
      </Fragment>
    )
  }
}
export default App;

CustomMarker.js:

import React, { Component } from 'react';
import { Marker, Tooltip } from 'react-leaflet';
import uuid from 'uuid-v4';
import { 
    heartIcon, 
    heartIconYellow, 
    heartIconLightblue, 
    heartIconDarkblue } from './../icons/icons';
import MarkerClusterGroup from 'react-leaflet-markercluster';
import L from 'leaflet';


class CustomMarkers extends Component {
    render() {
        const { visibleMarkers } = this.props;
        let markers; 
        if(Object.keys(visibleMarkers).length > 0) {
            markers = Object.keys(visibleMarkers).map(key => {
                let latitude = visibleMarkers[key].coordinates.latitude;
                let longitude = visibleMarkers[key].coordinates.longitude;
                let icon = heartIcon;
                if(visibleMarkers[key].category === 'fb') icon = heartIconLightblue;
                if(visibleMarkers[key].category === 'blogs') icon = heartIconYellow;
                if(visibleMarkers[key].category === 'artisan') icon = heartIcon;
                if(visibleMarkers[key].category === 'website') icon = heartIconDarkblue;
                return (
                    <Marker 
                        key={uuid()}
                        position={ [latitude, longitude] } 
                        icon={icon}    
                    >
                        <Tooltip>{visibleMarkers[key].category}</Tooltip>
                    </Marker>
                    )
            }); 
        }

        const createClusterCustomIcon = (cluster) => {
            return L.divIcon({
              html: `<span>${cluster.getChildCount()}</span>`,
              className: 'marker-cluster-custom',
              iconSize: L.point(40, 40, true),
            });
          }
        return (
            <MarkerClusterGroup 
                iconCreateFunction={createClusterCustomIcon}
                disableClusteringAtZoom={10} 
                zoomToBoundsOnClick={true}
                spiderfyOnMaxZoom={false} 
                removeOutsideVisibleBounds={true}
                maxClusterRadius={150}
                showCoverageOnHover={false}
                >
                    {markers}
            </MarkerClusterGroup>      
        )
    }
}
export default CustomMarkers;

createSampleData принимает количество данных выборки для генерации в качестве входных данных и создает структуру jsonдля выборочных данных {id: 1 {координаты: {}, ...}

Узким местом является функция checkVisibleMarkers .Эта функция вычисляет, находится ли маркер в области просмотра. Математически это только два умножения на маркер.

1 Ответ

0 голосов
/ 11 декабря 2018

Я вижу несколько потенциальных проблем - производительность функции checkVisibleMarkers, и использование uuid() для создания уникальных (и различных) значений key накаждая повторная передача <Marker />.

checkVisibleMarkers

Относительно функции checkVisibleMarkers.Там есть несколько вызовов и шаблонов, которые можно оптимизировать.Вот что сейчас происходит:

  1. Создание массива ключей маркеров
  2. Циклическое переключение клавиш, обращение к соответствующему маркеру и фильтрация по местоположению с использованием L.latLngBounds().contains()
  3. Прокручивайте отфильтрованные ключи, чтобы создать массив объектов как {key: marker}
  4. . Используйте Object.assign(), чтобы создать объект из массива объектов

. В конце концов, мы имеемобъект с каждым значением, являющимся маркером.

Я не уверен в внутренностях L.latLngBounds, но это может быть частично причиной узкого места.Игнорируя это, я сконцентрируюсь на рефакторинге шаблона Object.assign({}, ...Object.keys().filter().map()) с использованием оператора for...in.

checkVisibleMarkers = () => {
  const visibleMarkers = {};
  const { markers, mapBoundaries } = this.state;

  for (let key in markers) {
    const marker = markers[key];
    const { latitude, longitude } = marker.coordinates;

    const isVisible = mapBoundaries.contains([latitude, longitude]);

    if (isVisible) {
      visibleMarkers[key] = marker;
    }
  }

  this.setState({ visibleMarkers });
}

Быстрая проверка jsPerf показывает, что описанный выше метод на ~ 50% быстрее, чем метод, который вы используете.используя, но он не содержит вызов L.latLngBounds().contains(), поэтому это не точное сравнение.

Я также попробовал метод, использующий Object.entries(markers).forEach(), который был немного медленнее, чем метод for...in выше.

key опора <Marker />

В компоненте <Marker /> вы используете uuid() для генерации уникальных ключей.Несмотря на свою уникальность, каждое повторное генерирование генерирует новый ключ, и каждый раз, когда изменяется ключ компонента, React создает новый экземпляр компонента.Это означает, что каждый <Marker /> воссоздается при каждом повторном запуске.

Решение состоит в том, чтобы использовать уникальный и постоянный ключ для каждого <Marker />.К счастью, похоже, у вас уже есть значение, которое будет работать для этого, key из visibleMarkers.Так что используйте это вместо:

<Marker 
  key={key}
  position={ [latitude, longitude] } 
  icon={icon}    
>
  <Tooltip>{visibleMarkers[key].category}</Tooltip>
</Marker>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...