use anyhow::{Context, Result as AnyResult};
use geo_types::{LineString, coord};
use h3o::{CellIndex, LatLng};
use kml::{Kml, KmlDocument, KmlWriter, types::Folder};
use maplit::hashmap;
use std::io;
pub fn boundaries(indexes: &[CellIndex], style: &str) -> Vec<Kml> {
indexes
.iter()
.map(|index| {
let mut linestring: LineString = index.boundary().into();
linestring.close();
let geometry = kml::types::LineString {
coords: linestring.0.into_iter().map(Into::into).collect(),
tessellate: true,
..kml::types::LineString::default()
};
let placemark = kml::types::Placemark {
name: Some(index.to_string()),
attrs: hashmap! {
"styleUrl".to_owned() => format!("#{style}"),
},
geometry: Some(kml::types::Geometry::LineString(geometry)),
..kml::types::Placemark::default()
};
Kml::Placemark(placemark)
})
.collect()
}
pub fn centers(indexes: &[CellIndex], style: &str) -> Vec<Kml> {
indexes
.iter()
.copied()
.map(|index| {
let ll = LatLng::from(index);
let geometry = kml::types::Point {
coord: coord! {x: ll.lng(), y: ll.lat()}.into(),
altitude_mode: kml::types::AltitudeMode::RelativeToGround,
..kml::types::Point::default()
};
let placemark = kml::types::Placemark {
name: Some(index.to_string()),
attrs: hashmap! {
"styleUrl".to_owned() => format!("#{style}"),
},
geometry: Some(kml::types::Geometry::Point(geometry)),
..kml::types::Placemark::default()
};
Kml::Placemark(placemark)
})
.collect()
}
pub fn print_document(
name: String,
description: String,
elements: Vec<Kml>,
) -> AnyResult<()> {
let document = KmlDocument::<f64> {
version: kml::KmlVersion::V22,
attrs: hashmap! {
"xmlns".to_owned() => "http://www.opengis.net/kml/2.2".to_owned(),
"xmlns:gx".to_owned() => "http://www.google.com/kml/ext/2.2".to_owned(),
"xmlns:kml".to_owned() => "http://www.opengis.net/kml/2.2".to_owned(),
"xmlns:atom".to_owned() => "http://www.w3.org/2005/Atom".to_owned(),
},
elements: vec![Kml::Folder(Folder {
name: Some(name),
description: Some(description),
style_url: None,
attrs: hashmap! {},
elements,
})],
};
let mut stdout = io::stdout().lock();
let mut writer = KmlWriter::from_writer(&mut stdout);
let kml = Kml::KmlDocument(document);
println!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
writer.write(&kml).context("write KML to stdout")?;
Ok(())
}
pub fn polygons(polygons: geo_types::MultiPolygon, style: &str) -> Kml {
let geometries = kml::types::MultiGeometry::new(
polygons
.into_iter()
.map(|polygon| {
let mut polygon = kml::types::Polygon::from(polygon);
polygon.tessellate = true;
kml::types::Geometry::Polygon(polygon)
})
.collect(),
);
let placemark = kml::types::Placemark {
attrs: hashmap! {
"styleUrl".to_owned() => format!("#{style}"),
},
geometry: Some(kml::types::Geometry::MultiGeometry(geometries)),
..kml::types::Placemark::default()
};
Kml::Placemark(placemark)
}
pub fn to_geometry(kml: Kml) -> AnyResult<Option<geo_types::Geometry>> {
Ok(match kml {
Kml::KmlDocument(document) => {
Some(geo_types::Geometry::GeometryCollection(
geo_types::GeometryCollection(
document
.elements
.into_iter()
.filter_map(|element| to_geometry(element).transpose())
.collect::<Result<Vec<_>, _>>()
.context("invalid geometry in document/folder")?,
),
))
}
Kml::Point(point) => Some(geo_types::Geometry::Point(point.into())),
Kml::Location(location) => Some(geo_types::Geometry::Point(
(location.longitude, location.latitude).into(),
)),
Kml::LineString(line) => {
Some(geo_types::Geometry::LineString(line.into()))
}
Kml::LinearRing(ring) => {
Some(geo_types::Geometry::LineString(ring.into()))
}
Kml::Polygon(polygon) => {
Some(geo_types::Geometry::Polygon(polygon.into()))
}
Kml::MultiGeometry(geometries) => {
Some(geo_types::Geometry::GeometryCollection(
geometries.try_into().context("invalid multigeometry")?,
))
}
Kml::Placemark(placemark) => placemark
.geometry
.map(TryInto::try_into)
.transpose()
.context("invalid geometry in placemark")?,
Kml::Document { elements, .. }
| Kml::Folder(Folder { elements, .. }) => {
Some(geo_types::Geometry::GeometryCollection(
geo_types::GeometryCollection(
elements
.into_iter()
.filter_map(|element| to_geometry(element).transpose())
.collect::<Result<Vec<_>, _>>()
.context("invalid geometry in document/folder")?,
),
))
}
_ => None,
})
}