Mapbox — это мощная картографическая платформа, которая позволяет разработчикам интегрировать интерактивные и настраиваемые карты в свои веб-приложения. В этой статье мы рассмотрим, как использовать Mapbox в приложении React для отображения карт, добавления маркеров, расчета направлений и многого другого. Мы предоставим пошаговые инструкции и фрагменты кода, которые помогут вам начать работу с Mapbox в React.

Начиная

Для начала убедитесь, что у вас настроено приложение React. Если у вас его еще нет, вы можете создать новый проект React, используя create vite@latest или любой другой предпочтительный метод.

Установка зависимостей

Чтобы использовать Mapbox в React, нам нужно установить несколько зависимостей. Откройте терминал и перейдите в каталог проекта React. Затем выполните следующую команду:

npm install mapbox-gl axios @mapbox/mapbox-sdk react-icons bootstrap sass

При этом будут установлены необходимые пакеты: mapbox-gl для библиотеки Mapbox, axios для выполнения HTTP-запросов и @mapbox/mapbox-sdk для служб геокодирования.

Настройка токена доступа Mapbox

Чтобы получить доступ к API Mapbox, вам необходимо иметь токен доступа. Если у вас его нет, вы можете зарегистрировать учетную запись Mapbox на mapbox.com. Получив учетную запись, перейдите к настройкам учетной записи и создайте новый токен доступа.

Скопируйте свой токен доступа, так как он понадобится нам позже в нашем коде. Для производственных приложений рекомендуется безопасно хранить токен доступа, например, в переменной среды. В этой статье мы предполагаем, что токен доступа хранится в безопасном месте.

Импорт зависимостей:

import React, { useRef, useEffect, useState, useContext } from "react";
import "./Map.scss";
import mapboxgl from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import axios from "axios";
import MapboxGeocoding from "@mapbox/mapbox-sdk/services/geocoding";
import { FaArrowsAltV } from "react-icons/fa";
import { MdLocationOn } from "react-icons/md";
import { AiFillHome } from "react-icons/ai";

Здесь импортируются необходимые зависимости для создания карты и реализации функций геокодирования.

Инициализация состояния и ссылок:

const mapContainerRef = useRef(null);
const [mapStyle, setMapStyle] = useState("mapbox://styles/mapbox/streets-v11");
const [isFocused, setIsFocused] = useState(false);
const [origin, setOrigin] = useState("");
const destination = [88.3639, 22.5726];
const [routeGeometry, setRouteGeometry] = useState(null);
const [originCord, setOriginCord] = useState([]);
let originCoordinates = [];
const [routeInfo, setRouteInfo] = useState([]);
const [suggestions, setSuggestions] = useState([]);
const [expand, setExpand] = useState(false);
const initialItemCount = 4;
const geocodingClient = MapboxGeocoding({
  accessToken: import.meta.env.VITE_MAP_BOX_ACCESS_TOKEN,
});

Здесь объявлены переменные состояния для отслеживания различных данных, таких как стиль карты, состояние фокуса, исходный адрес, координаты пункта назначения, геометрия маршрута, исходные координаты, информация о маршруте, предложения для автозаполнения и состояние расширения для направлений. Ссылки используются для ссылки на контейнер карты.

Инициализация карты и стиль:

Этот раздел инициализирует карту Mapbox, создавая новый экземпляр mapboxgl.Map и устанавливая его начальные свойства, такие как контейнер, стиль, центральные координаты и уровень масштабирования.

Он также добавляет на карту элемент управления компасом и пользовательский маркер.

useEffect(() => {
    mapboxgl.accessToken = import.meta.env.VITE_MAP_BOX_ACCESS_TOKEN;
    const map = new mapboxgl.Map({
      container: mapContainerRef.current,
      style: mapStyle,
      center: [88.3639, 22.5726], // longitude and latitude
      zoom: 12,
      attributionControl: false,
    });

    map.on("style.load", () => {
      // Add the compass control
      const compassControl = new mapboxgl.NavigationControl({
        showCompass: true,
      });
      map.addControl(compassControl, "top-right");

      // Create a marker with a custom icon
      const marker = new mapboxgl.Marker({
        element: document.getElementById("custom-marker"),
      })
        .setLngLat([88.3639, 22.5726]) // longitude and latitude
        .addTo(map)
        .setPopup(
          new mapboxgl.Popup({ closeButton: true }).setHTML(
            `
          <div class="location-details">
            <span><strong>City:</strong> Kolkata</span><br>
            <span><strong>State:</strong> West Bengal</span><br>
            <span><strong>Country:</strong> INDIA</span></div>
          </div>
          `
          )
        );

      // Create a marker at the starting position
      const startMarker = new mapboxgl.Marker()
        .setLngLat(originCord)
        .addTo(map);

      if (routeGeometry) {
        map.addSource("route", {
          type: "geojson",
          data: {
            type: "Feature",
            geometry: routeGeometry,
          },
        });

        map.addLayer({
          id: "route",
          type: "line",
          source: "route",
          layout: {
            "line-join": "round",
            "line-cap": "round",
          },
          paint: {
            "line-color": "#3b9ddd",
            "line-width": 6,
          },
        });
      }
      // Get the route bounds
      const bounds = routeGeometry.coordinates.reduce(
        (bounds, coord) => bounds.extend(coord),
        new mapboxgl.LngLatBounds()
      );

      // Zoom out to fit the route within the map view
      map.fitBounds(bounds, {
        padding: 50,
      });
    });

    // return () => {
    //   map.remove();
    // };
  }, [mapStyle, routeGeometry]);

Автозаполнение поиска:

В этом разделе обрабатывается пользовательский ввод в поле ввода поиска (<input id="fromAddress" />) и извлекаются предложения автозаполнения с использованием Mapbox Geocoding API.

const handleInputChange = (event) => {
  const { value } = event.target;
  setOrigin(value);

  axios
    .get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${value}.json`, {
      params: {
        access_token: mapboxgl.accessToken,
        autocomplete: true,
        types: ["place"],
        limit: 5,
      },
    })
    .then((response) => {
      const { features } = response.data;
      setSuggestions(features);
    })
    .catch((error) => {
      console.error("Error fetching autocomplete suggestions:", error);
    });
};

Обработка предложений автозаполнения:

В этом разделе отображаются предложения автозаполнения на основе пользовательского ввода и обрабатывается выбор предложения.

const handleSelectSuggestion = (suggestion) => {
  setOrigin(suggestion.place_name);
  setOriginCord(suggestion.center);
  setSuggestions([]);
};

Расчет и отображение направлений:

В этом разделе маршруты маршрута рассчитываются с помощью Mapbox Directions API на основе координат исходной и конечной точек.

Он делает HTTP-запрос к API и получает информацию о маршруте, включая расстояние, продолжительность и пошаговые инструкции.

const calcRouteDirection = async () => {
    if (origin.length > 2) {
      try {
        const origin = document.getElementById("fromAddress").value;
        if (origin.length > 2) {
          try {
            const response = await geocodingClient
              .forwardGeocode({
                query: origin,
                types: ["place"],
                limit: 1,
              })
              .send();

            const destinationCoordinates = response.body.features[0].center;
            originCoordinates = destinationCoordinates;
            setOriginCord(destinationCoordinates);
          } catch (error) {
            console.error("Error calculating directions:", error);
            throw error;
          }
        }
        const response = await axios.get(
          `https://api.mapbox.com/directions/v5/mapbox/${localStorage.getItem(
            "mode"
          )}/${originCoordinates[0]},${originCoordinates[1]};${
            destination[0]
          },${destination[1]}?steps=true&geometries=geojson&access_token=${
            import.meta.env.VITE_MAP_BOX_ACCESS_TOKEN
          }`
        );

        const routes = response.data.routes;
        console.log("routes=>", routes);
        setRouteInfo(routes);
        // Check if any routes are returned
        if (routes.length > 0) {
          const { distance, duration, geometry } = routes[0];

          // Valid directions, use the distance and duration for further processing
          const directions = {
            distance,
            duration,
          };
          localStorage.setItem("fromLocation", origin);
          setRouteGeometry(geometry); // Set the route geometry
          return directions;
        } else {
          // No routes found
          throw new Error("Unable to calculate directions");
        }
      } catch (error) {
        // Handle error
        console.error("Error calculating directions:", error);
        throw error;
      }
    }
  };

Переключение режимов движения:

Этот раздел позволяет пользователю переключаться между различными режимами движения (вождение, ходьба, езда на велосипеде) и соответствующим образом пересчитывать направления.

Он обновляет значение localStorage для выбранного режима движения и запускает функцию calcRouteDirection.

<a
  className={`mode-button ${selectedMode === "driving" ? "active" : ""}`}
  onClick={() => handleModeChange("driving")}
>
  Driving
</a>
<a
  className={`mode-button ${selectedMode === "walking" ? "active" : ""}`}
  onClick={() => handleModeChange("walking")}
>
  Walking
</a>
<a
  className={`mode-button ${selectedMode === "cycling" ? "active" : ""}`}
  onClick={() => handleModeChange("cycling")}
>
  Cycling
</a>
const handleModeChange = (mode) => {
  localStorage.setItem("selectedMode", mode);
  setSelectedMode(mode);
  calcRouteDirection();
};

Отображение инструкций по маршруту:

В этом разделе отображаются пошаговые инструкции для выбранного маршрута. Он перебирает массив routeInfo и отображает каждую инструкцию как элемент списка.

<ul>
  {routeInfo[0].legs[0].steps.map((step, index) => (
    <li key={index}>{step.maneuver.instruction}</li>
  ))}
</ul>

Обновление карты и визуализации маршрута:

Этот раздел обновляет карту и визуализирует маршрут на карте всякий раз, когда изменяется пункт отправления, пункт назначения или выбранный режим движения.

Он обновляет центр карты и уровень масштабирования в зависимости от границ маршрута и отображает маршрут с помощью Mapbox LineString и Layer.

useEffect(() => {
  if (map && routeGeometry) {
    map.fitBounds(routeGeometry.bounds, { padding: 20 });

    map.addLayer({
      id: "route",
      type: "line",
      source: {
        type: "geojson",
        data: {
          type: "Feature",
          properties: {},
          geometry: routeGeometry,
        },
      },
      layout: {
        "line-join": "round",
        "line-cap": "round",
      },
      paint: {
        "line-color": "#00f",
        "line-width": 4,
      },
    });
  }
}, [map, routeGeometry]);

В заключение представленный код демонстрирует потенциал React, Mapbox и API в создании динамической и функциональной карты и компонента направлений. Он демонстрирует такие функции, как предложения автозаполнения, переключение режима движения, расчет маршрута, визуализация маршрута и пошаговые инструкции. Используя эти технологии, разработчики могут создавать мощные и удобные картографические приложения, которые расширяют возможности пользователей и предоставляют ценные навигационные функции.

Исходный код: https://github.com/Priyammondal/map-box-react
Живая демонстрация: https://map-box-react.netlify.app/