Валидация геометрии

В OpenLayers геометрия рассматривается как структурированный объект, описывающий пространственные данные: точки, линии, полигоны и их комбинации. Корректность таких объектов критична для рендеринга, пространственных операций и обмена данными через GeoJSON/WKT.

Валидность геометрии определяется соответствием геометрического объекта набору правил, зависящих от типа:

  • отсутствие самопересечений (для полигонов и линий в строгих сценариях);
  • корректный порядок координат;
  • замкнутость колец у полигонов;
  • допустимые значения координат (числа, не NaN/Infinity);
  • отсутствие пустых или деградированных структур (например, полигон с менее чем 3 вершинами);
  • согласованность вложенных колец (внешний контур и внутренние вырезы).

OpenLayers не навязывает строгую топологическую проверку по умолчанию, оставляя это прикладной логике, но предоставляет инструменты для работы с геометриями через ol/geom и форматами сериализации.


Структура геометрий и источники ошибок

Основные классы геометрий OpenLayers:

  • Point
  • LineString
  • LinearRing
  • Polygon
  • MultiPoint
  • MultiLineString
  • MultiPolygon
  • GeometryCollection

Ошибки валидности чаще всего возникают на уровнях:

Некорректные координаты

Координаты должны быть числами в формате [x, y] или [x, y, z].

Проблемные случаи:

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

Ломаная структура линий

LineString требует минимум двух точек. LinearRing требует минимум четырёх координат, причём первая и последняя должны совпадать.

Полигональные ошибки

Polygon состоит из массива колец:

  • первое кольцо — внешний контур;
  • последующие — внутренние вырезы.

Типичные нарушения:

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

Базовая проверка структуры геометрии

OpenLayers предоставляет доступ к координатам через методы getCoordinates() и setCoordinates(), что позволяет выполнять первичную валидацию на уровне структуры.

Простейшая проверка может быть реализована вручную:

import Polygon from 'ol/geom/Polygon';

function isFiniteNumber(value) {
  return typeof value === 'number' && isFinite(value);
}

function validateCoordinate(coord) {
  if (!Array.isArray(coord)) return false;
  if (!isFiniteNumber(coord[0]) || !isFiniteNumber(coord[1])) return false;
  return true;
}

function validateLinearRing(ring) {
  if (!Array.isArray(ring) || ring.length < 4) return false;

  const first = ring[0];
  const last = ring[ring.length - 1];

  if (first[0] !== last[0] || first[1] !== last[1]) return false;

  return ring.every(validateCoordinate);
}

function validatePolygon(polygon) {
  const coords = polygon.getCoordinates();

  if (!Array.isArray(coords) || coords.length === 0) return false;

  const outerRing = coords[0];
  if (!validateLinearRing(outerRing)) return false;

  return true;
}

Такой уровень проверки покрывает синтаксическую корректность, но не геометрическую топологию.


Проверка самопересечений

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

Подход через векторную математику

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

Алгоритм:

  1. Разбить кольцо на отрезки.
  2. Проверить пересечение каждой пары отрезков.
  3. Исключить смежные сегменты.

Упрощённая проверка пересечения:

function orientation(a, b, c) {
  return (b[1] - a[1]) * (c[0] - b[0]) -
         (b[0] - a[0]) * (c[1] - b[1]);
}

function segmentsIntersect(a, b, c, d) {
  const o1 = orientation(a, b, c);
  const o2 = orientation(a, b, d);
  const o3 = orientation(c, d, a);
  const o4 = orientation(c, d, b);

  return (o1 * o2 < 0) && (o3 * o4 < 0);
}

Для полигона:

function hasSelfIntersection(ring) {
  for (let i = 0; i < ring.length - 1; i++) {
    for (let j = i + 1; j < ring.length - 1; j++) {

      if (Math.abs(i - j) <= 1) continue;
      if (i === 0 && j === ring.length - 2) continue;

      if (segmentsIntersect(
        ring[i], ring[i + 1],
        ring[j], ring[j + 1]
      )) {
        return true;
      }
    }
  }
  return false;
}

Использование сторонних библиотек для строгой топологии

Для серьёзной геопространственной валидации применяются специализированные библиотеки:

  • JSTS (JavaScript Topology Suite)
  • Turf.js
  • GEOS через серверные решения

Пример с JSTS

import GeoJSON from 'ol/format/GeoJSON';
import { GeometryFactory } from 'jsts/org/locationtech/jts/geom';

function validateWithJSTS(feature) {
  const format = new GeoJSON();
  const geojson = format.writeFeatureObject(feature);

  const reader = new jsts.io.GeoJSONReader();
  const geom = reader.read(geojson.geometry);

  return geom.isValid();
}

Метод isValid() проверяет:

  • самопересечения;
  • корректность колец;
  • топологическую согласованность.

Валидация при чтении GeoJSON

При загрузке данных через ol/format/GeoJSON возможны ошибки входного формата.

import GeoJSON from 'ol/format/GeoJSON';

const format = new GeoJSON();

const features = format.readFeatures(geojsonData, {
  featureProjection: 'EPSG:3857'
});

Проблемные случаи:

  • отсутствует type: "Feature" или FeatureCollection;
  • геометрия равна null;
  • координаты имеют неверную вложенность;
  • смешение 2D и 3D координат без обработки.

Дополнительная проверка структуры:

function validateGeoJSONGeometry(geometry) {
  if (!geometry || !geometry.type) return false;

  if (geometry.type === 'Polygon') {
    return Array.isArray(geometry.coordinates);
  }

  if (geometry.type === 'LineString') {
    return Array.isArray(geometry.coordinates);
  }

  return true;
}

Проверка согласованности проекций

OpenLayers оперирует координатами в разных проекциях, чаще всего:

  • EPSG:4326 (географические координаты);
  • EPSG:3857 (Web Mercator).

Несоответствие проекций приводит к визуально некорректной геометрии и ошибкам пространственного анализа.

Типичный сценарий ошибки:

  • геометрия создана в EPSG:4326;
  • отображается в карте EPSG:3857 без трансформации.

Корректная трансформация:

import { transform } from 'ol/proj';

function transformCoords(coord) {
  return transform(coord, 'EPSG:4326', 'EPSG:3857');
}

Для массивов координат требуется рекурсивная трансформация.


Проверка деградации геометрии

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

  • полигон с нулевой площадью;
  • линия, где все точки совпадают;
  • мультигеометрия с пустыми элементами.

Проверка площади:

import Polygon from 'ol/geom/Polygon';

function hasArea(polygon) {
  return polygon.getArea() > 0;
}

Для линий:

import LineString from 'ol/geom/LineString';

function isDegenerateLine(line) {
  const coords = line.getCoordinates();
  return coords.every(c => c[0] === coords[0][0] && c[1] === coords[0][1]);
}

Интеграция в пайплайн обработки данных

Валидация геометрии обычно встраивается в цепочку обработки:

  1. получение данных (GeoJSON, WKT, API);
  2. десериализация через ol/format/*;
  3. структурная проверка;
  4. топологическая проверка;
  5. трансформация проекций;
  6. добавление в VectorSource.

Пример интеграции:

import VectorSource from 'ol/source/Vector';

function addValidatedFeature(source, feature) {
  const geom = feature.getGeometry();

  if (!geom) return;

  if (geom.getType() === 'Polygon') {
    const coords = geom.getCoordinates();
    if (hasSelfIntersection(coords[0])) return;
  }

  source.addFeature(feature);
}

Обработка ошибок и стратегия отказоустойчивости

При работе с внешними источниками данных ошибки геометрии неизбежны. Применяются стратегии:

  • пропуск невалидных объектов;
  • логирование проблемных геометрий;
  • попытка исправления (snapping, simplification);
  • разбиение сложных объектов на части.

Простейшая стратегия фильтрации:

function filterValidFeatures(features) {
  return features.filter(f => {
    const geom = f.getGeometry();
    return geom && geom.getCoordinates().length > 0;
  });
}

Типичные ошибки при ручном создании геометрий

При программной генерации часто возникают:

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

Особенно критичны операции геометрической генерации:

  • буферизация;
  • сдвиг (offset);
  • пересечения;
  • упрощение (simplify).

Каждая из них требует повторной валидации результата.


Проверка после геометрических операций

После трансформаций геометрия может потерять корректность.

import { buffer } from 'ol/extent';

function safeOperation(feature) {
  const geom = feature.getGeometry();

  const result = geom.buffer(10);

  if (!result) return null;

  if (result.getCoordinates().length === 0) return null;

  feature.setGeometry(result);
  return feature;
}

Особое внимание требуется операциям:

  • simplify
  • buffer
  • intersection
  • union (при использовании внешних библиотек)

Контроль качества геометрии в векторных слоях

При работе с VectorLayer в OpenLayers валидность геометрий напрямую влияет на:

  • скорость отрисовки;
  • корректность hit-testing;
  • стабильность WebGL/Canvas рендерера;
  • пространственные запросы.

Рекомендуется централизованная точка проверки перед добавлением в VectorSource, а не распределённая логика по компонентам приложения.

import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';

const source = new VectorSource({
  features: []
});

const layer = new VectorLayer({
  source
});