Создание собственных оверлеев

Оверлеи (Overlay) в Google Maps JavaScript API позволяют размещать поверх карты собственные визуальные элементы, не ограничиваясь стандартными маркерами, информационными окнами и фигурами. Пользовательский оверлей может представлять собой изображение, HTML-блок, интерактивную панель, всплывающую подсказку, схему здания, слой данных или любой другой элемент интерфейса.

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

Основные возможности пользовательских оверлеев:

  • отображение произвольного HTML-контента;
  • привязка элементов к координатам карты;
  • управление позиционированием и масштабированием;
  • создание интерактивных интерфейсов поверх карты;
  • интеграция внешних визуализаций и графиков;
  • отображение изображений на определенной территории.

Класс OverlayView

Класс google.maps.OverlayView является абстрактным базовым классом. Для создания собственного оверлея необходимо унаследоваться от него и реализовать ряд методов жизненного цикла.

Стандартная структура выглядит следующим образом:

class CustomOverlay extends google.maps.OverlayView {

    constructor(bounds, element) {
        super();

        this.bounds = bounds;
        this.element = element;
    }

    onAdd() {

    }

    draw() {

    }

    onRemove() {

    }
}

Google Maps автоматически вызывает эти методы в нужные моменты работы карты.


Жизненный цикл оверлея

Метод onAdd()

Вызывается после добавления оверлея на карту.

В этом методе обычно:

  • создаются DOM-элементы;
  • настраиваются стили;
  • добавляются обработчики событий;
  • выполняется вставка элемента в нужный слой карты.

Пример:

onAdd() {

    const div = document.createElement("div");

    div.className = "custom-overlay";

    div.innerHTML = `
        <div class="info-panel">
            Москва
        </div>
    `;

    this.div = div;

    const panes = this.getPanes();

    panes.overlayLayer.appendChild(div);
}

Метод draw()

Вызывается:

  • после добавления оверлея;
  • при перемещении карты;
  • при изменении масштаба;
  • при изменении размеров контейнера карты.

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

draw() {

}

Метод onRemove()

Вызывается при удалении оверлея с карты.

Здесь необходимо освобождать ресурсы и удалять DOM-элементы.

onRemove() {

    if (this.div) {
        this.div.remove();
        this.div = null;
    }

}

Панели карты (Map Panes)

Google Maps содержит несколько слоев отображения.

Получить их можно через:

const panes = this.getPanes();

Доступны следующие панели:

Панель Назначение
mapPane Базовый слой карты
overlayLayer Большинство пользовательских элементов
markerLayer Маркеры
overlayMouseTarget Элементы, обрабатывающие события мыши
floatPane Информационные окна

Пример:

panes.overlayMouseTarget.appendChild(div);

Такой элемент сможет получать события мыши.


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

Для вычисления положения элементов используется объект проекции:

const projection = this.getProjection();

Он позволяет переводить координаты карты в пиксельные координаты.

Основной метод:

projection.fromLatLngToDivPixel()

Пример:

const position = projection.fromLatLngToDivPixel(
    new google.maps.LatLng(55.7558, 37.6176)
);

Результат:

{
    x: 412,
    y: 286
}

Полученные координаты используются для позиционирования HTML-элементов.


Простейший HTML-оверлей

Создадим окно с текстом поверх карты.

Класс оверлея

class LabelOverlay extends google.maps.OverlayView {

    constructor(position, text) {
        super();

        this.position = position;
        this.text = text;
    }

    onAdd() {

        this.div = document.createElement("div");

        this.div.className = "label-overlay";
        this.div.textContent = this.text;

        this.getPanes()
            .overlayMouseTarget
            .appendChild(this.div);
    }

    draw() {

        const projection = this.getProjection();

        const pixel =
            projection.fromLatLngToDivPixel(
                this.position
            );

        this.div.style.left = pixel.x + "px";
        this.div.style.top = pixel.y + "px";
    }

    onRemove() {

        this.div.remove();
    }
}

Создание экземпляра

const overlay = new LabelOverlay(
    new google.maps.LatLng(
        55.7558,
        37.6176
    ),
    "Центр Москвы"
);

overlay.setMap(map);

Метод:

setMap(map);

добавляет оверлей на карту.


Абсолютное позиционирование

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

.label-overlay {
    position: absolute;
    background: white;
    padding: 8px 12px;
    border-radius: 6px;
    box-shadow: 0 2px 8px rgba(0,0,0,.2);
}

Поскольку элемент располагается поверх карты, используется:

position: absolute;

Центрирование относительно координаты

По умолчанию верхний левый угол элемента будет совпадать с координатой точки.

Для центрирования используется CSS-трансформация:

.label-overlay {
    position: absolute;
    transform: translate(-50%, -100%);
}

Результат:

  • центр блока совпадает с координатой;
  • блок располагается над точкой.

Оверлей на основе изображения

Часто требуется наложить изображение на определенную область карты.

Например:

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

Конструктор

class ImageOverlay extends google.maps.OverlayView {

    constructor(bounds, image) {
        super();

        this.bounds = bounds;
        this.image = image;
    }

}

Создание элемента

onAdd() {

    const div = document.createElement("div");

    const img = document.createElement("img");

    img.src = this.image;

    img.style.width = "100%";
    img.style.height = "100%";

    div.appendChild(img);

    this.div = div;

    this.getPanes()
        .overlayLayer
        .appendChild(div);
}

Масштабирование изображения

В методе draw() необходимо вычислять размеры и положение изображения.

draw() {

    const projection =
        this.getProjection();

    const sw =
        projection.fromLatLngToDivPixel(
            this.bounds.getSouthWest()
        );

    const ne =
        projection.fromLatLngToDivPixel(
            this.bounds.getNorthEast()
        );

    const div = this.div;

    div.style.left = sw.x + "px";
    div.style.top = ne.y + "px";

    div.style.width =
        (ne.x - sw.x) + "px";

    div.style.height =
        (sw.y - ne.y) + "px";
}

Теперь изображение автоматически изменяет размер при масштабировании карты.


Интерактивные элементы внутри оверлея

Поскольку оверлей представляет собой обычный HTML, внутри него могут находиться любые элементы интерфейса.

Пример:

this.div.innerHTML = `
    <div class="popup">
        <h3>Склад</h3>

        <button id="details">
            Подробнее
        </button>
    </div>
`;

Можно подключать обработчики событий:

this.div
    .querySelector("#details")
    .addEventListener("click", () => {

        console.log("Открытие карточки");

    });

Обработка событий мыши

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

overlayMouseTarget

Пример:

this.getPanes()
    .overlayMouseTarget
    .appendChild(this.div);

Теперь будут работать:

click
dblclick
mouseover
mouseout
mousemove

Предотвращение перемещения карты

Иногда при работе с элементами интерфейса необходимо блокировать передачу событий карте.

Google Maps предоставляет специальные методы:

google.maps.OverlayView.preventMapHitsFrom(
    element
);

Пример:

google.maps.OverlayView
    .preventMapHitsFrom(
        this.div
    );

Теперь щелчки не будут достигать карты.


Для полной блокировки жестов:

google.maps.OverlayView
    .preventMapHitsAndGesturesFrom(
        this.div
    );

Будут отключены:

  • перетаскивание;
  • масштабирование колесиком;
  • касания;
  • клики по карте через элемент.

Управление видимостью

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

this.div.style.display = "none";

Показ:

this.div.style.display = "block";

Методы класса:

hide() {
    this.div.style.display = "none";
}

show() {
    this.div.style.display = "block";
}

Удаление оверлея

Для полного удаления используется:

overlay.setMap(null);

После вызова:

overlay.setMap(null);

Google Maps автоматически выполнит:

onRemove()

и очистит элемент.


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

Поскольку оверлей является HTML-элементом, доступны любые CSS-анимации.

Пример появления:

.overlay {
    opacity: 0;

    animation: fadeIn .3s forwards;
}

@keyframes fadeIn {

    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }

}

Создание всплывающих карточек объектов

Распространенный сценарий — отображение карточек объектов непосредственно на карте.

Структура:

<div class="property-card">

    <img src="house.jpg">

    <h3>Дом</h3>

    <p>120 м²</p>

</div>

Такой элемент может отображаться:

  • над маркером;
  • рядом с объектом;
  • при наведении;
  • после выбора объекта.

Производительность при большом количестве оверлеев

При создании сотен элементов необходимо учитывать нагрузку на DOM.

Основные рекомендации:

Минимизировать количество HTML-узлов

Плохо:

<div>
    <div>
        <div>
            <span>
            </span>
        </div>
    </div>
</div>

Лучше:

<div class="marker-label">
    Точка
</div>

Избегать тяжелых вычислений внутри draw()

Метод вызывается очень часто.

Плохо:

draw() {

    expensiveCalculation();

}

Лучше:

draw() {

    updatePosition();

}

Удалять неиспользуемые обработчики

Перед удалением элемента:

button.removeEventListener(
    "click",
    handler
);

Не создавать DOM заново

Плохо:

draw() {

    this.div =
        document.createElement("div");

}

Правильно:

onAdd() {

    this.div =
        document.createElement("div");

}

Полноценный пример пользовательского оверлея

class CustomOverlay extends google.maps.OverlayView {

    constructor(position, html) {
        super();

        this.position = position;
        this.html = html;
    }

    onAdd() {

        this.div = document.createElement("div");

        this.div.className = "custom-overlay";

        this.div.innerHTML = this.html;

        this.getPanes()
            .overlayMouseTarget
            .appendChild(this.div);

        google.maps.OverlayView
            .preventMapHitsFrom(
                this.div
            );
    }

    draw() {

        const projection =
            this.getProjection();

        const pixel =
            projection.fromLatLngToDivPixel(
                this.position
            );

        this.div.style.left =
            pixel.x + "px";

        this.div.style.top =
            pixel.y + "px";

        this.div.style.transform =
            "translate(-50%, -100%)";
    }

    onRemove() {

        if (this.div) {

            this.div.remove();

            this.div = null;
        }
    }
}

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

const overlay =
    new CustomOverlay(
        {
            lat: 55.7558,
            lng: 37.6176
        },
        `
        <div class="popup">
            <h3>Москва</h3>
            <p>Пользовательский оверлей</p>
        </div>
        `
    );

overlay.setMap(map);

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