Измерения площадей

Измерение площадей относится к числу наиболее востребованных геопространственных операций в трёхмерных приложениях. В среде CesiumJS подобный функционал используется для анализа земельных участков, оценки территорий строительства, мониторинга природных объектов, планирования инфраструктуры и выполнения различных задач геоинформационных систем.

В отличие от обычных двумерных карт, CesiumJS работает с трёхмерным глобусом, рельефом и пространственными координатами. По этой причине вычисление площади требует учёта особенностей геометрии поверхности Земли и способов представления объектов на глобусе.

Основная идея измерения площади заключается в следующем:

  1. Пользователь последовательно выбирает вершины полигона.
  2. Из выбранных точек формируется замкнутая область.
  3. Выполняется вычисление площади фигуры.
  4. Результат отображается на сцене.

Основные подходы к вычислению площади

В приложениях на базе CesiumJS используются несколько методов расчёта площади.

Площадь на плоскости

Наиболее простой способ.

Координаты переводятся в локальную систему координат, после чего применяется классическая формула площади многоугольника.

Преимущества:

  • высокая скорость;
  • простота реализации;
  • достаточная точность для небольших участков.

Недостатки:

  • ошибки увеличиваются с ростом размеров полигона;
  • не учитывается кривизна Земли.

Геодезическая площадь

Более точный метод.

Расчёт выполняется с учётом эллипсоида Земли, используемого в CesiumJS. Обычно применяется для крупных объектов и профессиональных ГИС-систем.

Преимущества:

  • высокая точность;
  • корректная работа на больших территориях.

Недостатки:

  • более сложная реализация;
  • повышенная вычислительная нагрузка.

Площадь по рельефу

В некоторых задачах требуется учитывать фактическую поверхность местности.

Например, площадь склона горы всегда больше площади его проекции на карту.

Для подобных вычислений используется триангуляция участка с последующим суммированием площадей отдельных треугольников.


Создание инструмента измерения площади

Инициализация Viewer

Базовая сцена создаётся стандартным образом:

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
            )
        ]
    );

Общая площадь вычисляется как:

Площадь внешнего контура
–
Сумма площадей отверстий

Подобный подход используется для озёр внутри земельных участков, внутренних дворов и других сложных геометрий.


Учёт рельефа

Стандартное вычисление площади обычно выполняется по проекции полигона.

Для получения площади по поверхности рельефа требуется:

  1. Получить высоты большого числа промежуточных точек.
  2. Разбить полигон на треугольники.
  3. Рассчитать площадь каждого треугольника в пространстве.
  4. Суммировать результаты.

Такой подход значительно увеличивает точность при работе с горными районами.


Оптимизация производительности

При реализации инструмента измерения площади рекомендуется:

  • использовать CallbackProperty только для динамических объектов;
  • ограничивать количество временных сущностей;
  • удалять обработчики событий после завершения измерения;
  • переиспользовать объекты Cartesian3;
  • избегать постоянного пересоздания массивов в циклах.

Для больших полигонов это позволяет заметно снизить нагрузку на браузер.


Типичные ошибки

Использование географических координат напрямую

Неверный вариант:

area += longitude * latitude;

Долгота и широта выражены в градусах и не подходят для непосредственного вычисления площади.


Отсутствие замыкания контура

Если алгоритм не соединяет последнюю точку с первой, результат будет ошибочным.

Правильный подход:

const j =
    (i + 1) % points.length;

Недостаточное количество вершин

Полигон должен содержать минимум три точки.

if(positions.length < 3) {
    return;
}

Игнорирование размеров объекта

Для небольшого участка достаточно локальной проекции.

Для областей площадью в сотни квадратных километров предпочтительнее использовать геодезические методы вычисления.


Архитектура полноценного инструмента измерения площади

Крупные приложения обычно разделяют функциональность на несколько компонентов:

Менеджер взаимодействия

  • обработка кликов;
  • завершение измерения;
  • отмена операции.

Менеджер визуализации

  • точки;
  • линии;
  • полигон;
  • подписи.

Модуль вычислений

  • преобразование координат;
  • расчёт площади;
  • преобразование единиц измерения.

Модуль хранения

  • история измерений;
  • экспорт результатов;
  • сохранение пользовательских данных.

Подобная архитектура облегчает поддержку проекта и позволяет расширять функциональность без изменения основных алгоритмов измерения площадей.