Лучший способ обновить слой MapBox на основе вызова API? - PullRequest
0 голосов
/ 04 февраля 2020

Заранее извините за длинный пример кода. Я выполняю вызов API (API Mapbox Directions) в моем componentDidMount, чтобы получить координаты точек и нарисовать маршрутную линию между несколькими координатами. В настоящее время я рисую линию на моей карте на основе координат, которые я получаю из этого вызова API, дело в том, что вызов API выполняется на основе данных, хранящихся в моем магазине Redux, если я меняю магазин, то линия, нарисованная на карте остается там (потому что componentDidMount больше не вызывается, поэтому данные для строки остаются прежними). Я не эксперт по React, и я ищу элегантный способ обновить линию sh, нарисованную на карте. Любая помощь приветствуется! Заранее спасибо.

Дэйв

import React, { Component } from 'react';
import ReactMapboxGl, { Marker } from 'react-mapbox-gl';
import marker from '../../assets/images/marker.png';
import Loading from '../Loading';
import { connect } from 'react-redux';
import ItineraryCard from '../Itinerary/ItineraryCard';
import photo1 from '../../assets/images/nav_tourisme.jpg';

const Map = ReactMapboxGl({
  accessToken:
    '...',
});

class MapWithLine extends Component {
  constructor(props) {
    super(props);
    this.state = {
      error: null,
      data: [],
      isLoaded: false,
      zoom: [13],
    };
  }

  onMapLoad = map => {
    map.addLayer({
      id: 'route',
      type: 'line',
      source: {
        type: 'geojson',
        data: {
          type: 'Feature',
          properties: {},
          geometry: {
            type: 'LineString',
            coordinates: this.state.data,
          },
        },
      },
      layout: {
        'line-join': 'round',
        'line-cap': 'round',
      },
      paint: {
        'line-color': '#888',
        'line-width': 8,
      },
    });
    map.resize();
  };

  removeLastCharacter(str) {
    if (str != null && str.length > 0) {
      str = str.substring(0, str.length - 1);
    }
    return str;
  }

  getItineraryCoords() {
    let itineraryString = '';
    this.props.itineraryReducer.itinerary.map(exp => {
      const coords = exp.lng + ',' + exp.lat + ';';
      itineraryString = itineraryString + coords;
      return itineraryString;
    });
    return this.removeLastCharacter(itineraryString);
  }

  componentDidMount() {
    if (this.props.itineraryReducer.itinerary.length > 1) {
      const url =
        'https://api.mapbox.com/directions/v5/mapbox/' +
        'driving/' +
        this.getItineraryCoords() +
        '?geometries=geojson&' +
        'access_token='...';
      fetch(url)
        .then(response => response.json())
        .then(
          result => {
            result.routes
              ? this.setState({
                  data: result.routes[0].geometry.coordinates,
                  isLoaded: true,
                })
              : this.setState({
                  isLoaded: true,
                });
          },
          error => {
            this.setState({
              isLoaded: true,
              error: error,
            });
          }
        );
    }
  }

  componentWillUnmount() {
    //Resume scrolling on body when drawer is not maximized (component unmounted)
    document.body.style.overflow = 'visible';
  }
  render() {
    const { error, isLoaded } = this.state;
    if (error) {
      return <div>Error: {error.message}</div>;
    } else if (!isLoaded) {
      return <Loading />;
    } else {
      return (
        <div className="mapWrapper">
          <Map
            style="mapbox://styles/mapbox/streets-v8"
            className="mapContainer"
            center={this.state.data[0]}
            zoom={this.state.zoom}
            onStyleLoad={this.onMapLoad}
          >
            });
            {this.props.itineraryReducer.itinerary.map((exp, index) => {
              const coords = [exp.lng, exp.lat];
              return (
                <Marker coordinates={coords} anchor="bottom" key={index}>
                  <img src={marker} alt="marker-icon" className="mapMarker" />
                </Marker>
              );
            })}
          </Map>
        </div>
      );
    }
  }
}

const mapStateToProps = state => {
  return {
    itineraryReducer: state.itineraryReducer,
  };
};

export default connect(mapStateToProps)(MapWithLine);

1 Ответ

0 голосов
/ 13 февраля 2020

Если я правильно понимаю, вы хотите отображать ответ, поступающий от API-адресов Mapbox, всякий раз, когда обновляется props.itineraryReducer, и который вы храните в state.data. Следовательно, вы можете использовать ComponentDidUpdate - метод жизненного цикла React, который вы можете использовать для воздействия на изменения в вашем компоненте (будь то состояние или подпорки) - чтобы инициировать обновление на вашей карте всякий раз, когда эти подпорки и переменные состояния изменяются. Вы можете сделать что-то вроде этого:

componentDidMount (prevProps, prevState) {
  const { data } = this.state
  const { itineraryReducer } = this.props

  if (prevProps.itineraryReducer !== itineraryReducer) {
    if (itineraryReducer.itinerary.length > 1) {
      const url =
        'https://api.mapbox.com/directions/v5/mapbox/' +
        'driving/' +
        this.getItineraryCoords() +
        '?geometries=geojson&' +
        'access_token='...';

      fetch(url)
        .then(response => response.json())
        .then(
          result => {
            result.routes
              ? this.setState({
                  data: result.routes[0].geometry.coordinates,
                  isLoaded: true,
                })
              : this.setState({
                  isLoaded: true,
                });
          },
          error => { // You should probably catch this error on a .catch() in your promise chain
            this.setState({
              isLoaded: true, 
              error: error,
            });
          }
        );
    }
  }

  if (prevState.data !== data) {
    this.addLineLayer(data) // Here is where we are actually drawing the line map layer
  }
}

addLineLayer = (coords) => {
  this.map.addLayer({
      id: 'route',
      type: 'line',
      source: {
        type: 'geojson',
        data: {
          type: 'Feature',
          properties: {},
          geometry: {
            type: 'LineString',
            coordinates: this.state.data,
          },
        },
      },
      layout: {
        'line-join': 'round',
        'line-cap': 'round',
      },
      paint: {
        'line-color': '#888',
        'line-width': 8,
      },
    });
    this.map.resize();
}

// ... (Rest of the Component)

Потенциально, в зависимости от вашей цели, вам придется удалить предыдущий существующий слой на карте, прежде чем рисовать следующий слой. Вы можете проверить эту ссылку для ссылки на вызываемый метод.

Возможно, вы также заметили, что к объекту карты обращаются в this.map, поэтому вам нужно сохранить map объект в вашем экземпляре класса при загрузке стиля. Для этого вы можете передать следующий метод в качестве атрибута onStyleLoad в <Map>:

onStyleLoad = (map) => {
  this.map = map // This saves the object map in your class instance, so that you can access it later
}

...

<Map
  style="mapbox://styles/mapbox/streets-v8"
  className="mapContainer"
  center={this.state.data[0]}
  zoom={this.state.zoom}
  onStyleLoad={this.onStyleLoad}
>

...
...