diff --git a/nusamai-kml/examples/write_kml.rs b/nusamai-kml/examples/write_kml.rs deleted file mode 100644 index f6b10751..00000000 --- a/nusamai-kml/examples/write_kml.rs +++ /dev/null @@ -1,70 +0,0 @@ -use kml::{ - types::{Geometry, Kml, MultiGeometry, Placemark}, - KmlWriter, -}; -use nusamai_geometry::{MultiPoint, Polygon3}; -use nusamai_kml::conversion::{multipoint_to_kml, polygon_to_kml}; -use std::collections::HashMap; -use std::fs::File; -use std::io::{BufWriter, Write}; - -fn poly_to_multigeom() -> MultiGeometry { - let mut poly = Polygon3::new(); - poly.add_ring([ - [10., 10., 0.], - [10., 20., 0.], - [20., 20., 0.], - [20., 10., 0.], // not closed - ]); - poly.add_ring([ - [15., 15., 0.], - [18., 10., 0.], - [18., 18., 0.], - [15., 18., 0.], - ]); - - polygon_to_kml(&poly) -} -fn point_to_multigeom() -> MultiGeometry { - let mut mpoint = MultiPoint::<3>::new(); - mpoint.push(&[11., 12., 13.]); - mpoint.push(&[21., 22., 23.]); - mpoint.push(&[31., 32., 33.]); - - multipoint_to_kml(&mpoint) -} - -fn multigeometry_to_file(multi_geom: MultiGeometry, filename: String) { - let placemark = Placemark { - geometry: Some(Geometry::MultiGeometry(multi_geom)), - ..Default::default() - }; - - let folder = Kml::Folder { - attrs: HashMap::new(), - elements: vec![Kml::Placemark(placemark)], - }; - - let file_path = filename; - let file = File::create(file_path).expect("Failed to create file"); - - let mut buf_writer = BufWriter::new(file); - - writeln!(buf_writer, r#""#) - .expect("Failed to write XML declaration"); - writeln!( - buf_writer, - r#""# - ) // Add tag here - .expect("Failed to write tag"); - let mut kml_writer = KmlWriter::from_writer(&mut buf_writer); - kml_writer.write(&folder).expect("Failed to write KML data"); - writeln!(buf_writer, "") // Add tag here - .expect("Failed to write tag"); -} - -fn main() { - multigeometry_to_file(point_to_multigeom(), "point.kml".to_string()); - - multigeometry_to_file(poly_to_multigeom(), "poly.kml".to_string()); -} diff --git a/nusamai-kml/src/conversion.rs b/nusamai-kml/src/conversion.rs index e4d042fe..b75e1f71 100644 --- a/nusamai-kml/src/conversion.rs +++ b/nusamai-kml/src/conversion.rs @@ -5,122 +5,103 @@ use nusamai_geometry::{CoordNum, MultiPoint, MultiPolygon, Polygon}; use std::{collections::HashMap, vec}; const EXTRUDE: bool = false; -const ALTITUDEMODE: AltitudeMode = AltitudeMode::RelativeToGround; +const ALTITUDE_MODE: AltitudeMode = AltitudeMode::RelativeToGround; -pub fn multipolygon_to_kml(mpoly: &MultiPolygon<3>) -> MultiGeometry { +pub fn multipolygon_to_kml(mpoly: &MultiPolygon<3>) -> Vec { multipolygon_to_kml_with_mapping(mpoly, |c| c) } pub fn indexed_multipolygon_to_kml( vertices: &[[f64; 3]], mpoly_idx: &MultiPolygon<1, u32>, -) -> MultiGeometry { +) -> Vec { multipolygon_to_kml_with_mapping(mpoly_idx, |idx| vertices[idx[0] as usize]) } fn multipolygon_to_kml_with_mapping( mpoly: &MultiPolygon, mapping: impl Fn([T; D]) -> [f64; 3], -) -> MultiGeometry { - let polygons = mpoly +) -> Vec { + mpoly .iter() - .map(|poly| polygon_to_kml_with_mapping(poly.clone(), &mapping)) - .collect::>(); - MultiGeometry { - geometries: polygons.into_iter().map(Geometry::MultiGeometry).collect(), + .flat_map(|poly| polygon_to_kml_with_mapping(&poly, &mapping)) // Flatten the vector of vectors + .collect() +} + +fn polygon_to_kml_polygon_with_mapping( + poly: &Polygon, + mapping: impl Fn([T; D]) -> [f64; 3], +) -> KmlPolygon { + KmlPolygon { + outer: polygon_to_kml_outer_boundary_with_mapping(poly, &mapping), + inner: polygon_to_kml_inner_boundary_with_mapping(poly, &mapping), + extrude: EXTRUDE, + tessellate: false, + altitude_mode: ALTITUDE_MODE, attrs: HashMap::new(), } } fn polygon_to_kml_outer_boundary_with_mapping( - poly: Polygon, + poly: &Polygon, mapping: impl Fn([T; D]) -> [f64; 3], ) -> LinearRing { let outer_coords: Vec = poly .exterior() .iter_closed() .map(&mapping) - .map(|coords| Coord { - x: coords[0], - y: coords[1], - z: Some(coords[2]), - }) + .map(|[x, y, z]| Coord { x, y, z: Some(z) }) .collect(); LinearRing { coords: outer_coords, extrude: EXTRUDE, tessellate: false, - altitude_mode: ALTITUDEMODE, + altitude_mode: ALTITUDE_MODE, attrs: HashMap::new(), } } fn polygon_to_kml_inner_boundary_with_mapping( - poly: Polygon, + poly: &Polygon, mapping: impl Fn([T; D]) -> [f64; 3], ) -> Vec { poly.interiors() .map(|ring| { ring.iter_closed() .map(&mapping) - .map(|coords| Coord { - x: coords[0], - y: coords[1], - z: Some(coords[2]), - }) - .collect::>() + .map(|[x, y, z]| Coord { x, y, z: Some(z) }) + .collect() }) .map(|coords| LinearRing { coords, extrude: EXTRUDE, tessellate: false, - altitude_mode: ALTITUDEMODE, + altitude_mode: ALTITUDE_MODE, attrs: HashMap::new(), }) .collect() } -fn polygon_to_kml_polygon_with_mapping( - poly: Polygon, - mapping: impl Fn([T; D]) -> [f64; 3], -) -> KmlPolygon { - let outer = polygon_to_kml_outer_boundary_with_mapping(poly.clone(), &mapping); - let inner = polygon_to_kml_inner_boundary_with_mapping(poly, &mapping); - - KmlPolygon { - outer, - inner, - extrude: EXTRUDE, - tessellate: false, - altitude_mode: ALTITUDEMODE, - attrs: HashMap::new(), - } -} - /// Create a kml::MultiGeometry with Polygon from `nusamai_geometry::MultiPoint` with a mapping function. pub fn polygon_to_kml_with_mapping( - poly: Polygon, + poly: &Polygon, mapping: impl Fn([T; D]) -> [f64; 3], -) -> MultiGeometry { - let polygons = vec![polygon_to_kml_polygon_with_mapping(poly, mapping)]; - MultiGeometry { - geometries: polygons - .into_iter() - .map(|poly: KmlPolygon| Geometry::Polygon(poly)) - .collect(), - attrs: HashMap::new(), - } +) -> Vec { + vec![polygon_to_kml_polygon_with_mapping(poly, mapping)] } /// Create a kml::MultiGeometry from a nusamai_geometry::MultiPolygon -pub fn polygon_to_kml(poly: &Polygon<3>) -> MultiGeometry { - polygon_to_kml_with_mapping(poly.clone(), |c| c) +pub fn polygon_to_kml(poly: &Polygon<3>) -> Vec { + polygon_to_kml_with_mapping(poly, |c| c) } /// Create a kml::MultiGeometry with Polygon vertices and indices. -pub fn indexed_polygon_to_kml(vertices: &[[f64; 3]], poly_idx: &Polygon<1, u32>) -> MultiGeometry { - polygon_to_kml_with_mapping(poly_idx.clone(), |idx| vertices[idx[0] as usize]) +pub fn indexed_polygon_to_kml( + vertices: &[[f64; 3]], + poly_idx: &Polygon<1, u32>, +) -> Vec { + polygon_to_kml_with_mapping(poly_idx, |idx| vertices[idx[0] as usize]) } /// Create a kml::MultiGeometry with Points from `nusamai_geometry::MultiPoint` with a mapping function. @@ -128,15 +109,11 @@ pub fn multipoint_to_kml_with_mapping( mpoint: &MultiPoint, mapping: impl Fn([T; D]) -> [f64; 3], ) -> MultiGeometry { - let points = mpoint - .iter() - .map(&mapping) - .map(|coords| Point::new(coords[0], coords[1], Some(coords[2]))) - .collect::>(); MultiGeometry { - geometries: points - .into_iter() - .map(|pt: Point| Geometry::Point(pt)) + geometries: mpoint + .iter() + .map(&mapping) + .map(|coord| Geometry::Point(Point::new(coord[0], coord[1], Some(coord[2])))) .collect(), attrs: HashMap::new(), } @@ -220,84 +197,90 @@ mod tests { [15., 18., 0.], ]); - let multi_geom = polygon_to_kml(&poly); + let polygons = polygon_to_kml(&poly); - assert_eq!(&multi_geom.geometries.len(), &1); + assert_eq!(polygons[0].outer.coords.len(), 5); + assert_eq!( + polygons[0].outer.coords[0], + Coord { + x: 10., + y: 10., + z: Some(0.) + } + ); + assert_eq!( + polygons[0].outer.coords[1], + Coord { + x: 10., + y: 20., + z: Some(0.) + } + ); + assert_eq!( + polygons[0].outer.coords[2], + Coord { + x: 20., + y: 20., + z: Some(0.) + } + ); + assert_eq!( + polygons[0].outer.coords[3], + Coord { + x: 20., + y: 10., + z: Some(0.) + } + ); + assert_eq!( + polygons[0].outer.coords[4], + Coord { + x: 10., + y: 10., + z: Some(0.) + } + ); + assert_eq!(polygons[0].inner[0].coords.len(), 5); + assert_eq!( + polygons[0].inner[0].coords[0], + Coord { + x: 15., + y: 15., + z: Some(0.) + } + ); + assert_eq!( + polygons[0].inner[0].coords[1], + Coord { + x: 18., + y: 10., + z: Some(0.) + } + ); + assert_eq!( + polygons[0].inner[0].coords[2], + Coord { + x: 18., + y: 18., + z: Some(0.) + } + ); + assert_eq!( + polygons[0].inner[0].coords[3], + Coord { + x: 15., + y: 18., + z: Some(0.) + } + ); assert_eq!( - &multi_geom.geometries[0], - &Geometry::Polygon(KmlPolygon { - outer: LinearRing { - coords: vec![ - Coord { - x: 10., - y: 10., - z: Some(0.), - }, - Coord { - x: 10., - y: 20., - z: Some(0.), - }, - Coord { - x: 20., - y: 20., - z: Some(0.), - }, - Coord { - x: 20., - y: 10., - z: Some(0.), - }, - Coord { - x: 10.0, - y: 10.0, - z: Some(0.0) - } - ], - extrude: false, - tessellate: false, - altitude_mode: AltitudeMode::RelativeToGround, - attrs: HashMap::new(), - }, - inner: vec![LinearRing { - coords: vec![ - Coord { - x: 15., - y: 15., - z: Some(0.), - }, - Coord { - x: 18., - y: 10., - z: Some(0.), - }, - Coord { - x: 18., - y: 18., - z: Some(0.), - }, - Coord { - x: 15., - y: 18., - z: Some(0.), - }, - Coord { - x: 15.0, - y: 15.0, - z: Some(0.0) - } - ], - extrude: false, - tessellate: false, - altitude_mode: AltitudeMode::RelativeToGround, - attrs: HashMap::new(), - }], - extrude: false, - tessellate: false, - altitude_mode: AltitudeMode::RelativeToGround, - attrs: HashMap::new(), - }) + polygons[0].inner[0].coords[4], + Coord { + x: 15., + y: 15., + z: Some(0.) + } ); } @@ -326,121 +309,92 @@ mod tests { poly.add_ring([[4], [5], [6], [7], [4]]); poly.add_ring([[8], [9], [10], [11], [8]]); - let multi_geom = indexed_polygon_to_kml(&vertices, &poly); + let polygons = indexed_polygon_to_kml(&vertices, &poly); - assert_eq!(&multi_geom.geometries.len(), &1); + assert_eq!(polygons.len(), 1); + assert_eq!(polygons[0].outer.coords.len(), 5); + assert_eq!( + polygons[0].outer.coords[0], + Coord { + x: 0., + y: 0., + z: Some(111.) + } + ); + assert_eq!( + polygons[0].outer.coords[1], + Coord { + x: 5., + y: 0., + z: Some(111.) + } + ); assert_eq!( - &multi_geom.geometries[0], - &Geometry::Polygon(KmlPolygon { - outer: LinearRing { - coords: vec![ - Coord { - x: 0., - y: 0., - z: Some(111.), - }, - Coord { - x: 5., - y: 0., - z: Some(111.), - }, - Coord { - x: 5., - y: 5., - z: Some(111.), - }, - Coord { - x: 0., - y: 5., - z: Some(111.), - }, - Coord { - x: 0.0, - y: 0.0, - z: Some(111.0) - } - ], - extrude: false, - tessellate: false, - altitude_mode: AltitudeMode::RelativeToGround, - attrs: HashMap::new(), - }, - inner: vec![ - LinearRing { - coords: vec![ - Coord { - x: 1., - y: 1., - z: Some(111.), - }, - Coord { - x: 2., - y: 1., - z: Some(111.), - }, - Coord { - x: 2., - y: 2., - z: Some(111.), - }, - Coord { - x: 1., - y: 2., - z: Some(111.), - }, - Coord { - x: 1.0, - y: 1.0, - z: Some(111.0) - } - ], - extrude: false, - tessellate: false, - altitude_mode: AltitudeMode::RelativeToGround, - attrs: HashMap::new(), - }, - LinearRing { - coords: vec![ - Coord { - x: 3., - y: 3., - z: Some(111.), - }, - Coord { - x: 4., - y: 3., - z: Some(111.), - }, - Coord { - x: 4., - y: 4., - z: Some(111.), - }, - Coord { - x: 3., - y: 4., - z: Some(111.), - }, - Coord { - x: 3.0, - y: 3.0, - z: Some(111.0) - } - ], - extrude: false, - tessellate: false, - altitude_mode: AltitudeMode::RelativeToGround, - attrs: HashMap::new(), - } - ], - extrude: false, - tessellate: false, - altitude_mode: AltitudeMode::RelativeToGround, - attrs: HashMap::new(), - }) + polygons[0].outer.coords[2], + Coord { + x: 5., + y: 5., + z: Some(111.) + } + ); + assert_eq!( + polygons[0].outer.coords[3], + Coord { + x: 0., + y: 5., + z: Some(111.) + } + ); + assert_eq!( + polygons[0].outer.coords[4], + Coord { + x: 0., + y: 0., + z: Some(111.) + } + ); + + assert_eq!(polygons[0].inner[0].coords.len(), 5); + assert_eq!( + polygons[0].inner[0].coords[0], + Coord { + x: 1., + y: 1., + z: Some(111.) + } + ); + assert_eq!( + polygons[0].inner[0].coords[1], + Coord { + x: 2., + y: 1., + z: Some(111.) + } + ); + assert_eq!( + polygons[0].inner[0].coords[2], + Coord { + x: 2., + y: 2., + z: Some(111.) + } + ); + assert_eq!( + polygons[0].inner[0].coords[3], + Coord { + x: 1., + y: 2., + z: Some(111.) + } + ); + assert_eq!( + polygons[0].inner[0].coords[4], + Coord { + x: 1., + y: 1., + z: Some(111.) + } ); } } - - diff --git a/nusamai/src/sink/kml/mod.rs b/nusamai/src/sink/kml/mod.rs index b47e2b69..04db7187 100644 --- a/nusamai/src/sink/kml/mod.rs +++ b/nusamai/src/sink/kml/mod.rs @@ -16,7 +16,7 @@ use nusamai_plateau::Entity; use rayon::prelude::*; use kml::{ - types::{Kml, MultiGeometry, Placemark}, + types::{Geometry, Kml, MultiGeometry, Placemark, Polygon as KmlPolygon}, KmlWriter, }; @@ -70,40 +70,37 @@ impl DataSink for KmlSink { let (ra, rb) = rayon::join( || { - // Convert CityObjects to GeoJSON objects + // Convert CityObjects to KML objects upstream .into_iter() .par_bridge() .try_for_each_with(sender, |sender, parcel| { feedback.ensure_not_canceled()?; - let multi_geom = entity_to_kml_mutilgeom(&parcel.entity); + let polygons = entity_to_kml_polygons(&parcel.entity); - for geom in multi_geom.geometries { - if sender.send(geom).is_err() { - return Err(PipelineError::Canceled); - } + let geoms = polygons.into_iter().map(Geometry::Polygon).collect(); + let multi_geom = MultiGeometry { + geometries: geoms, + ..Default::default() + }; + + let placemark = Placemark { + geometry: Some(Geometry::MultiGeometry(multi_geom)), + ..Default::default() + }; + + if sender.send(placemark).is_err() { + return Err(PipelineError::Canceled); } Ok(()) }) }, || { - // Write GeoJSON to a file - let mut placemarks: Vec = Vec::new(); - - for geom in receiver.into_iter() { - let placemark = Placemark { - geometry: Some(geom), - ..Default::default() - }; - - placemarks.push(Kml::Placemark(placemark)); - } - // TODO: Handle output file path - + let placemarks = receiver.into_iter().collect::>(); let folder = Kml::Folder { attrs: HashMap::new(), - elements: placemarks, + elements: placemarks.into_iter().map(Kml::Placemark).collect(), }; let mut file = File::create(&self.output_path).unwrap(); @@ -146,15 +143,15 @@ fn extract_properties(value: &nusamai_citygml::object::Value) -> Option MultiGeometry { +pub fn entity_to_kml_polygons(entity: &Entity) -> Vec { let _properties = extract_properties(&entity.root); let geom_store = entity.geometry_store.read().unwrap(); let Value::Object(obj) = &entity.root else { - return MultiGeometry::default(); + return Vec::new(); }; let ObjectStereotype::Feature { geometries, .. } = &obj.stereotype else { - return MultiGeometry::default(); + return Vec::new(); }; let mut mpoly = nusamai_geometry::MultiPolygon::<1, u32>::new();