Построение линии по точкам

Линия в геопространственных данных представляется как объект типа LineString в формате GeoJSON. Такой объект состоит из упорядоченного массива координат, где каждая точка задаётся парой [долгота, широта] или тройкой [долгота, широта, высота].

Базовая структура:

{
  "type": "LineString",
  "coordinates": [
    [30.5, 50.5],
    [31.0, 50.7],
    [31.5, 51.0]
  ]
}

Ключевой аспект при работе с линиями — строгий порядок точек. Геометрия интерпретируется как последовательное соединение сегментов, поэтому перестановка координат меняет форму линии.

Turf.js предоставляет набор инструментов для создания, преобразования и анализа таких объектов, включая построение линий из набора точек, вычисление длины, интерполяцию и разбиение.


Базовое построение линии из массива точек

Основная функция для создания линии — turf.lineString. Она принимает массив координат и возвращает GeoJSON-объект типа LineString.

import { lineString } fr om "@turf/turf";

const points = [
  [30.5, 50.5],
  [31.0, 50.7],
  [31.5, 51.0]
];

const line = lineString(points);

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


Построение линии из набора точек Feature

В реальных задачах точки часто представлены как Feature<Point>. В таком случае требуется извлечение координат перед построением линии.

import { lineString } from "@turf/turf";

const pointFeatures = [
  { type: "Feature", geometry: { type: "Point", coordinates: [30.5, 50.5] } },
  { type: "Feature", geometry: { type: "Point", coordinates: [31.0, 50.7] } },
  { type: "Feature", geometry: { type: "Point", coordinates: [31.5, 51.0] } }
];

const coordinates = pointFeatures.map(p => p.geometry.coordinates);

const line = lineString(coordinates);

Такой подход характерен для обработки данных, полученных из API или базы пространственных объектов.


Сортировка точек перед построением линии

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

Пример сортировки по индексу:

const points = [
  { coords: [31.5, 51.0], index: 3 },
  { coords: [30.5, 50.5], index: 1 },
  { coords: [31.0, 50.7], index: 2 }
];

points.sort((a, b) => a.index - b.index);

const line = lineString(points.map(p => p.coords));

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


Построение линии с атрибутами

Turf.js позволяет добавлять свойства к линии, которые сохраняются в properties.

const line = lineString(
  [
    [30.5, 50.5],
    [31.0, 50.7],
    [31.5, 51.0]
  ],
  {
    name: "Маршрут 1",
    type: "delivery-route",
    speedLim it: 60
  }
);

Эти свойства используются в визуализации и аналитике, но не влияют на геометрию.


Интерполяция точек для сглаживания линии

При редких точках линия может выглядеть «ломаной». Turf.js предоставляет инструменты для добавления промежуточных точек.

Используется подход через turf.along и turf.lineDistance.

import { lineString, lineDistance, along } from "@turf/turf";

const line = lineString([
  [30.5, 50.5],
  [31.0, 50.7],
  [31.5, 51.0]
]);

const distance = lineDistance(line, { units: "kilometers" });

const segmentStep = 5;
const interpolated = [];

for (let i = 0; i <= distance; i += segmentStep) {
  interpolated.push(along(line, i, { units: "kilometers" }).geometry.coordinates);
}

const smoothLine = lineString(interpolated);

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


Построение ломаной линии из GPS-трека

GPS-данные часто содержат шум и требуют предварительной обработки.

Типичная структура трека:

const track = [
  [30.5001, 50.5002],
  [30.5005, 50.5006],
  [30.5010, 50.5010],
  [30.5100, 50.5100]
];

Прямое построение:

const route = lineString(track);

Однако при анализе движения часто применяется фильтрация выбросов. Простая эвристика — удаление точек с аномально большим расстоянием между соседями.


Построение линии из случайно распределённых точек

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

function buildOrderedLine(points) {
  const unused = [...points];
  const ordered = [unused.shift()];

  while (unused.length) {
    const last = ordered[ordered.length - 1];

    let nearestIndex = 0;
    let nearestDist = Infinity;

    unused.forEach((p, i) => {
      const dx = p[0] - last[0];
      const dy = p[1] - last[1];
      const d = dx * dx + dy * dy;

      if (d < nearestDist) {
        nearestDist = d;
        nearestIndex = i;
      }
    });

    ordered.push(unused.splice(nearestIndex, 1)[0]);
  }

  return lineString(ordered);
}

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


Объединение нескольких сегментов в единую линию

При наличии нескольких линий используется turf.lineMerge.

import { lineMerge, featureCollection, lineString } from "@turf/turf";

const lines = featureCollection([
  lineString([[30, 50], [31, 51]]),
  lineString([[31, 51], [32, 52]])
]);

const merged = lineMerge(lines);

Результат объединяет смежные сегменты в одну непрерывную геометрию.


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

Корректная линия должна удовлетворять нескольким условиям:

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

Простейшая валидация:

function validateLine(coords) {
  return (
    Array.isArray(coords) &&
    coords.length >= 2 &&
    coords.every(
      p =>
        Array.isArray(p) &&
        typeof p[0] === "number" &&
        typeof p[1] === "number" &&
        p[1] >= -90 &&
        p[1] <= 90 &&
        p[0] >= -180 &&
        p[0] <= 180
    )
  );
}

Использование линии в аналитических задачах Turf.js

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

  • вычисление длины маршрута через turf.length
  • извлечение участка линии через turf.lineSlice
  • определение точки на линии через turf.along
  • упрощение геометрии через turf.simplify

Пример получения длины:

import { length, lineString } from "@turf/turf";

const line = lineString([
  [30.5, 50.5],
  [31.0, 50.7],
  [31.5, 51.0]
]);

const dist = length(line, { units: "kilometers" });

Оптимизация геометрии перед отрисовкой

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

import { simplify, lineString } from "@turf/turf";

const line = lineString(hugeCoordinateArray);

const simplified = simplify(line, {
  tolerance: 0.001,
  highQuality: true
});

Параметр tolerance определяет степень упрощения: чем выше значение, тем меньше точек остаётся.


Построение линии из сегментов разной длины

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

  • приведение координат к единому шагу
  • интерполяция недостающих точек
  • удаление дубликатов
function removeDuplicates(coords) {
  return coords.filter((p, i, arr) => {
    if (i === 0) return true;
    const prev = arr[i - 1];
    return p[0] !== prev[0] || p[1] !== prev[1];
  });
}

const clean = removeDuplicates(rawCoords);
const line = lineString(clean);