Объединение и пересечение фигур

Работа с пространственными объектами в приложениях на базе HERE Technologies и HERE Maps JavaScript API часто выходит за рамки простого отображения фигур на карте. При построении аналитических слоёв, зон покрытия, территориальных правил и геофенсинга возникает необходимость выполнять булевы операции над геометрией: объединение (union), пересечение (intersection), вычитание (difference).

В стандартном клиентском API базовые геометрические классы (например, полигоны и линии) предназначены для визуализации и простых проверок принадлежности точки области, но не предоставляют полноценного набора геометрических операций. Поэтому на практике используются внешние геометрические библиотеки, чаще всего Turf.js, либо серверные геосервисы.


Представление полигонов в HERE Maps

Полигон в HERE Maps JavaScript API обычно создаётся через набор точек:

const polygon = new H.map.Polygon(
  new H.geo.LineString([
    52.51, 13.40,
    52.52, 13.41,
    52.50, 13.42
  ]),
  {
    style: { strokeColor: 'blue', lineWidth: 2 }
  }
);

Внутренне объект хранит геометрию в формате H.geo.LineString или H.geo.Polygon. Эти структуры предназначены для отображения и базовых вычислений (например, площадь или проверка точки внутри области), но не поддерживают операции объединения нескольких полигонов напрямую.

Для выполнения булевой геометрии необходимо преобразование в универсальный формат, чаще всего GeoJSON.


Преобразование геометрии в GeoJSON

Перед выполнением операций union/intersection полигон переводится в GeoJSON:

function herePolygonToGeoJSON(polygon) {
  const strip = polygon.getGeometry();
  const coordinates = [];

  strip.eachLatLngAlt((lat, lng) => {
    coordinates.push([lng, lat]);
  });

  return {
    type: "Polygon",
    coordinates: [coordinates]
  };
}

Ключевой момент заключается в порядке координат: GeoJSON использует [longitude, latitude], тогда как HERE API — [latitude, longitude].


Использование Turf.js для объединения полигонов

Для операций объединения используется библиотека геообработки Turf.js.

import union from '@turf/union';

const poly1 = herePolygonToGeoJSON(polygonA);
const poly2 = herePolygonToGeoJSON(polygonB);

const result = union(poly1, poly2);

Результат представляет собой новый GeoJSON-объект, содержащий объединённую геометрию.

Далее он может быть обратно преобразован в объект карты:

function geoJSONToHerePolygon(geojson) {
  const lineString = new H.geo.LineString();

  geojson.coordinates[0].forEach(([lng, lat]) => {
    lineString.pushPoint({ lat, lng });
  });

  return new H.map.Polygon(lineString, {
    style: { strokeColor: 'red', lineWidth: 3 }
  });
}

Пересечение полигонов

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

import intersect from '@turf/intersect';

const poly1 = herePolygonToGeoJSON(polygonA);
const poly2 = herePolygonToGeoJSON(polygonB);

const result = intersect(poly1, poly2);

Если пересечение отсутствует, результат будет null.


Практический сценарий: наложение зон доставки

При моделировании зон доставки часто требуется:

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

Пример логики:

const warehouseZone = herePolygonToGeoJSON(zoneA);
const deliveryZone = herePolygonToGeoJSON(zoneB);

const overlap = intersect(warehouseZone, deliveryZone);
const merged = union(warehouseZone, deliveryZone);

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


Работа с мультиполигонами

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

{
  type: "MultiPolygon",
  coordinates: [
    [[[lng, lat], ...]],
    [[[lng, lat], ...]]
  ]
}

При конвертации обратно в HERE-объекты требуется обработка каждого полигона отдельно:

function geoJSONToMultiHere(geojson) {
  const objects = [];

  geojson.coordinates.forEach(polygon => {
    const line = new H.geo.LineString();

    polygon[0].forEach(([lng, lat]) => {
      line.pushPoint({ lat, lng });
    });

    objects.push(new H.map.Polygon(line));
  });

  return objects;
}

Проверка корректности геометрии

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

  • самопересечения полигонов
  • некорректный порядок вершин
  • незамкнутые контуры
  • дублирующиеся точки

Turf.js в большинстве случаев автоматически нормализует входные данные, однако при интеграции с HERE-структурами требуется явная проверка:

function isValidRing(coords) {
  return coords.length > 3 &&
    coords[0][0] === coords[coords.length - 1][0] &&
    coords[0][1] === coords[coords.length - 1][1];
}

Оптимизация вычислений

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

  • предварительная фильтрация по bounding box
  • разбиение сцены на тайлы
  • кэширование результатов union/intersection
  • упрощение геометрии (Douglas–Peucker алгоритм)

Упрощение через Turf.js:

import simplify from '@turf/simplify';

const simplified = simplify(polygon, {
  tolerance: 0.001,
  highQuality: false
});

Обработка коллизий и наложений на карте

При визуализации результатов операций важно учитывать порядок отрисовки:

map.addObject(new H.map.Polygon(shape1));
map.addObject(new H.map.Polygon(shape2));
map.addObject(new H.map.Polygon(result));

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


Комбинирование нескольких операций

В реальных сценариях операции часто комбинируются:

const unionZones = union(zoneA, zoneB);
const finalZone = intersect(unionZones, restrictionZone);

Такая цепочка используется для моделирования сложных пространственных ограничений, включая:

  • зоны покрытия сети
  • административные ограничения
  • гео-фильтры событий

Обработка координатных систем

HERE Maps использует WGS84, как и GeoJSON. Однако при интеграции с внешними системами необходимо учитывать:

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

Для критичных систем применяется нормализация координат до фиксированного количества знаков:

function normalizeCoord([lng, lat]) {
  return [
    parseFloat(lng.toFixed(6)),
    parseFloat(lat.toFixed(6))
  ];
}