Хуки для карт

В современной разработке пользовательских интерфейсов хуки позволяют инкапсулировать логику работы с компонентами и повторно использовать её в разных частях приложения. При интеграции HERE Maps API в приложения на JavaScript, особенно при использовании React, хуки становятся важным инструментом управления жизненным циклом карты, обработкой событий, состоянием объектов и взаимодействием с API.

Под хуками для карт обычно понимаются пользовательские (custom) React-хуки, внутри которых сосредоточена логика создания карты, управления слоями, маркерами, маршрутами, геолокацией и другими функциями HERE Maps.

Основные преимущества такого подхода:

  • разделение логики и представления;
  • повторное использование кода;
  • упрощение поддержки проекта;
  • централизованное управление состоянием карты;
  • снижение количества дублирующегося кода.

Создание базового хука карты

Одной из наиболее распространённых задач является инициализация карты.

Без выделения логики в хук код часто становится громоздким:

useEffect(() => {
    const platform = new H.service.Platform({
        apikey: API_KEY
    });

    const layers = platform.createDefaultLayers();

    const map = new H.Map(
        mapRef.current,
        layers.vector.normal.map,
        {
            zoom: 10,
            center: { lat: 55.751244, lng: 37.618423 }
        }
    );

    return () => {
        map.dispose();
    };
}, []);

Подобный код приходится повторять в разных компонентах.

Более удобным решением становится создание специализированного хука.

import { useEffect, useRef } from "react";

export function useHereMap(containerRef, options) {
    const mapRef = useRef(null);

    useEffect(() => {
        const platform = new H.service.Platform({
            apikey: options.apiKey
        });

        const layers = platform.createDefaultLayers();

        const map = new H.Map(
            containerRef.current,
            layers.vector.normal.map,
            {
                center: options.center,
                zoom: options.zoom
            }
        );

        mapRef.current = map;

        return () => {
            map.dispose();
        };
    }, []);

    return mapRef;
}

Использование:

const mapContainer = useRef(null);

const map = useHereMap(mapContainer, {
    apiKey: API_KEY,
    center: { lat: 55.75, lng: 37.61 },
    zoom: 12
});

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


Хук доступа к экземпляру карты

Во многих проектах требуется получать текущий экземпляр карты из разных компонентов.

Для этого можно использовать отдельный хук-контейнер.

import { createContext, useContext } from "react";

const MapContext = createContext(null);

export function useMap() {
    return useContext(MapContext);
}

Провайдер:

<MapContext.Provider value={map}>
    {children}
</MapContext.Provider>

Получение карты:

const map = useMap();

map.setZoom(15);

Подобная архитектура избавляет от передачи объекта карты через множество уровней компонентов.


Хук управления центром карты

Изменение центра карты является одной из самых частых операций.

Создание специального хука упрощает управление этой функциональностью.

import { useCallback } from "react";

export function useMapCenter(map) {
    const setCenter = useCallback((lat, lng) => {
        if (!map) return;

        map.setCenter({ lat, lng });
    }, [map]);

    return { setCenter };
}

Использование:

const { setCenter } = useMapCenter(map);

setCenter(48.8566, 2.3522);

Преимущество заключается в том, что вся логика изменения центра карты сосредоточена в одном месте.


Хук изменения масштаба

Работа с масштабированием также часто выносится в отдельный слой абстракции.

import { useCallback } from "react";

export function useZoom(map) {
    const zoomIn = useCallback(() => {
        map.setZoom(map.getZoom() + 1);
    }, [map]);

    const zoomOut = useCallback(() => {
        map.setZoom(map.getZoom() - 1);
    }, [map]);

    return {
        zoomIn,
        zoomOut
    };
}

Пример:

const { zoomIn, zoomOut } = useZoom(map);

zoomIn();
zoomOut();

Такой подход делает интерфейс независимым от конкретной реализации HERE Maps API.


Хук работы с маркерами

Добавление и удаление маркеров встречается практически в каждом картографическом приложении.

Хук может инкапсулировать всю связанную логику.

import { useCallback } from "react";

export function useMarkers(map) {
    const addMarker = useCallback((position) => {
        const marker = new H.map.Marker(position);

        map.addObject(marker);

        return marker;
    }, [map]);

    const removeMarker = useCallback((marker) => {
        map.removeObject(marker);
    }, [map]);

    return {
        addMarker,
        removeMarker
    };
}

Использование:

const { addMarker } = useMarkers(map);

addMarker({
    lat: 55.75,
    lng: 37.61
});

Хук массива маркеров

При отображении большого количества объектов важно централизованно управлять их жизненным циклом.

import { useEffect } from "react";

export function useMarkerCollection(map, points) {
    useEffect(() => {
        if (!map) return;

        const markers = points.map(point => {
            const marker = new H.map.Marker(point);

            map.addObject(marker);

            return marker;
        });

        return () => {
            markers.forEach(marker => {
                map.removeObject(marker);
            });
        };
    }, [map, points]);
}

Пример:

useMarkerCollection(map, locations);

При изменении массива старые маркеры автоматически удаляются и создаются новые.


Хук обработки кликов по карте

События карты удобно инкапсулировать внутри отдельных хуков.

import { useEffect } from "react";

export function useMapClick(map, callback) {
    useEffect(() => {
        if (!map) return;

        const handler = (event) => {
            const coord = map.screenToGeo(
                event.currentPointer.viewportX,
                event.currentPointer.viewportY
            );

            callback(coord);
        };

        map.addEventListener("tap", handler);

        return () => {
            map.removeEventListener("tap", handler);
        };
    }, [map, callback]);
}

Использование:

useMapClick(map, (coord) => {
    console.log(coord);
});

Хук получения координат пользователя

HERE Maps часто используется вместе с браузерной геолокацией.

import { useState, useEffect } from "react";

export function useGeolocation() {
    const [position, setPosition] = useState(null);

    useEffect(() => {
        navigator.geolocation.getCurrentPosition(
            (result) => {
                setPosition({
                    lat: result.coords.latitude,
                    lng: result.coords.longitude
                });
            }
        );
    }, []);

    return position;
}

Использование:

const location = useGeolocation();

Полученные координаты могут использоваться для центрирования карты или построения маршрутов.


Хук отслеживания местоположения

Для приложений навигации требуется постоянное обновление координат.

import { useEffect, useState } from "react";

export function useWatchPosition() {
    const [position, setPosition] = useState(null);

    useEffect(() => {
        const watchId =
            navigator.geolocation.watchPosition(
                (result) => {
                    setPosition({
                        lat: result.coords.latitude,
                        lng: result.coords.longitude
                    });
                }
            );

        return () => {
            navigator.geolocation.clearWatch(watchId);
        };
    }, []);

    return position;
}

Такой подход позволяет автоматически обновлять положение пользователя на карте.


Хук построения маршрута

Построение маршрутов является одной из ключевых возможностей HERE Maps.

Хук позволяет скрыть детали взаимодействия с Routing API.

import { useCallback } from "react";

export function useRouting(platform, map) {
    const calculateRoute = useCallback(
        async (origin, destination) => {
            const router =
                platform.getRoutingService(null, 8);

            const route = await router.calculateRoute({
                transportMode: "car",
                origin,
                destination,
                return: "polyline"
            });

            return route;
        },
        [platform]
    );

    return {
        calculateRoute
    };
}

Использование:

const { calculateRoute } =
    useRouting(platform, map);

Бизнес-логика маршрутизации полностью изолируется внутри специализированного хука.


Хук отображения маршрута

После получения маршрута требуется отобразить его на карте.

import { useCallback } from "react";

export function useRouteRenderer(map) {
    const drawRoute = useCallback((lineString) => {
        const routeLine = new H.map.Polyline(
            lineString
        );

        map.addObject(routeLine);

        return routeLine;
    }, [map]);

    return { drawRoute };
}

Пример использования:

const { drawRoute } =
    useRouteRenderer(map);

Хук управления объектами карты

При большом количестве элементов полезно централизованно управлять объектами.

import { useRef } from "react";

export function useMapObjects(map) {
    const objects = useRef([]);

    const addObject = (object) => {
        map.addObject(object);
        objects.current.push(object);
    };

    const clearObjects = () => {
        objects.current.forEach(object => {
            map.removeObject(object);
        });

        objects.current = [];
    };

    return {
        addObject,
        clearObjects
    };
}

Такой механизм особенно полезен при динамическом обновлении данных.


Хук автоматического подбора области просмотра

После добавления объектов часто требуется показать их все одновременно.

import { useCallback } from "react";

export function useFitBounds(map) {
    const fitObjects = useCallback(() => {
        const bounds =
            map.getObjects()[0]?.getBoundingBox();

        if (bounds) {
            map.getViewModel()
               .setLookAtData({
                   bounds
               });
        }
    }, [map]);

    return { fitObjects };
}

Использование:

const { fitObjects } =
    useFitBounds(map);

fitObjects();

Хук переключения слоёв карты

HERE Maps предоставляет различные типы картографических слоёв.

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

import { useState } from "react";

export function useMapLayers(map, layers) {
    const [currentLayer, setCurrentLayer] =
        useState("normal");

    const changeLayer = (name) => {
        map.setBaseLayer(layers[name]);
        setCurrentLayer(name);
    };

    return {
        currentLayer,
        changeLayer
    };
}

Пример:

changeLayer("satellite");

Хук управления пользовательским интерфейсом карты

Элементы управления HERE Maps могут динамически включаться и отключаться.

import { useCallback } from "react";

export function useMapUI(ui) {
    const hideControls = useCallback(() => {
        ui.getControl("zoom")
          ?.setVisibility(false);
    }, [ui]);

    const showControls = useCallback(() => {
        ui.getControl("zoom")
          ?.setVisibility(true);
    }, [ui]);

    return {
        hideControls,
        showControls
    };
}

Подобный подход облегчает создание адаптивных интерфейсов.


Комбинирование нескольких хуков

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

function MapContainer() {
    const mapRef = useRef(null);

    const map = useHereMap(
        mapRef,
        config
    );

    const { addMarker } =
        useMarkers(map.current);

    const { zoomIn } =
        useZoom(map.current);

    const userPosition =
        useGeolocation();

    useEffect(() => {
        if (userPosition) {
            addMarker(userPosition);
        }
    }, [userPosition]);

    return (
        <div
            ref={mapRef}
            style={{
                width: "100%",
                height: "600px"
            }}
        />
    );
}

Композиция хуков позволяет формировать сложную картографическую функциональность из небольших независимых блоков. Каждая часть отвечает только за одну задачу: инициализацию карты, управление масштабом, отображение маршрутов, работу с геолокацией или обработку событий. Такой подход делает приложения на основе HERE Maps API более модульными, расширяемыми и удобными для сопровождения.