Измерение площадей относится к числу наиболее востребованных геопространственных операций в трёхмерных приложениях. В среде CesiumJS подобный функционал используется для анализа земельных участков, оценки территорий строительства, мониторинга природных объектов, планирования инфраструктуры и выполнения различных задач геоинформационных систем.
В отличие от обычных двумерных карт, CesiumJS работает с трёхмерным глобусом, рельефом и пространственными координатами. По этой причине вычисление площади требует учёта особенностей геометрии поверхности Земли и способов представления объектов на глобусе.
Основная идея измерения площади заключается в следующем:
В приложениях на базе CesiumJS используются несколько методов расчёта площади.
Наиболее простой способ.
Координаты переводятся в локальную систему координат, после чего применяется классическая формула площади многоугольника.
Преимущества:
Недостатки:
Более точный метод.
Расчёт выполняется с учётом эллипсоида Земли, используемого в CesiumJS. Обычно применяется для крупных объектов и профессиональных ГИС-систем.
Преимущества:
Недостатки:
В некоторых задачах требуется учитывать фактическую поверхность местности.
Например, площадь склона горы всегда больше площади его проекции на карту.
Для подобных вычислений используется триангуляция участка с последующим суммированием площадей отдельных треугольников.
Базовая сцена создаётся стандартным образом:
const viewer = new Cesium.Viewer("cesiumContainer", {
terrain: Cesium.Terrain.fromWorldTerrain()
});
Подключение рельефа позволяет отображать измеряемую область непосредственно на поверхности Земли.
Для построения полигона потребуется массив координат.
const positions = [];
Каждый элемент массива представляет объект типа:
Cesium.Cartesian3
Для обработки кликов применяется ScreenSpaceEventHandler.
const handler = new Cesium.ScreenSpaceEventHandler(
viewer.scene.canvas
);
handler.setInputAction(function(click) {
const position =
viewer.scene.pickPosition(click.position);
if(position) {
positions.push(position);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
После каждого щелчка новая вершина добавляется в массив.
Для визуального контроля удобно показывать точки.
viewer.entities.add({
position: position,
point: {
pixelSize: 10,
color: Cesium.Color.YELLOW
}
});
Каждая вершина становится заметной на глобусе.
После выбора минимум трёх точек можно сформировать область.
viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(
positions
),
material: Cesium.Color.BLUE.withAlpha(0.4)
}
});
Параметр hierarchy определяет контур полигона.
Часто требуется отображать область ещё до завершения измерения.
Для этого используется CallbackProperty.
const polygonEntity = viewer.entities.add({
polygon: {
hierarchy: new Cesium.CallbackProperty(
function() {
return new Cesium.PolygonHierarchy(
positions
);
},
false
),
material: Cesium.Color.CYAN.withAlpha(0.5)
}
});
Полигон будет автоматически обновляться при изменении массива вершин.
Перед вычислением площади координаты необходимо перевести в географическую форму.
const cartographic =
Cesium.Cartographic.fromCartesian(position);
Получение долготы и широты:
const longitude =
Cesium.Math.toDegrees(cartographic.longitude);
const latitude =
Cesium.Math.toDegrees(cartographic.latitude);
const points = positions.map(position => {
const cartographic =
Cesium.Cartographic.fromCartesian(position);
return {
lon: Cesium.Math.toDegrees(
cartographic.longitude
),
lat: Cesium.Math.toDegrees(
cartographic.latitude
)
};
});
Результат:
[
{ lon: 37.6, lat: 55.7 },
{ lon: 37.7, lat: 55.8 },
{ lon: 37.8, lat: 55.75 }
]
Для локальных вычислений широко применяется формула Гаусса (Shoelace Formula).
Для многоугольника с вершинами:
[ (x_1,y_1), (x_2,y_2), …, (x_n,y_n)]
площадь определяется выражением:
S=|{i=1}^{n}(x_i y{i+1}-x_{i+1} y_i)|
Формула эффективно работает после перевода координат в локальную плоскую систему.
function polygonArea(points) {
let area = 0;
for(let i = 0; i < points.length; i++) {
const j =
(i + 1) % points.length;
area +=
points[i].x * points[j].y;
area -=
points[j].x * points[i].y;
}
return Math.abs(area / 2);
}
Алгоритм обладает линейной сложностью:
O(n)
где n — количество вершин.
Для получения корректных результатов географические координаты необходимо перевести в метры.
Один из способов — использовать первую точку как центр локальной системы.
const center = positions[0];
Далее создаётся матрица преобразования:
const transform =
Cesium.Transforms.eastNorthUpToFixedFrame(
center
);
Получение обратной матрицы:
const inverse =
Cesium.Matrix4.inverse(
transform,
new Cesium.Matrix4()
);
const localPoints = positions.map(position => {
const local =
Cesium.Matrix4.multiplyByPoint(
inverse,
position,
new Cesium.Cartesian3()
);
return {
x: local.x,
y: local.y
};
});
После этого координаты представлены в метрах относительно выбранного центра.
const area =
polygonArea(localPoints);
console.log(area);
Результат:
125430.87
Площадь выражена в квадратных метрах.
const km2 =
area / 1000000;
const hectares =
area / 10000;
const cm2 =
area * 10000;
function formatArea(area) {
if(area > 1000000) {
return (
area / 1000000
).toFixed(2) + " км²";
}
if(area > 10000) {
return (
area / 10000
).toFixed(2) + " га";
}
return area.toFixed(2) + " м²";
}
После завершения измерения результат можно вывести с помощью метки.
viewer.entities.add({
position: positions[0],
label: {
text: formatArea(area),
font: "18px sans-serif",
fillColor: Cesium.Color.WHITE,
showBackground: true
}
});
Размещение подписи в первой вершине не всегда удобно.
Для вычисления центра:
function getCenter(points) {
let x = 0;
let y = 0;
let z = 0;
points.forEach(point => {
x += point.x;
y += point.y;
z += point.z;
});
return new Cesium.Cartesian3(
x / points.length,
y / points.length,
z / points.length
);
}
const center =
getCenter(positions);
viewer.entities.add({
position: center,
label: {
text: formatArea(area)
}
});
Такой вариант обеспечивает более наглядное отображение результата.
Обычно процесс выбора точек завершается отдельным событием.
handler.setInputAction(function() {
calculateArea();
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
После правого клика начинается вычисление площади.
Для повторных измерений полезно предусмотреть сброс.
function clearMeasurement() {
viewer.entities.removeAll();
positions.length = 0;
}
CesiumJS поддерживает полигоны со внутренними контурами.
Пример структуры:
const hierarchy =
new Cesium.PolygonHierarchy(
outerRing,
[
new Cesium.PolygonHierarchy(
holeRing
)
]
);
Общая площадь вычисляется как:
Площадь внешнего контура
–
Сумма площадей отверстий
Подобный подход используется для озёр внутри земельных участков, внутренних дворов и других сложных геометрий.
Стандартное вычисление площади обычно выполняется по проекции полигона.
Для получения площади по поверхности рельефа требуется:
Такой подход значительно увеличивает точность при работе с горными районами.
При реализации инструмента измерения площади рекомендуется:
CallbackProperty только для динамических
объектов;Cartesian3;Для больших полигонов это позволяет заметно снизить нагрузку на браузер.
Неверный вариант:
area += longitude * latitude;
Долгота и широта выражены в градусах и не подходят для непосредственного вычисления площади.
Если алгоритм не соединяет последнюю точку с первой, результат будет ошибочным.
Правильный подход:
const j =
(i + 1) % points.length;
Полигон должен содержать минимум три точки.
if(positions.length < 3) {
return;
}
Для небольшого участка достаточно локальной проекции.
Для областей площадью в сотни квадратных километров предпочтительнее использовать геодезические методы вычисления.
Крупные приложения обычно разделяют функциональность на несколько компонентов:
Менеджер взаимодействия
Менеджер визуализации
Модуль вычислений
Модуль хранения
Подобная архитектура облегчает поддержку проекта и позволяет расширять функциональность без изменения основных алгоритмов измерения площадей.