Getting started¶
Install¶
gmat-czml requires Python 3.10–3.12. It pulls in czml3 for CZML serialization and orbit-formats for the canonical schema, the frame rotation, and the file readers.
The one call¶
Everything goes through to_czml. Hand it a trajectory and it returns a
CzmlDocument:
trajectory is whatever carries the canonical state series: a pandas DataFrame, an
orbit-formats Ephemeris, a file orbit-formats can read (an OEM, a GMAT report, an SP3, an STK
ephemeris, …), or an iterable of any of these for a multi-object scene.
From a GMAT (or any) ephemeris file¶
Reading is delegated to orbit-formats, so any format it understands works without extra code:
from orbit_formats import read
from gmat_czml import to_czml
trajectory = read("mission.oem")
to_czml(trajectory, ground_track=True).save("mission.czml")
From a DataFrame you build yourself¶
Any producer that yields the canonical columns and metadata feeds the same call — see the schema reference for the full contract:
import pandas as pd
from gmat_czml import to_czml
frame = pd.DataFrame({"Epoch": epochs, "X": x, "Y": y, "Z": z, "VX": vx, "VY": vy, "VZ": vz})
frame.attrs.update(
{
"object_name": "MySat",
"central_body": "Earth",
"coordinate_system": "EME2000",
"time_scale": "UTC",
}
)
to_czml(frame).save("mysat.czml")
Options¶
to_czml(
trajectory,
ground_track=True, # also emit the sub-satellite ground track (Earth-only)
playback_seconds=60.0, # the whole span plays back in ~this many wall-clock seconds
)
ground_trackis off by default. Turning it on is the only path that loads the Earth-orientation rotation (and astropy, transitively), so the core orbit-path conversion stays light unless you ask for a ground track. See Ground track.playback_secondssets the document clock's default speed: the trajectory plays back in roughly that many seconds of real time (floored at 1×).
A malformed input raises a typed error naming exactly what is wrong — a missing
column, an unrecognised frame, an absent time scale — rather than a bare KeyError.
Getting the output¶
A CzmlDocument is in-memory first; nothing touches disk unless you ask:
czml = to_czml(trajectory)
czml.save("orbit.czml") # write a .czml file (returns the Path)
text = czml.to_json() # the document as a CZML JSON string
packets = czml.to_dict() # the document as a list of packet dicts
save writes exactly what to_json returns, byte-for-byte.
Viewing the result¶
CZML is rendered by a Cesium client, not by gmat-czml. The quickest ways to see a document:
- Cesium Sandcastle — paste a
Cesium.CzmlDataSource.load()snippet, or drag your.czmlonto the page. Nothing to install. - The bundled viewer — run
python examples/serve.py(CesiumJS needs Web Workers, which browsers block on afile://page, so it must be served over http) and drag a.czmlonto the page. Its Base imagery dropdown picks the globe underlay — offline (the default), OpenStreetMap, Cesium ion, or Mapbox — see Imagery underlay below. - Your own app — load the document with
Cesium.CzmlDataSource.load()in CesiumJS, Cesium ion, or Resium (React). See the client matrix.
See the Gallery for complete, runnable examples.
Imagery underlay (OSM / Mapbox)¶
CZML carries no base imagery — the underlay is a property of the Cesium client, not the document — so
the same .czml renders over whatever globe imagery the viewer is configured for. The bundled viewer
offers four base layers in its Base imagery dropdown:
| Option | Imagery | Token |
|---|---|---|
| Offline (bundled) | Natural Earth II shipped with CesiumJS | none (no network) |
| OpenStreetMap | OSM street tiles | none |
| Cesium ion world imagery | ion's high-resolution imagery | a Cesium ion token |
| Mapbox | Mapbox satellite-streets |
a Mapbox access token |
Paste a token for ion or Mapbox (stored in your browser only); a token-requiring option falls back to
the offline imagery when no token is supplied, so the globe is never blank. A ?imagery=osm (or
ion / mapbox / offline) query parameter selects the underlay for a shareable link.
In your own app¶
The underlay is a few lines of CesiumJS. For an OpenStreetMap base layer (no token):
const viewer = new Cesium.Viewer("cesiumContainer", {
baseLayer: Cesium.ImageryLayer.fromProviderAsync(
Promise.resolve(
new Cesium.UrlTemplateImageryProvider({
url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
credit: "© OpenStreetMap contributors",
maximumLevel: 19,
})
)
),
});
const dataSource = await Cesium.CzmlDataSource.load("orbit.czml");
viewer.dataSources.add(dataSource);
For Mapbox, swap the base layer for a MapboxStyleImageryProvider carrying your access token:
baseLayer: Cesium.ImageryLayer.fromProviderAsync(
Promise.resolve(
new Cesium.MapboxStyleImageryProvider({ styleId: "satellite-streets-v12", accessToken: MAPBOX_TOKEN })
)
),
The public OSM tile server is fine for a quick look but is governed by the OSM tile usage policy — use a dedicated tile host (or Mapbox / ion) for anything beyond casual use.
Clamping the ground track¶
The ground track floats at the satellite's own geodetic height (see
Ground track), which lines up with the underlay seen from straight
overhead but rides above it in a tilted view. To make the track hug the imagery, drape its polylines
onto the globe with clampToGround — the bundled viewer's Clamp ground track to surface checkbox
does exactly this: