Пространственная фильтрация представляет собой процесс отбора географических объектов на основе их пространственного положения, взаимного расположения или принадлежности определённой территории. В геоинформационных системах подобные операции используются для поиска объектов внутри заданной области, выявления пересечений, отбора ближайших объектов и анализа пространственных отношений.
Библиотека Turf.js предоставляет широкий набор инструментов для выполнения таких операций непосредственно в браузере или на сервере Node.js, работая с объектами формата GeoJSON.
На практике пространственная фильтрация применяется для решения следующих задач:
Для решения подобных задач Turf.js предоставляет специализированные функции пространственного анализа.
Одной из самых распространённых операций является определение того, находится ли точка внутри полигона.
Для этого используется функция
booleanPointInPolygon().
import * as turf from "@turf/turf";
const point = turf.point([37.62, 55.75]);
const polygon = turf.polygon([[
[37.50, 55.70],
[37.80, 55.70],
[37.80, 55.85],
[37.50, 55.85],
[37.50, 55.70]
]]);
const result = turf.booleanPointInPolygon(point, polygon);
console.log(result);
Результат:
true
Если точка находится внутри границ полигона, функция возвращает
true, иначе — false.
Часто необходимо проверить сразу большое количество объектов.
Исходная коллекция:
const points = turf.featureCollection([
turf.point([37.60, 55.76], { name: "A" }),
turf.point([37.90, 55.76], { name: "B" }),
turf.point([37.70, 55.80], { name: "C" })
]);
Фильтрация:
const filtered = points.features.filter(feature =>
turf.booleanPointInPolygon(feature, polygon)
);
console.log(filtered);
В результирующий массив попадут только точки, расположенные внутри полигона.
Такой подход широко применяется при анализе объектов недвижимости, клиентов, транспортных средств и других пространственных сущностей.
Для определения факта пересечения используется функция
booleanIntersects().
const polygon1 = turf.polygon([[
[0, 0],
[5, 0],
[5, 5],
[0, 5],
[0, 0]
]]);
const polygon2 = turf.polygon([[
[3, 3],
[8, 3],
[8, 8],
[3, 8],
[3, 3]
]]);
Проверка:
const intersects = turf.booleanIntersects(
polygon1,
polygon2
);
console.log(intersects);
Результат:
true
Пересечение означает наличие хотя бы одной общей точки между объектами.
Допустим, имеется набор земельных участков.
const parcels = [
parcel1,
parcel2,
parcel3,
parcel4
];
Необходимо оставить только те участки, которые пересекаются с зоной строительства.
const filteredParcels = parcels.filter(parcel =>
turf.booleanIntersects(parcel, constructionZone)
);
После выполнения операции остаются исключительно объекты, имеющие пространственный контакт с заданной территорией.
Иногда недостаточно знать факт пересечения. Требуется определить, полностью ли объект находится внутри другой геометрии.
Для этого применяется функция booleanWithin().
const building = turf.polygon([[
[1, 1],
[2, 1],
[2, 2],
[1, 2],
[1, 1]
]]);
const district = turf.polygon([[
[0, 0],
[5, 0],
[5, 5],
[0, 5],
[0, 0]
]]);
Проверка:
const isInside = turf.booleanWithin(
building,
district
);
console.log(isInside);
Результат:
true
Полезно при определении объектов, полностью принадлежащих административной территории.
Обратной операцией является определение того, содержит ли одна геометрия другую.
Используется функция booleanContains().
const result = turf.booleanContains(
district,
building
);
console.log(result);
Результат:
true
Несмотря на схожесть с booleanWithin(), направление
проверки имеет принципиальное значение.
В некоторых задачах требуется определить факт соприкосновения объектов.
Для этого существует функция booleanTouches().
const polyA = turf.polygon([[
[0, 0],
[4, 0],
[4, 4],
[0, 4],
[0, 0]
]]);
const polyB = turf.polygon([[
[4, 1],
[6, 1],
[6, 3],
[4, 3],
[4, 1]
]]);
Проверка:
const touches = turf.booleanTouches(
polyA,
polyB
);
Результат:
true
Объекты имеют общую границу, но не перекрывают площади друг друга.
Для анализа наложения объектов используется функция
booleanOverlap().
const overlap = turf.booleanOverlap(
polygon1,
polygon2
);
console.log(overlap);
Результат:
true
Перекрытие означает наличие общей области ненулевой площади.
Подобная проверка востребована при анализе кадастровых данных, экологических зон и территориального планирования.
Частой задачей является поиск объектов в пределах определённого радиуса.
Для вычисления расстояния применяется функция
distance().
const center = turf.point([37.62, 55.75]);
const stores = [
turf.point([37.63, 55.76], { name: "Store A" }),
turf.point([37.80, 55.80], { name: "Store B" }),
turf.point([37.61, 55.74], { name: "Store C" })
];
const nearbyStores = stores.filter(store => {
const dist = turf.distance(
center,
store,
{ units: "kilometers" }
);
return dist <= 2;
});
В результате будут получены объекты, расположенные не далее двух километров от центра.
Для пространственной фильтрации часто создают буфер вокруг объекта.
Буфер представляет собой область на фиксированном расстоянии от исходной геометрии.
const buffer = turf.buffer(
center,
2,
{ units: "kilometers" }
);
Буфер будет представлен полигоном.
const result = stores.filter(store =>
turf.booleanPointInPolygon(
store,
buffer
)
);
Такой подход особенно удобен при поиске объектов рядом с дорогами, станциями метро, складами и пунктами выдачи.
Полный пример:
const station = turf.point([37.62, 55.75]);
const stationBuffer = turf.buffer(
station,
1,
{ units: "kilometers" }
);
const nearbyPoints = points.features.filter(point =>
turf.booleanPointInPolygon(
point,
stationBuffer
)
);
Сначала формируется зона поиска, затем выполняется фильтрация всех объектов по принадлежности этой зоне.
Для ускорения пространственных запросов часто используется ограничивающий прямоугольник (Bounding Box).
Создание рамки:
const bbox = [
37.50,
55.70,
37.80,
55.85
];
Преобразование в полигон:
const bboxPolygon = turf.bboxPolygon(bbox);
Фильтрация:
const filtered = points.features.filter(point =>
turf.booleanPointInPolygon(
point,
bboxPolygon
)
);
Подобный подход значительно быстрее сложных геометрических проверок и часто используется как предварительный этап фильтрации.
Для работы с большими коллекциями точек Turf.js предоставляет специализированную функцию.
const points = turf.featureCollection([
turf.point([1, 1]),
turf.point([2, 2]),
turf.point([10, 10])
]);
const result = turf.pointsWithinPolygon(
points,
polygon
);
Возвращается новая коллекция GeoJSON, содержащая только подходящие объекты.
Преимущества метода:
Дороги, маршруты и трубопроводы обычно представлены линиями.
Проверка пересечения линии и полигона:
const intersects = turf.booleanIntersects(
road,
district
);
Фильтрация набора дорог:
const roadsInsideDistrict = roads.filter(road =>
turf.booleanIntersects(
road,
district
)
);
Подход позволяет быстро выделять инфраструктурные объекты, относящиеся к заданной территории.
Для коллекции полигонов принцип остаётся тем же.
const selectedAreas = areas.filter(area =>
turf.booleanWithin(
area,
region
)
);
Либо:
const selectedAreas = areas.filter(area =>
turf.booleanIntersects(
area,
region
)
);
Выбор функции зависит от требований задачи:
| Функция | Назначение |
|---|---|
booleanWithin |
Полное нахождение внутри |
booleanContains |
Полное включение |
booleanIntersects |
Любое пересечение |
booleanTouches |
Касание границ |
booleanOverlap |
Перекрытие площадей |
Нередко требуется учитывать несколько критериев одновременно.
Пример: объекты должны находиться внутри района и располагаться не дальше одного километра от станции метро.
const filtered = objects.filter(object => {
const insideDistrict =
turf.booleanPointInPolygon(
object,
district
);
const distanceToMetro =
turf.distance(
object,
metroStation,
{ units: "kilometers" }
);
return insideDistrict &&
distanceToMetro <= 1;
});
Подобная логика лежит в основе большинства современных геоинформационных сервисов.
При работе с большими объёмами данных важно учитывать производительность.
Наиболее распространённые методы оптимизации:
Сначала выполняется грубый отбор по ограничивающему прямоугольнику, затем точная геометрическая проверка.
const bboxPolygon = turf.bboxPolygon(
turf.bbox(region)
);
После этого:
const candidates = objects.filter(obj =>
turf.booleanIntersects(
obj,
bboxPolygon
)
);
И только затем:
const result = candidates.filter(obj =>
turf.booleanWithin(
obj,
region
)
);
Предпочтительно применять:
turf.pointsWithinPolygon()
вместо ручного перебора тысяч точек.
Буферы, полигоны и Bounding Box желательно вычислять один раз и переиспользовать.
Плохо:
objects.filter(obj =>
turf.booleanPointInPolygon(
obj,
turf.buffer(center, 1)
)
);
Хорошо:
const zone = turf.buffer(
center,
1,
{ units: "kilometers" }
);
objects.filter(obj =>
turf.booleanPointInPolygon(
obj,
zone
)
);
Последовательность обработки данных в реальном проекте обычно выглядит следующим образом:
Подобный конвейер позволяет эффективно обрабатывать десятки и сотни тысяч пространственных объектов средствами Turf.js без использования полноценных серверных ГИС-систем.