Классификация точек — это процесс определения принадлежности географической точки к определённой области, объекту или категории на основе её пространственного положения. В геоинформационных системах такая задача возникает постоянно: необходимо определить, находится ли объект внутри административной границы, попадает ли пользователь в зону обслуживания, принадлежит ли координата определённому району или лежит ли она в пределах заданного полигона.
Библиотека Turf.js предоставляет набор функций пространственного анализа, позволяющих выполнять подобные проверки непосредственно в JavaScript без использования специализированных ГИС-серверов.
Основными задачами классификации точек являются:
Перед выполнением классификации необходимо понимать типы геометрий GeoJSON.
const point = turf.point([37.6176, 55.7558]);
Структура GeoJSON:
{
"type": "Point",
"coordinates": [37.6176, 55.7558]
}
Точка представляет собой единственную пару координат.
const line = turf.lineString([
[37.5, 55.7],
[37.7, 55.8]
]);
Линия состоит из последовательности точек.
const polygon = turf.polygon([
[
[37.5, 55.7],
[37.8, 55.7],
[37.8, 55.9],
[37.5, 55.9],
[37.5, 55.7]
]
]);
Полигон описывает замкнутую область.
const multiPolygon = turf.multiPolygon([
[
[
[37.5, 55.7],
[37.6, 55.7],
[37.6, 55.8],
[37.5, 55.8],
[37.5, 55.7]
]
],
[
[
[37.7, 55.7],
[37.8, 55.7],
[37.8, 55.8],
[37.7, 55.8],
[37.7, 55.7]
]
]
]);
Мультиполигон объединяет несколько независимых полигонов.
Наиболее распространённая задача классификации — определение того, находится ли точка внутри полигона.
Для этого используется функция
booleanPointInPolygon.
const point = turf.point([37.65, 55.8]);
const polygon = turf.polygon([
[
[37.5, 55.7],
[37.8, 55.7],
[37.8, 55.9],
[37.5, 55.9],
[37.5, 55.7]
]
]);
const inside = turf.booleanPointInPolygon(
point,
polygon
);
console.log(inside);
Результат:
true
Точка попадает внутрь полигона.
const point = turf.point([38.0, 56.0]);
const result =
turf.booleanPointInPolygon(
point,
polygon
);
console.log(result);
Результат:
false
Координаты расположены за пределами области.
Функция booleanPointInPolygon() реализует модификацию
алгоритма Ray Casting.
Суть метода:
Схематично:
Точка внутри
+----------------+
| |
| ● -----> |
| |
+----------------+
Количество пересечений = 1
Точка снаружи
● ----->
+----------------+
| |
| |
+----------------+
Количество пересечений = 0
Особое внимание следует уделять ситуациям, когда точка лежит на границе.
const point = turf.point([37.5, 55.8]);
const result =
turf.booleanPointInPolygon(
point,
polygon
);
Точка на границе обычно считается принадлежащей полигону.
Для строгой проверки используется параметр
ignoreBoundary.
const result =
turf.booleanPointInPolygon(
point,
polygon,
{
ignoreBoundary: true
}
);
Теперь точка на границе будет считаться внешней.
Это полезно при:
Многие реальные географические объекты представлены несколькими отдельными участками.
Например:
Проверка выполняется аналогично.
const point =
turf.point([37.55, 55.75]);
const result =
turf.booleanPointInPolygon(
point,
multiPolygon
);
Если точка находится хотя бы в одном полигоне мультиполигона, результат будет:
true
Часто требуется определить принадлежность большого количества точек одной территории.
const points = turf.featureCollection([
turf.point([37.55, 55.75]),
turf.point([37.65, 55.82]),
turf.point([38.0, 55.9])
]);
const classified =
points.features.map(feature => ({
...feature,
properties: {
inside:
turf.booleanPointInPolygon(
feature,
polygon
)
}
}));
Результат:
[
{ inside: true },
{ inside: true },
{ inside: false }
]
Каждая точка получает собственную пространственную категорию.
Классификация часто используется для присвоения атрибутов.
Предположим, имеется набор районов города.
const districts = [
{
name: "Северный",
polygon: northDistrict
},
{
name: "Южный",
polygon: southDistrict
}
];
Определение района:
function findDistrict(point) {
for (const district of districts) {
if (
turf.booleanPointInPolygon(
point,
district.polygon
)
) {
return district.name;
}
}
return null;
}
Использование:
const district =
findDistrict(userPoint);
Результат:
"Северный"
Иногда точка не принадлежит ни одной области, но требуется определить ближайший объект.
Для этого используется функция nearestPoint.
const stores =
turf.featureCollection([
turf.point([37.6, 55.75], {
name: "Магазин 1"
}),
turf.point([37.8, 55.8], {
name: "Магазин 2"
})
]);
const user =
turf.point([37.7, 55.78]);
const nearest =
turf.nearestPoint(
user,
stores
);
console.log(
nearest.properties.name
);
Результат:
"Магазин 2"
Такая классификация относится к категории анализа близости.
Иногда принадлежность определяется не полигоном, а радиусом.
const center =
turf.point([37.6176, 55.7558]);
const point =
turf.point([37.7, 55.8]);
const distance =
turf.distance(
center,
point,
{
units: "kilometers"
}
);
let zone;
if (distance < 5) {
zone = "A";
} else if (distance < 15) {
zone = "B";
} else {
zone = "C";
}
Результат:
"B"
Подобная схема применяется для:
Буфер создаёт область вокруг объекта.
const buffer =
turf.buffer(
center,
10,
{
units: "kilometers"
}
);
После этого точка классифицируется относительно буфера.
const inside =
turf.booleanPointInPolygon(
point,
buffer
);
Результат показывает, находится ли объект в пределах заданного радиуса.
Иногда необходимо определить, лежит ли точка на маршруте.
Для этого применяется функция booleanPointOnLine.
const route =
turf.lineString([
[37.5, 55.7],
[37.8, 55.9]
]);
const point =
turf.point([37.65, 55.8]);
const result =
turf.booleanPointOnLine(
point,
route
);
Результат:
true
Типичная задача транспортных систем — определение нахождения транспортного средства на маршруте.
const onRo ute =
turf.booleanPointOnLine(
gpsPoint,
route
);
Если результат:
true
транспорт движется по заданному маршруту.
Если:
false
произошло отклонение.
Допустим, существуют несколько объектов обслуживания.
const stations = [
{
name: "Станция 1",
point: station1
},
{
name: "Станция 2",
point: station2
}
];
Для каждого объекта создаётся буфер.
const serviceArea =
turf.buffer(
station.point,
5,
{
units: "kilometers"
}
);
Далее проверяется принадлежность точки буферу.
const covered =
turf.booleanPointInPolygon(
userPoint,
serviceArea
);
Так реализуются:
При работе с тысячами точек возникают проблемы производительности.
Наивный подход:
for (const point of points) {
for (const polygon of polygons) {
turf.booleanPointInPolygon(
point,
polygon
);
}
}
Сложность:
O(n × m)
где:
Получение границ полигона:
const bbox =
turf.bbox(polygon);
Проверка попадания в рамку:
const inBox =
pointLng >= bbox[0] &&
pointLat >= bbox[1] &&
pointLng <= bbox[2] &&
pointLat <= bbox[3];
Только после этого выполняется более дорогая операция:
turf.booleanPointInPolygon(
point,
polygon
);
Такой подход существенно ускоряет массовую классификацию.
| Функция | Назначение |
|---|---|
booleanPointInPolygon() |
Проверка нахождения точки внутри полигона |
booleanPointOnLine() |
Проверка принадлежности точки линии |
nearestPoint() |
Поиск ближайшей точки |
distance() |
Вычисление расстояния между объектами |
buffer() |
Создание зон вокруг объектов |
bbox() |
Получение ограничивающего прямоугольника |
pointsWithinPolygon() |
Поиск точек внутри полигона |
collect() |
Сбор атрибутов объектов внутри области |
Для одновременной классификации множества точек используется функция
pointsWithinPolygon.
const result =
turf.pointsWithinPolygon(
points,
polygon
);
Результатом станет новый набор данных, содержащий только точки, попавшие внутрь области.
Это значительно удобнее и эффективнее ручной фильтрации через цикл при обработке больших коллекций GeoJSON.