Профили высот

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

В экосистеме CesiumJS профиль высот не является встроенным компонентом интерфейса, однако платформа предоставляет все необходимые инструменты для его построения: получение высот рельефа, выборку точек вдоль линии, интерполяцию маршрута и визуализацию результатов в сторонних графических библиотеках.


Назначение профилей высот

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

Типичный профиль содержит:

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

Например, маршрут длиной 10 км может проходить через:

  • равнину;
  • холмистую местность;
  • овраг;
  • горный участок.

На профиле все эти особенности будут представлены в виде кривой изменения высот.


Основные этапы построения профиля

Процесс формирования профиля состоит из нескольких шагов:

  1. Создание линии маршрута.
  2. Генерация промежуточных точек.
  3. Получение высот рельефа.
  4. Вычисление расстояний.
  5. Формирование массива данных.
  6. Отображение графика.

Схема выглядит следующим образом:

Маршрут
   ↓
Промежуточные точки
   ↓
Высоты Terrain
   ↓
Расстояния
   ↓
Массив данных
   ↓
График профиля

Создание маршрута

Маршрут обычно задаётся набором координат.

const route = [
    Cesium.Cartesian3.fromDegrees(30.5, 50.45),
    Cesium.Cartesian3.fromDegrees(30.7, 50.50)
];

Для визуализации линии:

viewer.entities.add({
    polyline: {
        positions: route,
        width: 3,
        material: Cesium.Color.RED
    }
});

Генерация промежуточных точек

Высоты необходимо получать не только в конечных точках, но и вдоль всей линии.

Для этого используется линейная интерполяция.

Сначала преобразуем координаты:

const start = Cesium.Cartographic.fromCartesian(route[0]);
const end = Cesium.Cartographic.fromCartesian(route[1]);

Создадим точки выборки:

const samples = [];

const count = 100;

for (let i = 0; i <= count; i++) {

    const fraction = i / count;

    const longitude = Cesium.Math.lerp(
        start.longitude,
        end.longitude,
        fraction
    );

    const latitude = Cesium.Math.lerp(
        start.latitude,
        end.latitude,
        fraction
    );

    samples.push(
        new Cesium.Cartographic(
            longitude,
            latitude
        )
    );
}

Теперь массив содержит 101 точку вдоль маршрута.


Получение высот рельефа

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

Для этого применяется функция:

Cesium.sampleTerrainMostDetailed()

Пример:

const terrainSamples =
    await Cesium.sampleTerrainMostDetailed(
        viewer.terrainProvider,
        samples
    );

После завершения запроса каждая точка будет содержать поле:

height

Например:

console.log(terrainSamples[0].height);

Результат:

178.4

Высота задаётся в метрах относительно эллипсоида или геоида в зависимости от источника данных.


Формирование данных профиля

Создадим структуру:

const profileData = [];

Расстояние между соседними точками вычисляется постепенно.

let distance = 0;

Добавление первой точки:

profileData.push({
    distance: 0,
    height: terrainSamples[0].height
});

Далее вычисляем накопленное расстояние.

for (let i = 1; i < terrainSamples.length; i++) {

    const p1 =
        Cesium.Cartesian3.fromRadians(
            terrainSamples[i - 1].longitude,
            terrainSamples[i - 1].latitude,
            terrainSamples[i - 1].height
        );

    const p2 =
        Cesium.Cartesian3.fromRadians(
            terrainSamples[i].longitude,
            terrainSamples[i].latitude,
            terrainSamples[i].height
        );

    distance +=
        Cesium.Cartesian3.distance(p1, p2);

    profileData.push({
        distance,
        height: terrainSamples[i].height
    });
}

Получаем структуру:

[
    {
        distance: 0,
        height: 176
    },
    {
        distance: 105,
        height: 180
    },
    {
        distance: 210,
        height: 184
    }
]

Построение графика

CesiumJS не содержит встроенных средств построения графиков, поэтому обычно используются сторонние библиотеки:

  • Chart.js;
  • D3.js;
  • Highcharts;
  • Plotly;
  • ECharts.

Пример подготовки данных для Chart.js:

const labels =
    profileData.map(
        p => p.distance.toFixed(0)
    );

const heights =
    profileData.map(
        p => p.height
    );

Создание графика:

new Chart(ctx, {
    type: "line",

    data: {
        labels,

        datasets: [{
            label: "Высота",

            data: heights
        }]
    }
});

Результатом станет классический профиль рельефа.


Профиль по полилинии сложной формы

Маршрут редко состоит из двух точек.

Чаще используется полилиния с множеством вершин.

Например:

const route = [
    [30.1, 50.1],
    [30.3, 50.2],
    [30.5, 50.35],
    [30.7, 50.4]
];

В этом случае профиль строится по каждому сегменту отдельно.

Алгоритм:

  1. Выбрать сегмент.
  2. Сгенерировать промежуточные точки.
  3. Получить высоты.
  4. Перейти к следующему сегменту.
  5. Объединить результаты.

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


Адаптивная плотность выборки

Фиксированное количество точек подходит не всегда.

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

Лучше определять число выборок автоматически.

Например:

const length =
    Cesium.Cartesian3.distance(
        startCartesian,
        endCartesian
    );

const count =
    Math.max(
        20,
        Math.floor(length / 50)
    );

Здесь одна точка создаётся примерно каждые 50 метров.


Отображение точки профиля на карте

Часто требуется связь между графиком и сценой.

При наведении на график:

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

Создание маркера:

const marker =
    viewer.entities.add({
        position: routePoint,

        point: {
            pixelSize: 12,
            color: Cesium.Color.YELLOW
        }
    });

Пользователь получает возможность видеть местоположение участка рельефа непосредственно на глобусе.


Обратная синхронизация

Полезной является и обратная схема взаимодействия.

При выборе точки на карте:

handler.setInputAction(
    function(click) {

        const picked =
            viewer.scene.pick(
                click.position
            );

    },
    Cesium.ScreenSpaceEventType.LEFT_CLICK
);

Можно:

  • определить ближайшую точку профиля;
  • подсветить её на графике;
  • показать высоту;
  • вывести расстояние от начала маршрута.

Такой механизм широко применяется в профессиональных геоинформационных системах.


Определение максимальной и минимальной высоты

После формирования профиля легко получить статистику.

Минимальная высота:

const minHeight =
    Math.min(
        ...profileData.map(
            p => p.height
        )
    );

Максимальная:

const maxHeight =
    Math.max(
        ...profileData.map(
            p => p.height
        )
    );

Средняя:

const averageHeight =
    profileData.reduce(
        (sum, point) =>
            sum + point.height,
        0
    ) / profileData.length;

Расчёт набора высоты

Набор высоты показывает суммарный подъём по маршруту.

Алгоритм:

let ascent = 0;

for (let i = 1; i < profileData.length; i++) {

    const delta =
        profileData[i].height -
        profileData[i - 1].height;

    if (delta > 0) {
        ascent += delta;
    }
}

Полученный результат выражается в метрах.


Расчёт сброса высоты

Аналогичным образом вычисляется общий спуск.

let descent = 0;

for (let i = 1; i < profileData.length; i++) {

    const delta =
        profileData[i].height -
        profileData[i - 1].height;

    if (delta < 0) {
        descent += Math.abs(delta);
    }
}

Вычисление уклона

Уклон определяется отношением перепада высот к расстоянию.

Формула:

i=%

Пример:

const slope =
    (heightDiff / distance) * 100;

Если подъём составил:

10 м

на участке:

200 м

то уклон равен:

5 %

Анализ крутых участков

Для поиска опасных зон профиль можно анализировать автоматически.

Например:

const dangerousSections = [];

for (let i = 1; i < profileData.length; i++) {

    const dh =
        profileData[i].height -
        profileData[i - 1].height;

    const dd =
        profileData[i].distance -
        profileData[i - 1].distance;

    const slope =
        Math.abs(dh / dd);

    if (slope > 0.15) {

        dangerousSections.push(i);
    }
}

Подобные алгоритмы применяются при проектировании:

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

Профили зданий и объектов

Источником высоты может выступать не только Terrain.

Для 3D Tiles можно получать координаты непосредственно из объектов сцены.

Например:

const position =
    viewer.scene.pickPosition(
        screenPosition
    );

После выборки множества точек возможно построение профиля:

  • крыши здания;
  • моста;
  • эстакады;
  • инженерного сооружения.

Профиль над поверхностью

Иногда требуется анализ не рельефа, а траектории движения.

Пример:

entity.position

Для каждой временной отметки можно получать:

const position =
    entity.position.getValue(
        currentTime
    );

После чего формируется профиль:

Время → Высота

Такой подход используется для:

  • авиации;
  • космических аппаратов;
  • беспилотников;
  • морских исследований.

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

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

Для повышения производительности применяются следующие методы:

Кэширование результатов

const cache = new Map();

Высоты для уже обработанных координат не запрашиваются повторно.

Ограничение детализации

const count = 300;

В большинстве случаев 300–500 точек достаточно для качественного профиля.

Асинхронная обработка

await Cesium.sampleTerrainMostDetailed(
    terrainProvider,
    positions
);

Запросы выполняются без блокировки интерфейса.

Пакетная выборка

Лучше отправлять один большой запрос:

sampleTerrainMostDetailed(
    provider,
    positions
);

чем множество мелких запросов в цикле.


Типичные проблемы

Отсутствие Terrain

Если используется только эллипсоид Земли:

viewer.terrainProvider

может возвращать нулевые значения высот.

Результатом станет плоский профиль.


Недостаточная плотность выборки

Малое количество точек приводит к потере деталей рельефа.

Особенно заметно это в горных районах.


Шумы данных

Высокодетализированные модели рельефа могут содержать локальные скачки высот.

Для сглаживания применяются:

  • скользящее среднее;
  • медианный фильтр;
  • сплайн-интерполяция.

Ошибки расстояний

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

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


Архитектура системы профилей высот

Полноценный модуль профилей обычно включает следующие компоненты:

Map Module
    ↓
Route Builder
    ↓
Sampling Engine
    ↓
Terrain Query
    ↓
Profile Processor
    ↓
Chart Renderer

Такое разделение позволяет независимо развивать:

  • построение маршрутов;
  • получение данных рельефа;
  • расчёт характеристик;
  • визуализацию профиля;
  • взаимодействие карты и графика.

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