use super::fmt_dp;
use super::scene::Scene;
pub fn xml_escape(s: &str) -> String {
let mut o = String::with_capacity(s.len());
for c in s.chars() {
match c {
'&' => o.push_str("&"),
'<' => o.push_str("<"),
'>' => o.push_str(">"),
'"' => o.push_str("""),
'\'' => o.push_str("'"),
_ => o.push(c),
}
}
o
}
fn coord(lon: f64, lat: f64, h: f64, sep: char) -> String {
format!(
"{}{sep}{}{sep}{}",
fmt_dp(lon, 9),
fmt_dp(lat, 9),
fmt_dp(h, 3)
)
}
fn style(id: &str, abgr: &str, line_abgr: &str) -> String {
format!(
" <Style id=\"{id}\">\n <IconStyle><color>{abgr}</color><scale>0.8</scale></IconStyle>\n <LineStyle><color>{line_abgr}</color><width>2</width></LineStyle>\n <PolyStyle><color>{line_abgr}</color></PolyStyle>\n </Style>\n"
)
}
pub fn write(scene: &Scene) -> String {
let mut o = String::new();
o.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
o.push_str("<kml xmlns=\"http://www.opengis.net/kml/2.2\" xmlns:gx=\"http://www.google.com/kml/ext/2.2\">\n");
o.push_str(" <Document>\n");
o.push_str(&format!(
" <name>{}</name>\n",
xml_escape(&scene.title())
));
o.push_str(&format!(
" <description>{}</description>\n",
xml_escape(&format!(
"Kshana {} export. Epoch: {}. Coordinates: WGS 84 longitude, latitude (deg) and height above the WGS 84 ellipsoid (m); altitudeMode absolute reads the height as above mean sea level, and the geoid undulation is not applied.",
env!("CARGO_PKG_VERSION"),
scene.epoch_note
))
));
o.push_str(&style("satellite", "ff00c4ff", "8000c4ff"));
o.push_str(&style("user", "ffffc800", "80ffc800"));
o.push_str(&style("site", "ffffc800", "ffffc800"));
o.push_str(&style("jammer", "ff1e1edc", "501e1edc"));
o.push_str(&style("route", "ffffc800", "ffffc800"));
if let Some(e) = scene.epoch {
if !scene.movers.is_empty() {
o.push_str(" <Folder>\n <name>Moving objects</name>\n");
for m in &scene.movers {
o.push_str(" <Placemark>\n");
o.push_str(&format!(" <name>{}</name>\n", xml_escape(&m.id)));
o.push_str(&format!(
" <description>{}</description>\n",
xml_escape(&format!("{} ({})", m.description, m.role.as_str()))
));
o.push_str(&format!(
" <styleUrl>#{}</styleUrl>\n",
m.role.as_str()
));
o.push_str(" <gx:Track>\n <altitudeMode>absolute</altitudeMode>\n");
for &t in &scene.times_s {
o.push_str(&format!(" <when>{}</when>\n", e.iso(t)));
}
for i in 0..scene.times_s.len() {
let (lat, lon, h) = m.geodetic(i);
o.push_str(&format!(
" <gx:coord>{}</gx:coord>\n",
coord(lon, lat, h, ' ')
));
}
o.push_str(" </gx:Track>\n </Placemark>\n");
}
o.push_str(" </Folder>\n");
}
}
if !scene.sites.is_empty() {
o.push_str(" <Folder>\n <name>Sites</name>\n");
for s in &scene.sites {
let st = if s.role == super::scene::SiteRole::Jammer {
"jammer"
} else {
"site"
};
o.push_str(" <Placemark>\n");
o.push_str(&format!(" <name>{}</name>\n", xml_escape(&s.id)));
o.push_str(&format!(
" <description>{}</description>\n",
xml_escape(&format!("{} ({})", s.description, s.role.as_str()))
));
o.push_str(&format!(" <styleUrl>#{st}</styleUrl>\n"));
o.push_str(&format!(
" <Point>\n <altitudeMode>absolute</altitudeMode>\n <coordinates>{}</coordinates>\n </Point>\n",
coord(s.lon_deg, s.lat_deg, s.h_m, ',')
));
o.push_str(" </Placemark>\n");
}
o.push_str(" </Folder>\n");
}
if !scene.footprints.is_empty() {
o.push_str(" <Folder>\n <name>Jammer footprints</name>\n");
for f in &scene.footprints {
o.push_str(" <Placemark>\n");
o.push_str(&format!(" <name>{}</name>\n", xml_escape(&f.id)));
o.push_str(&format!(
" <description>{}</description>\n",
xml_escape(&format!(
"{}. Radius {} m along the ground.",
f.description, f.radius_m
))
));
o.push_str(" <styleUrl>#jammer</styleUrl>\n");
o.push_str(" <Polygon>\n <altitudeMode>clampToGround</altitudeMode>\n <outerBoundaryIs>\n <LinearRing>\n <coordinates>");
let pts: Vec<String> = f.ring.iter().map(|p| coord(p[1], p[0], 0.0, ',')).collect();
o.push_str(&pts.join(" "));
o.push_str("</coordinates>\n </LinearRing>\n </outerBoundaryIs>\n </Polygon>\n </Placemark>\n");
}
o.push_str(" </Folder>\n");
}
if !scene.routes.is_empty() {
o.push_str(" <Folder>\n <name>Tracks</name>\n");
for r in &scene.routes {
o.push_str(" <Placemark>\n");
o.push_str(&format!(" <name>{}</name>\n", xml_escape(&r.id)));
o.push_str(&format!(
" <description>{}</description>\n",
xml_escape(&format!("{} (untimed)", r.description))
));
o.push_str(" <styleUrl>#route</styleUrl>\n");
o.push_str(" <LineString>\n <altitudeMode>clampToGround</altitudeMode>\n <coordinates>");
let pts: Vec<String> = r
.points
.iter()
.map(|p| coord(p[1], p[0], 0.0, ','))
.collect();
o.push_str(&pts.join(" "));
o.push_str("</coordinates>\n </LineString>\n </Placemark>\n");
}
o.push_str(" </Folder>\n");
}
o.push_str(" </Document>\n</kml>\n");
o
}