Анализ видимости в пространственных задачах опирается на проверку пересечения лучей, сегментов и полигональных препятствий. В контексте Turf.js такие операции реализуются через комбинацию базовых геометрических функций: построение геометрий, вычисление пересечений, проверка принадлежности и измерение расстояний.
Ключевые типы объектов:
Базовый набор функций:
turf.point()turf.lineString()turf.polygon()turf.booleanPointInPolygon()turf.booleanIntersects()turf.lineIntersect()turf.distance()turf.bearing()turf.destination()Эти операции формируют основу вычислений видимости в плоской модели координат.
Одним из фундаментальных шагов является проверка того, находится ли точка внутри заданной области наблюдения. В задачах видимости это может быть:
Проверка расстояния:
import * as turf from "@turf/turf";
const observer = turf.point([30.5, 50.5]);
const target = turf.point([30.55, 50.52]);
const dist = turf.distance(observer, target, { units: "kilometers" });
const visible = dist <= 2;
const viewPolygon = turf.polygon([[
[30.4, 50.4],
[30.6, 50.4],
[30.6, 50.6],
[30.4, 50.6],
[30.4, 50.4]
]]);
const target = turf.point([30.55, 50.55]);
const inside = turf.booleanPointInPolygon(target, viewPolygon);
Функция booleanPointInPolygon реализует алгоритм ray
casting и позволяет определять принадлежность точки области
видимости.
Линия видимости (line of sight) моделируется как отрезок между наблюдателем и целевой точкой:
const observer = turf.point([30.5, 50.5]);
const target = turf.point([30.8, 50.7]);
const sightLine = turf.lineString([
observer.geometry.coordinates,
target.geometry.coordinates
]);
Далее выполняется проверка пересечений с препятствиями:
const building = turf.polygon([[
[30.6, 50.55],
[30.65, 50.55],
[30.65, 50.65],
[30.6, 50.65],
[30.6, 50.55]
]]);
const intersects = turf.booleanIntersects(sightLine, building);
Если intersects === true, прямая видимости нарушена.
При наличии множества объектов используется перебор FeatureCollection:
const obstacles = turf.featureCollection([
building1,
building2,
building3
]);
let blocked = false;
for (const feature of obstacles.features) {
if (turf.booleanIntersects(sightLine, feature)) {
blocked = true;
break;
}
}
Для больших наборов объектов применяется предварительная фильтрация по bounding box или расстоянию.
Для более точной модели видимости линия между точками дискретизируется на множество сегментов.
const line = turf.lineString([
observer.geometry.coordinates,
target.geometry.coordinates
]);
const segmentLength = 0.01; // км
const lineLength = turf.distance(observer, target);
const steps = Math.ceil(lineLength / segmentLength);
Далее создаются промежуточные точки:
const sampledPoints = [];
for (let i = 0; i <= steps; i++) {
const segment = i / steps;
const point = turf.along(line, lineLength * segment);
sampledPoints.push(point);
}
Каждая точка проверяется на попадание в препятствие:
let blocked = false;
for (const p of sampledPoints) {
for (const obstacle of obstacles.features) {
if (turf.booleanPointInPolygon(p, obstacle)) {
blocked = true;
break;
}
}
if (blocked) break;
}
Этот подход используется для приближённого моделирования непрерывной среды.
Для ограничения видимости по направлению используется азимут:
const observer = turf.point([30.5, 50.5]);
const target = turf.point([30.55, 50.55]);
const angle = turf.bearing(observer, target);
Далее вводится поле зрения:
const fovMin = 30;
const fovMax = 120;
const isInView = angle >= fovMin && angle <= fovMax;
Такой подход используется в моделях камер, радаров и игровых системах.
Сектор обзора формируется через генерацию дуги:
const center = turf.point([30.5, 50.5]);
const radius = 2;
const steps = 64;
const arcPoints = [];
for (let i = 0; i <= steps; i++) {
const angle = 30 + (90 * i) / steps;
const destination = turf.destination(center, radius, angle, { units: "kilometers" });
arcPoints.push(destination.geometry.coordinates);
}
arcPoints.push(center.geometry.coordinates);
const viewSector = turf.polygon([arcPoints]);
Полученный полигон используется как область видимости с направлением.
Функция lineIntersect позволяет определить точки
пересечения:
const intersections = turf.lineIntersect(sightLine, obstacles);
Результат содержит все точки пересечения линии с препятствиями.
Если хотя бы одна точка пересечения находится ближе к наблюдателю, чем цель, видимость считается заблокированной.
const observerToTarget = turf.distance(observer, target);
let blocked = false;
intersections.features.forEach(intersection => {
const dist = turf.distance(observer, intersection);
if (dist < observerToTarget) {
blocked = true;
}
});
При работе с большим количеством объектов применяются следующие методы:
const maxRange = 5;
const filtered = obstacles.features.filter(f =>
turf.distance(observer, f) <= maxRange
);
const bbox = turf.bbox(viewSector);
const bboxPolygon = turf.bboxPolygon(bbox);
const candidates = obstacles.features.filter(f =>
turf.booleanIntersects(f, bboxPolygon)
);
Для высоконагруженных систем используется R-tree или аналоги через сторонние библиотеки, где Turf.js применяется только для финальной геометрической проверки.
В задачах городской геометрии здания рассматриваются как вертикальные экструзии, но Turf.js работает в 2D, поэтому применяется упрощение:
const buildingHeight = 30;
const observerHeight = 2;
const heightBlocked = buildingHeight > observerHeight;
Комбинация с пересечением линии даёт финальную модель видимости:
Для вычисления карты видимости от одной точки используется перебор всех целей:
const visibleTargets = [];
for (const target of targets.features) {
const line = turf.lineString([
observer.geometry.coordinates,
target.geometry.coordinates
]);
let blocked = false;
for (const obs of obstacles.features) {
if (turf.booleanIntersects(line, obs)) {
blocked = true;
break;
}
}
if (!blocked) {
visibleTargets.push(target);
}
}
Полная проверка включает последовательность условий:
const dist = turf.distance(observer, target);
const angle = turf.bearing(observer, target);
const inRange = dist < maxRange;
const inAngle = angle >= fovMin && angle <= fovMax;
let blocked = false;
for (const obs of obstacles.features) {
if (turf.booleanIntersects(
turf.lineString([observer.geometry.coordinates, target.geometry.coordinates]),
obs
)) {
blocked = true;
break;
}
}
const visible = inRange && inAngle && !blocked;
Эта модель используется в системах GIS-аналитики, симуляциях и пространственном моделировании сцен.