Геометрическая основа задачи
Работа с проверкой принадлежности точек полигону в Mapbox GL JS опирается на стандартные геометрические операции над GeoJSON-данными. В контексте веб-картографии полигон задаётся как замкнутый набор координат, а точка — как одиночная пара долгота/широта. Основная задача сводится к определению: лежит ли каждая точка из набора внутри заданной области или нет.
В GeoJSON полигон описывается структурой Polygon, где первая координатная цепочка является внешним контуром, а последующие (если есть) — вырезами:
const polygon = {
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [[
[30.1, 59.9],
[30.5, 59.9],
[30.5, 60.1],
[30.1, 60.1],
[30.1, 59.9]
]]
}
};
Точки, которые требуется проверить, обычно хранятся в виде FeatureCollection:
const points = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: { type: "Point", coordinates: [30.2, 60.0] },
properties: { id: 1 }
},
{
type: "Feature",
geometry: { type: "Point", coordinates: [30.6, 60.0] },
properties: { id: 2 }
}
]
};
Алгоритмически задача сводится к классическому point-in-polygon test, чаще всего реализуемому через ray casting или winding number. В экосистеме Mapbox GL JS подобные вычисления обычно выносятся в библиотеку Turf.js, которая предоставляет оптимизированные геопространственные функции.
Использование Turf.js для фильтрации точек
Наиболее прямолинейный способ определить принадлежность точек
полигону — функция booleanPointInPolygon:
import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
const insidePoints = points.features.filter((pt) =>
booleanPointInPolygon(pt, polygon)
);
Этот подход удобен для однократных вычислений, но при большом количестве объектов становится узким местом, так как каждый вызов выполняет геометрическую проверку в JavaScript.
Визуализация результата в Mapbox GL JS
После фильтрации данные можно передать в источник карты:
map.addSource("inside-points", {
type: "geojson",
data: {
type: "FeatureCollection",
features: insidePoints
}
});
map.addLayer({
id: "inside-layer",
type: "circle",
source: "inside-points",
paint: {
"circle-radius": 6,
"circle-color": "
}
});
Такой подход разделяет вычислительный этап и этап рендеринга, что соответствует архитектуре Mapbox GL JS, где источники данных независимы от визуальных слоёв.
Динамическая фильтрация без пересборки источника
При интерактивной работе с картой часто требуется не пересоздавать GeoJSON source, а использовать фильтры слоя. Однако важно понимать ограничение: Mapbox GL JS не умеет выполнять геометрические операции в фильтрах. Поэтому заранее вычисленный признак принадлежности становится обязательным.
Расширенный вариант — добавление свойства:
points.features.forEach((pt) => {
pt.properties.inside = booleanPointInPolygon(pt, polygon);
});
И последующее использование фильтра слоя:
map.addLayer({
id: "filtered-points",
type: "circle",
source: "points",
filter: ["==", ["get", "inside"], true],
paint: {
"circle-color": "#e74c3c",
"circle-radius": 5
}
});
Такой подход переносит вычисления в подготовительный этап и позволяет использовать мощную систему фильтрации Mapbox без дополнительных пересчётов на стороне рендера.
Использование queryRenderedFeatures для интерактивного анализа
Mapbox GL JS предоставляет возможность анализировать уже отрисованные
объекты через queryRenderedFeatures. Это особенно полезно
при работе с пользовательскими полигонами, нарисованными на карте.
map.on("click", (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ["points-layer"]
});
console.log(features);
});
Однако данный метод не заменяет полноценную проверку внутри полигона, так как он работает в экранных координатах и зависит от текущего зума и стиля.
Оптимизация при больших наборах данных
При работе с десятками или сотнями тысяч точек прямой перебор становится неэффективным. Основные стратегии оптимизации:
Bounding box фильтр часто используется как первый этап отсечения:
const bboxFiltered = points.features.filter((pt) => {
const [x, y] = pt.geometry.coordinates;
return x >= 30.1 && x <= 30.5 && y >= 59.9 && y <= 60.1;
});
Только после этого применяется точный алгоритм point-in-polygon.
Работа через Web Worker
Для сохранения отзывчивости интерфейса вычисления выносятся в отдельный поток:
// worker.js
importScripts("https://unpkg.com/@turf/turf/turf.min.js");
onmess age = function (e) {
const { points, polygon } = e.data;
const result = points.features.filter((pt) =>
turf.booleanPointInPolygon(pt, polygon)
);
postMessage(result);
};
Основной поток:
const worker = new Worker("worker.js");
worker.postMessage({ points, polygon });
worker.onmess age = (e) => {
map.getSource("inside-points").setData({
type: "FeatureCollection",
features: e.data
});
};
Такое разделение критично при интерактивных сценариях редактирования геометрии, когда пересчёт должен происходить мгновенно.
Полигональные маски и визуальные эффекты
Mapbox GL JS позволяет не только фильтровать точки, но и использовать полигоны как маски для визуального выделения областей. Один из распространённых приёмов — дублирование слоя с разными фильтрами: внутри и вне полигона.
map.addLayer({
id: "points-inside",
type: "circle",
source: "points",
filter: ["==", ["get", "inside"], true],
paint: { "circle-color": "#27ae60" }
});
map.addLayer({
id: "points-outside",
type: "circle",
source: "points",
filter: ["!=", ["get", "inside"], true],
paint: { "circle-color": "#95a5a6" }
});
Такой подход позволяет визуально разделять данные без изменения исходного набора.
Сложные полигоны и вложенные области
При работе с полигонами с отверстиями или мультиполигонами важно учитывать структуру GeoJSON. В случае MultiPolygon каждая отдельная область обрабатывается независимо, а отверстия внутри polygon исключаются из результата автоматически алгоритмом проверки.
Пример структуры:
const multiPolygon = {
type: "Feature",
geometry: {
type: "MultiPolygon",
coordinates: [
[
[
[30.0, 60.0],
[30.5, 60.0],
[30.5, 60.5],
[30.0, 60.5],
[30.0, 60.0]
]
],
[
[
[31.0, 60.0],
[31.5, 60.0],
[31.5, 60.5],
[31.0, 60.5],
[31.0, 60.0]
]
]
]
}
};
Корректная обработка таких структур особенно важна при анализе территориальных данных, где области могут быть разнесены географически, но логически объединены в один объект.
Производственные ограничения Mapbox GL JS
Несмотря на богатый API, Mapbox GL JS не предоставляет встроенных пространственных запросов уровня GIS-систем. Все операции анализа геометрии выполняются либо через сторонние библиотеки, либо через подготовленные атрибуты данных.
Ключевое ограничение заключается в том, что фильтры слоя работают только с атрибутами, а не с геометрией. Поэтому архитектура решений всегда строится вокруг предварительного вычисления пространственных отношений.
При проектировании систем с динамическими полигонами и большим количеством точек основная нагрузка переносится на этап подготовки данных, а рендеринг карты остаётся максимально лёгким и реактивным.