Получение нарисованных объектов

В Google Maps JavaScript API отсутствует универсальный встроенный реестр всех добавленных на карту графических объектов. Карта не предоставляет метода наподобие getOverlays(), поэтому управление созданными примитивами (маркер, линия, полигон, круг, прямоугольник) осуществляется через собственное хранение ссылок на объекты в коде приложения.

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


Drawing Library и создание объектов

Для интерактивного рисования используется библиотека drawing, подключаемая при загрузке API:

<script src="https://maps.googleapis.com/maps/api/js?key=API_KEY&libraries=drawing"></script>

Основной компонент — google.maps.drawing.DrawingManager, который управляет режимами рисования:

const drawingManager = new google.maps.drawing.DrawingManager({
  drawingMode: null,
  drawingControl: true,
  drawingControlOptions: {
    position: google.maps.ControlPosition.TOP_CENTER,
    drawingModes: [
      google.maps.drawing.OverlayType.MARKER,
      google.maps.drawing.OverlayType.POLYGON,
      google.maps.drawing.OverlayType.POLYLINE,
      google.maps.drawing.OverlayType.CIRCLE,
      google.maps.drawing.OverlayType.RECTANGLE
    ]
  }
});

drawingManager.setMap(map);

Перехват события overlaycomplete

Каждый раз после завершения рисования генерируется событие overlaycomplete, содержащее ссылку на созданный объект.

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

const drawnObjects = [];

google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
  const overlay = event.overlay;
  const type = event.type;

  drawnObjects.push({
    type,
    overlay
  });
});

Структура event:

  • event.overlay — созданный объект (Marker, Polygon, Polyline, Circle, Rectangle)
  • event.type — тип объекта

Унифицированное хранение объектов

Практика хранения только overlay недостаточна при сложных сценариях. Обычно сохраняется расширенная структура:

const drawnObjects = [];
google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
  const obj = {
    id: crypto.randomUUID(),
    type: event.type,
    overlay: event.overlay,
    createdAt: Date.now()
  };

  drawnObjects.push(obj);
});

Такой подход позволяет:

  • идентифицировать объект
  • удалять его по ID
  • сериализовать данные
  • фильтровать по типу

Получение координат объектов

Каждый тип оверлея имеет собственный API для извлечения геометрии.

Marker

const position = marker.getPosition();
const lat = position.lat();
const lng = position.lng();

Polyline

const path = polyline.getPath();

const coordinates = [];
path.forEach(function(latlng) {
  coordinates.push({
    lat: latlng.lat(),
    lng: latlng.lng()
  });
});

Polygon

const paths = polygon.getPaths();

const polygons = [];

paths.forEach(function(path) {
  const ring = [];

  path.forEach(function(latlng) {
    ring.push({
      lat: latlng.lat(),
      lng: latlng.lng()
    });
  });

  polygons.push(ring);
});

Circle

const center = circle.getCenter();
const radius = circle.getRadius();

const data = {
  center: {
    lat: center.lat(),
    lng: center.lng()
  },
  radius
};

Rectangle

const bounds = rectangle.getBounds();

const data = {
  northEast: {
    lat: bounds.getNorthEast().lat(),
    lng: bounds.getNorthEast().lng()
  },
  southWest: {
    lat: bounds.getSouthWest().lat(),
    lng: bounds.getSouthWest().lng()
  }
};

Итерация по сохранённым объектам

Так как API не предоставляет глобального списка, перебор выполняется через локальный массив:

drawnObjects.forEach(item => {
  console.log(item.type);
  console.log(item.overlay);
});

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


Удаление нарисованных объектов

Удаление выполняется через метод setMap(null):

function removeObject(id) {
  const index = drawnObjects.findIndex(o => o.id === id);

  if (index === -1) return;

  drawnObjects[index].overlay.setMap(null);
  drawnObjects.splice(index, 1);
}

Очистка всех объектов

function clearAll() {
  drawnObjects.forEach(obj => {
    obj.overlay.setMap(null);
  });

  drawnObjects.length = 0;
}

Редактируемые объекты и обновление данных

При включённой опции редактирования (editable: true) геометрия может изменяться пользователем. В этом случае важно отслеживать изменения через события объекта.

Polygon editing

polygon.setEditable(true);

google.maps.event.addListener(polygon.getPath(), 'set_at', updateData);
google.maps.event.addListener(polygon.getPath(), 'insert_at', updateData);
google.maps.event.addListener(polygon.getPath(), 'remove_at', updateData);

function updateData() {
  const path = polygon.getPath();

  const coords = [];
  path.forEach(latlng => {
    coords.push({ lat: latlng.lat(), lng: latlng.lng() });
  });

  console.log(coords);
}

Использование Data Layer как альтернатива

Для сценариев, где требуется системное хранение геометрии, применяется google.maps.Data.

map.data.addGeoJson(geoJsonObject);

Получение всех объектов:

map.data.forEach(function(feature) {
  console.log(feature.getGeometry());
});

Извлечение координат:

map.data.forEach(function(feature) {
  const geometry = feature.getGeometry();

  geometry.forEachLatLng(function(latlng) {
    console.log(latlng.lat(), latlng.lng());
  });
});

Преимущества Data Layer:

  • встроенная работа с GeoJSON
  • централизованное хранение
  • удобная сериализация
  • стилизация через setStyle

Сериализация нарисованных объектов

Для сохранения состояния карты необходимо преобразовать объекты в JSON-подобную структуру вручную.

function serialize() {
  return drawnObjects.map(obj => {
    const overlay = obj.overlay;

    if (obj.type === 'marker') {
      const pos = overlay.getPosition();
      return {
        type: 'marker',
        lat: pos.lat(),
        lng: pos.lng()
      };
    }

    if (obj.type === 'circle') {
      const center = overlay.getCenter();
      return {
        type: 'circle',
        center: {
          lat: center.lat(),
          lng: center.lng()
        },
        radius: overlay.getRadius()
      };
    }

    if (obj.type === 'polyline') {
      const path = overlay.getPath();
      const coords = [];

      path.forEach(p => coords.push({ lat: p.lat(), lng: p.lng() }));

      return {
        type: 'polyline',
        path: coords
      };
    }

    return null;
  });
}

Восстановление объектов из сохранённых данных

function restore(data) {
  data.forEach(item => {
    if (item.type === 'marker') {
      new google.maps.Marker({
        position: item,
        map
      });
    }

    if (item.type === 'circle') {
      new google.maps.Circle({
        center: item.center,
        radius: item.radius,
        map
      });
    }

    if (item.type === 'polyline') {
      new google.maps.Polyline({
        path: item.path,
        map
      });
    }
  });
}

Управление состоянием объектов в архитектуре приложения

При масштабировании проекта хранение нарисованных объектов обычно выносится в отдельный слой состояния:

  • массив объектов в памяти
  • стор (Redux, Zustand, MobX)
  • синхронизация с сервером
  • восстановление при загрузке карты

Ключевой принцип: карта рассматривается как визуализация состояния, а не как источник истины.