GPX треки

GPX (GPS Exchange Format) представляет собой XML-структуру, предназначенную для хранения треков, маршрутов и точек интереса, полученных с GPS-устройств. В контексте трёхмерной визуализации в CesiumJS GPX чаще всего используется для отображения траекторий движения объектов во времени и пространстве.

Структура GPX обычно включает:

  • trk (track) — набор сегментов трека
  • trkseg (track segment) — последовательность точек
  • trkpt (track point) — отдельная точка с координатами и временем
  • атрибуты lat, lon, а также ele и time

CesiumJS не всегда предоставляет единый встроенный универсальный загрузчик GPX во всех версиях, поэтому работа с форматом строится либо через специализированные классы, либо через преобразование в поддерживаемые структуры: CZML, GeoJSON или набор сущностей (Entity).


Подготовка GPX к использованию в CesiumJS

Перед визуализацией данные GPX приводятся к структуре, удобной для движка. Основные подходы:

1. Использование Cesium.GpxDataSource (если доступен в сборке)

В некоторых сборках Cesium присутствует GpxDataSource, который позволяет загружать GPX напрямую:

const viewer = new Cesium.Viewer("cesiumContainer");

const gpxDataSource = new Cesium.GpxDataSource();

viewer.dataSources.add(
  Cesium.GpxDataSource.load("track.gpx", {
    clampToGround: true
  })
);

Такой способ автоматически:

  • парсит XML
  • создаёт набор Entity
  • строит линию трека
  • добавляет точки

2. Преобразование GPX в GeoJSON

Если встроенная поддержка отсутствует, GPX конвертируется в GeoJSON:

function gpxToGeoJSON(xmlText) {
  const parser = new DOMParser();
  const xml = parser.parseFromString(xmlText, "application/xml");

  const points = xml.getElementsByTagName("trkpt");

  const coordinates = [];
  for (let i = 0; i < points.length; i++) {
    const lat = parseFloat(points[i].getAttribute("lat"));
    const lon = parseFloat(points[i].getAttribute("lon"));
    const ele = points[i].getElementsByTagName("ele")[0]?.textContent;

    coordinates.push([lon, lat, parseFloat(ele || 0)]);
  }

  return {
    type: "Feature",
    geometry: {
      type: "LineString",
      coordinates
    },
    properties: {}
  };
}

Далее GeoJSON загружается стандартным способом:

Cesium.GeoJsonDataSource.load(geojson).then(ds => {
  viewer.dataSources.add(ds);
  viewer.zoomTo(ds);
});

3. Преобразование GPX в CZML для временной анимации

Для треков с временными метками CZML является более точным вариантом, поскольку поддерживает интерполяцию движения.

function gpxToCzml(xmlText) {
  const parser = new DOMParser();
  const xml = parser.parseFromString(xmlText, "application/xml");

  const points = xml.getElementsByTagName("trkpt");

  const position = [];

  for (let i = 0; i < points.length; i++) {
    const lat = parseFloat(points[i].getAttribute("lat"));
    const lon = parseFloat(points[i].getAttribute("lon"));
    const ele = parseFloat(points[i].getElementsByTagName("ele")[0]?.textContent || 0);
    const time = points[i].getElementsByTagName("time")[0]?.textContent;

    position.push(time);
    position.push(lon);
    position.push(lat);
    position.push(ele);
  }

  return [
    {
      id: "track",
      availability: `${position[0]}/${position[position.length - 4]}`,
      position: {
        epoch: position[0],
        cartographicDegrees: position
      },
      path: {
        material: {
          solidColor: {
            color: {
              rgba: [0, 180, 255, 255]
            }
          }
        },
        width: 3,
        leadTime: 0,
        trailTime: 3600
      }
    }
  ];
}

Отображение трека через Entity API

CesiumJS позволяет визуализировать GPX как линию через Entity:

const positions = Cesium.Cartesian3.fromDegreesArray([
  71.4304, 51.1281,
  71.4320, 51.1295,
  71.4350, 51.1310
]);

viewer.entities.add({
  polyline: {
    positions: positions,
    width: 4,
    material: Cesium.Color.ORANGE
  }
});

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


Работа с высотой и Terrain

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

Привязка к поверхности

polyline: {
  positions: positions,
  clampToGround: true
}

Использование terrain elevation

При включённом terrain модель трека может визуально корректироваться под рельеф:

viewer.terrainProvider = Cesium.createWorldTerrain();

Анимация движения по GPX-треку

Для имитации движения объекта используется SampledPositionProperty.

const property = new Cesium.SampledPositionProperty();

property.addSample(
  Cesium.JulianDate.fromIso8601("2024-01-01T10:00:00Z"),
  Cesium.Cartesian3.fromDegrees(71.4304, 51.1281, 100)
);

property.addSample(
  Cesium.JulianDate.fromIso8601("2024-01-01T10:05:00Z"),
  Cesium.Cartesian3.fromDegrees(71.4320, 51.1295, 110)
);

viewer.entities.add({
  position: property,
  billboard: {
    image: "vehicle.png",
    scale: 0.8
  },
  path: {
    width: 2,
    material: Cesium.Color.CYAN
  }
});

Для синхронизации используется viewer.clock:

viewer.clock.startTime = Cesium.JulianDate.fromIso8601("2024-01-01T10:00:00Z");
viewer.clock.currentTime = viewer.clock.startTime;
viewer.clock.stopTime = Cesium.JulianDate.fromIso8601("2024-01-01T11:00:00Z");
viewer.clock.multiplier = 10;
viewer.clock.shouldAnimate = true;

Сглаживание и интерполяция трека

GPS-данные часто содержат шум. CesiumJS поддерживает интерполяцию:

property.setInterpolationOptions({
  interpolationDegree: 2,
  interpolationAlgorithm: Cesium.LagrangePolynomialApproximation
});

Также применяется фильтрация точек до загрузки:

  • удаление дубликатов координат
  • ограничение минимального расстояния между точками
  • усреднение высоты

Визуализация направления движения

Для отображения ориентации используется вычисление курса:

function computeHeading(start, end) {
  const carto1 = Cesium.Cartographic.fromCartesian(start);
  const carto2 = Cesium.Cartographic.fromCartesian(end);

  const y = Math.sin(carto2.longitude - carto1.longitude) * Math.cos(carto2.latitude);
  const x = Math.cos(carto1.latitude) * Math.sin(carto2.latitude) -
            Math.sin(carto1.latitude) * Math.cos(carto2.latitude) *
            Math.cos(carto2.longitude - carto1.longitude);

  return Math.atan2(y, x);
}

Далее значение используется для поворота модели:

orientation: new Cesium.VelocityOrientationProperty(property)

Отрисовка больших GPX-треков

При работе с треками, содержащими десятки тысяч точек, ключевыми становятся оптимизации:

Упрощение геометрии

  • алгоритм Дугласа-Пекера
  • удаление избыточных точек по углу поворота

Батчинг координат

const positions = new Float64Array(largeArray);

Использование Primitive вместо Entity

const geometry = new Cesium.PolylineGeometry({
  positions: Cesium.Cartesian3.fromDegreesArray(coords),
  width: 3
});

Ошибки и особенности обработки GPX

  • отсутствующие time делают невозможной анимацию
  • несоответствие системы координат (GPX всегда WGS84)
  • разрывы сегментов trkseg
  • некорректные высоты (0 или NaN)
  • большие файлы могут вызывать лаги при парсинге XML

Структура полного конвейера обработки GPX

  1. Загрузка XML-файла
  2. Парсинг DOM
  3. Извлечение trkpt
  4. Очистка данных
  5. Преобразование в Cesium-структуру (Entity / CZML / GeoJSON)
  6. Привязка к Viewer
  7. Настройка времени (если требуется)
  8. Включение визуализации и интерполяции

Использование Path для визуального следа

path: {
  material: new Cesium.PolylineGlowMaterialProperty({
    glowPower: 0.2,
    color: Cesium.Color.YELLOW
  }),
  width: 5
}

Path создаёт динамическую линию, которая растёт по мере движения объекта, что особенно эффективно при анимации GPX-трека во времени.