Определение точки в полигоне

Определение принадлежности точки полигону в OpenLayers опирается на геометрические операции над объектами ol/geom, где ключевую роль играет топология координат и корректная работа с проекциями. Полигон рассматривается как замкнутый набор координатных контуров, а точка — как упрощённая геометрия с одной парой координат. Проверка принадлежности сводится к вычислению: лежит ли координата внутри внешнего кольца полигона и не попадает ли в область внутренних отверстий.


В OpenLayers полигон задаётся через структуру ol/geom/Polygon, которая хранит массив колец:

  • первое кольцо — внешний контур
  • последующие кольца — отверстия

Формат координат:

[
  [ [x1, y1], [x2, y2], ... ], // внешний контур
  [ [xA, yA], [xB, yB], ... ]  // отверстие (опционально)
]

Каждая координата интерпретируется в системе координат карты, чаще всего EPSG:3857.

Точка задаётся объектом ol/geom/Point:

import Point from 'ol/geom/Point';

const point = new Point([x, y]);

Проверка принадлежности через встроенные методы геометрии

OpenLayers предоставляет методы геометрии, позволяющие выполнять пространственные запросы без внешних библиотек.

Метод intersectsCoordinate

Наиболее универсальный вариант — проверка пересечения координаты с геометрией:

import Polygon from 'ol/geom/Polygon';

const polygon = new Polygon([
  [
    [0, 0],
    [10, 0],
    [10, 10],
    [0, 10],
    [0, 0]
  ]
]);

const coordinate = [5, 5];

const result = polygon.intersectsCoordinate(coordinate);

Если координата находится внутри полигона или на его границе, результат будет true.


Метод containsCoordinate

В ряде версий OpenLayers используется метод:

polygon.containsCoordinate(coordinate);

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


Работа с точкой как объектом Feature

Векторная модель OpenLayers строится на ol/Feature, где геометрия является свойством объекта.

import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';

const pointFeature = new Feature({
  geometry: new Point([5, 5])
});

Полигоны также представлены как Feature:

const polygonFeature = new Feature({
  geometry: polygon
});

Проверка принадлежности выполняется через извлечение геометрии:

const pointGeom = pointFeature.getGeometry();
const polygonGeom = polygonFeature.getGeometry();

const coordinate = pointGeom.getCoordinates();

const isInside = polygonGeom.intersectsCoordinate(coordinate);

Учет проекций координат

Ключевой источник ошибок — несовпадение систем координат.

OpenLayers по умолчанию использует EPSG:3857, тогда как внешние данные часто приходят в EPSG:4326.

Преобразование выполняется через ol/proj:

import { fromLonLat, toLonLat } from 'ol/proj';

const lonLat = [30.5, 50.4];
const projected = fromLonLat(lonLat);

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

const polygon = new Polygon([...]); // уже в EPSG:3857
const point = fromLonLat([30.5, 50.4]);

const result = polygon.intersectsCoordinate(point);

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


Проверка точки в полигоне через перебор вершин (ручной алгоритм)

При необходимости независимой логики применяется алгоритм ray casting (пересечение луча).

Идея: проводится горизонтальный луч вправо от точки и считается количество пересечений с рёбрами полигона.

function pointInPolygon(point, ring) {
  const x = point[0], y = point[1];
  let inside = false;

  for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
    const xi = ring[i][0], yi = ring[i][1];
    const xj = ring[j][0], yj = ring[j][1];

    const intersect =
      ((yi > y) !== (yj > y)) &&
      (x < (xj - xi) * (y - yi) / (yj - yi) + xi);

    if (intersect) inside = !inside;
  }

  return inside;
}

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

function pointInOlPolygon(point, polygon) {
  const rings = polygon.getCoordinates();

  const outer = rings[0];
  const holes = rings.slice(1);

  let inside = pointInPolygon(point, outer);

  if (!inside) return false;

  for (const hole of holes) {
    if (pointInPolygon(point, hole)) {
      return false;
    }
  }

  return true;
}

Использование Turf.js как альтернативы

При необходимости расширенной геопространственной аналитики применяется @turf/boolean-point-in-polygon.

import booleanPointInPolygon from '@turf/boolean-point-in-polygon';
import { point, polygon } from '@turf/helpers';

const pt = point([5, 5]);
const poly = polygon([[
  [0, 0],
  [10, 0],
  [10, 10],
  [0, 10],
  [0, 0]
]]);

const result = booleanPointInPolygon(pt, poly);

В OpenLayers такая проверка часто используется при анализе GeoJSON-данных до их отображения на карте.


Проверка точек из событий карты

Векторная работа часто связана с событиями click или pointermove.

map.on('click', function (event) {
  const coordinate = event.coordinate;

  const isInside = polygon.intersectsCoordinate(coordinate);
});

Координата события уже находится в проекции карты, поэтому дополнительное преобразование не требуется.


Проверка множества точек относительно полигона

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

const extent = polygon.getExtent();

function fastReject(coord) {
  return (
    coord[0] < extent[0] ||
    coord[0] > extent[2] ||
    coord[1] < extent[1] ||
    coord[1] > extent[3]
  );
}

Далее выполняется точная проверка:

if (!fastReject(coord)) {
  polygon.intersectsCoordinate(coord);
}

Работа с векторными слоями и фильтрация объектов

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

vectorSource.forEachFeature(function (feature) {
  const geometry = feature.getGeometry();
  const coordinate = pointFeature.getGeometry().getCoordinates();

  if (geometry.intersectsCoordinate(coordinate)) {
    // объект содержит точку
  }
});

Такой подход применяется для пространственного фильтра, кластеризации и поиска объектов в зоне.


Особенности точности и пограничных случаев

Геометрические проверки чувствительны к:

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

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


Интеграция с GeoJSON

Часто полигоны приходят в формате GeoJSON:

import GeoJSON from 'ol/format/GeoJSON';

const format = new GeoJSON();

const feature = format.readFeature(geojsonObject);
const geometry = feature.getGeometry();

const isInside = geometry.intersectsCoordinate([x, y]);

GeoJSON автоматически приводится к внутреннему формату OpenLayers, но проекция остаётся критическим параметром:

format.readFeature(geojsonObject, {
  dataProjection: 'EPSG:4326',
  featureProjection: 'EPSG:3857'
});

Комбинирование методов проверки

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

  1. проверка bounding box
  2. проверка через intersectsCoordinate
  3. при необходимости — внешний алгоритм (ray casting или Turf)
function containsPoint(polygon, coord) {
  if (!polygon.intersectsCoordinate(coord)) return false;
  return polygon.intersectsCoordinate(coord);
}

В более строгих сценариях второй вызов заменяется на точный алгоритм без оптимизаций.


Векторная логика в прикладных сценариях

Определение точки в полигоне используется при:

  • выделении административных зон
  • анализе геозон доставки
  • проверке попадания GPS-трека в область
  • обработке пользовательских кликов на карте
  • фильтрации объектов по области интереса

Каждый сценарий требует согласованности геометрий, корректной проекции и выбора между быстрым приближённым методом и точной топологической проверкой.