Операция проверки принадлежности точки геометрическому объекту в
контексте картографических приложений сводится к определению
пространственного отношения между координатой LatLng и
набором ограничивающих или произвольных геометрий.
В Leaflet геометрические сущности представлены объектами:
L.LatLng — точка на сфере (широта/долгота)L.LatLngBounds — прямоугольная областьL.Polygon / L.Polyline — произвольные
ломаные и многоугольникиБазовые стратегии проверки:
Самый быстрый и часто используемый способ — проверка попадания точки в прямоугольные границы.
Leaflet предоставляет встроенный метод:
const bounds = L.latLngBounds(
L.latLng(50.0, 30.0),
L.latLng(55.0, 40.0)
);
const point = L.latLng(52.5, 35.0);
const result = bounds.contains(point);
Внутренняя логика сводится к сравнению диапазонов:
Эквивалентная ручная реализация:
function isPointInBounds(point, bounds) {
return (
point.lat >= bounds.getSouth() &&
point.lat <= bounds.getNorth() &&
point.lng >= bounds.getWest() &&
point.lng <= bounds.getEast()
);
}
Особенности:
Любой многоугольник в Leaflet имеет ограничивающий прямоугольник:
const polygonBounds = polygon.getBounds();
if (!polygonBounds.contains(point)) {
// точка гарантированно вне полигона
}
Такой подход снижает число вычислений при массовой обработке точек, особенно при кластеризации или фильтрации событий.
Для произвольных полигонов применяется алгоритм ray casting (алгоритм пересечения луча).
Идея:
function isPointInPolygon(point, latlngs) {
let inside = false;
for (let i = 0, j = latlngs.length - 1; i < latlngs.length; j = i++) {
const xi = latlngs[i].lng, yi = latlngs[i].lat;
const xj = latlngs[j].lng, yj = latlngs[j].lat;
const intersect =
((yi > point.lat) !== (yj > point.lat)) &&
(point.lng < (xj - xi) * (point.lat - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
}
Структура координат полигона:
const latlngs = polygon.getLatLngs();
Для простого полигона:
const ring = polygon.getLatLngs()[0];
Для мультиполигонов структура вложенная:
const multi = polygon.getLatLngs();
// multi = [ [ring1], [ring2], ... ]
Многоугольники могут содержать внутренние вырезы.
Логика проверки:
function isPointInPolygonWithHoles(point, polygonLatLngs) {
const outer = polygonLatLngs[0];
const holes = polygonLatLngs.slice(1);
if (!isPointInPolygon(point, outer)) {
return false;
}
for (const hole of holes) {
if (isPointInPolygon(point, hole)) {
return false;
}
}
return true;
}
Leaflet оперирует географическими координатами в WGS84, однако алгоритмы point-in-polygon работают в декартовой проекции.
Следствия:
Для геопространственной точности часто применяется библиотека геоанализа:
import booleanPointInPolygon from '@turf/boolean-point-in-polygon';
const point = turf.point([lng, lat]);
const poly = turf.polygon([coordinates]);
const result = booleanPointInPolygon(point, poly);
Преимущества:
Типичный сценарий — фильтрация кликов по слоям:
map.on('click', (e) => {
const point = e.latlng;
polygons.forEach(poly => {
const bounds = poly.getBounds();
if (!bounds.contains(point)) return;
const latlngs = poly.getLatLngs()[0];
if (isPointInPolygon(point, latlngs)) {
poly.fire('pointinside', { latlng: point });
}
});
});
При большом количестве объектов применяется многоуровневая фильтрация:
Пример структуры индекса:
const index = new Map(); // cellId -> polygons[]
Фильтрация:
const candidates = index.get(cellId) || [];
Polyline в Leaflet рассматривается как набор отрезков. Проверка принадлежности точки линии выполняется через минимальное расстояние до сегмента:
function distanceToSegment(p, v, w) {
const l2 = (w.lat - v.lat)**2 + (w.lng - v.lng)**2;
if (l2 === 0) return Math.hypot(p.lat - v.lat, p.lng - v.lng);
let t = ((p.lat - v.lat)*(w.lat - v.lat) + (p.lng - v.lng)*(w.lng - v.lng)) / l2;
t = Math.max(0, Math.min(1, t));
const projLat = v.lat + t * (w.lat - v.lat);
const projLng = v.lng + t * (w.lng - v.lng);
return Math.hypot(p.lat - projLat, p.lng - projLng);
}
При проверке принадлежности точки учитываются:
Типичная стабилизация:
const EPS = 1e-12;
function nearlyEqual(a, b) {
return Math.abs(a - b) < EPS;
}
function pointInLayer(point, layer) {
if (!layer.getBounds().contains(point)) return false;
const latlngs = layer.getLatLngs()[0];
return isPointInPolygon(point, latlngs);
}
Многоуровневый подход минимизирует стоимость вычислений и обеспечивает стабильную работу при больших наборах геометрий.