use super::round_dp;
use super::scene::{Scene, SiteRole};
use serde_json::{json, Value};
fn availability(scene: &Scene) -> Option<String> {
let e = scene.epoch?;
let last = *scene.times_s.last()?;
Some(format!("{}/{}", e.iso(0.0), e.iso(last)))
}
pub fn write(scene: &Scene) -> String {
let mut packets: Vec<Value> = Vec::new();
let mut doc = json!({
"id": "document",
"name": scene.title(),
"version": "1.0",
"description": format!(
"Kshana {} export. Epoch: {}. Moving objects: GCRS (referenceFrame INERTIAL), metres, seconds after the epoch. Fixed sites: Earth-fixed WGS 84 (referenceFrame FIXED), metres.",
env!("CARGO_PKG_VERSION"),
scene.epoch_note
),
});
if let (Some(e), Some(iv)) = (scene.epoch, availability(scene)) {
doc["clock"] = json!({
"interval": iv,
"currentTime": e.iso(0.0),
"multiplier": 60,
"range": "LOOP_STOP",
"step": "SYSTEM_CLOCK_MULTIPLIER",
});
}
packets.push(doc);
if let (Some(e), Some(iv)) = (scene.epoch, availability(scene)) {
for m in &scene.movers {
let mut cart: Vec<Value> = Vec::with_capacity(4 * scene.times_s.len());
for (i, &t) in scene.times_s.iter().enumerate() {
let r = m.gcrs_r_m[i];
cart.push(json!(round_dp(t, 6)));
for c in r {
cart.push(json!(round_dp(c, 4)));
}
}
let (colour, size) = match m.role {
super::scene::MoverRole::Satellite => ([255, 196, 0, 255], 6),
super::scene::MoverRole::User => ([0, 200, 255, 255], 8),
};
packets.push(json!({
"id": format!("{}/{}", m.role.as_str(), m.id),
"name": m.id,
"description": format!("{} ({}). Position: GCRS, metres.", m.description, m.role.as_str()),
"availability": iv,
"position": {
"epoch": e.iso(0.0),
"referenceFrame": "INERTIAL",
"interpolationAlgorithm": "LAGRANGE",
"interpolationDegree": 5,
"cartesian": cart,
},
"point": {
"pixelSize": size,
"color": { "rgba": colour },
},
"path": {
"width": 1,
"leadTime": 0,
"material": { "solidColor": { "color": { "rgba": [colour[0], colour[1], colour[2], 128] } } },
},
}));
}
}
for s in &scene.sites {
let p = s.ecef_m();
let colour = match s.role {
SiteRole::Jammer => [220, 30, 30, 255],
SiteRole::Station => [120, 220, 120, 255],
_ => [0, 200, 255, 255],
};
packets.push(json!({
"id": format!("site/{}", s.id),
"name": s.id,
"description": format!(
"{} ({}). Latitude {} deg, longitude {} deg, height {} m above the WGS 84 ellipsoid. Position: Earth-fixed, metres.",
s.description, s.role.as_str(), s.lat_deg, s.lon_deg, s.h_m
),
"position": {
"referenceFrame": "FIXED",
"cartesian": [round_dp(p[0], 4), round_dp(p[1], 4), round_dp(p[2], 4)],
},
"point": { "pixelSize": 9, "color": { "rgba": colour } },
}));
}
for f in &scene.footprints {
let p = crate::frames::geodetic_to_ecef(crate::frames::Geodetic {
lat_rad: f.centre_lat_deg.to_radians(),
lon_rad: f.centre_lon_deg.to_radians(),
alt_m: 0.0,
});
packets.push(json!({
"id": f.id,
"name": f.id,
"description": format!("{}. Radius {} m along the ground.", f.description, f.radius_m),
"position": {
"referenceFrame": "FIXED",
"cartesian": [round_dp(p[0], 4), round_dp(p[1], 4), round_dp(p[2], 4)],
},
"ellipse": {
"semiMajorAxis": f.radius_m,
"semiMinorAxis": f.radius_m,
"height": 0,
"fill": true,
"material": { "solidColor": { "color": { "rgba": [220, 30, 30, 60] } } },
"outline": true,
"outlineColor": { "rgba": [220, 30, 30, 255] },
},
}));
}
for r in &scene.routes {
let mut deg: Vec<Value> = Vec::with_capacity(3 * r.points.len());
for p in &r.points {
deg.push(json!(p[1]));
deg.push(json!(p[0]));
deg.push(json!(0));
}
packets.push(json!({
"id": format!("route/{}", r.id),
"name": r.id,
"description": format!("{}. Untimed: WGS 84 longitude and latitude in degrees, clamped to the ground.", r.description),
"polyline": {
"positions": { "cartographicDegrees": deg },
"clampToGround": true,
"width": 2,
"material": { "solidColor": { "color": { "rgba": [0, 200, 255, 255] } } },
},
}));
}
let lines: Vec<String> = packets
.iter()
.map(|p| serde_json::to_string(p).unwrap_or_else(|_| "{}".into()))
.collect();
format!("[\n{}\n]\n", lines.join(",\n"))
}