diff --git a/nusamai-geojson/src/conversion.rs b/nusamai-geojson/src/conversion.rs index 5a6d591e1..137495968 100644 --- a/nusamai-geojson/src/conversion.rs +++ b/nusamai-geojson/src/conversion.rs @@ -1,71 +1,91 @@ -use nusamai_geometry::{MultiLineString, MultiPoint, MultiPolygon, Polygon}; +use nusamai_geometry::{CoordNum, MultiLineString, MultiPoint, MultiPolygon}; -/// Create a GeoJSON geometry from `nusamai_geometry::MultiPolygon` +/// Create a GeoJSON MultiPolygon from `nusamai_geometry::MultiPolygon`. pub fn multipolygon_to_geometry(mpoly: &MultiPolygon<3>) -> geojson::Geometry { - let rings_list = mpoly - .iter() - .map(|poly| { - poly.rings() - .map(|c| c.iter_closed().map(|v| v.to_vec()).collect()) - .collect::>() - }) - .collect(); - geojson::Value::MultiPolygon(rings_list).into() + multipolygon_to_geometry_with_mapping(mpoly, |c| c.to_vec()) } -/// Create a GeoJSON geometry from vertices and MultiPolygon indices. +/// Create a GeoJSON MultiPolygon from vertices and indices. pub fn indexed_multipolygon_to_geometry( vertices: &[[f64; 3]], - mpoly: &MultiPolygon<1, u32>, + mpoly_idx: &MultiPolygon<1, u32>, ) -> geojson::Geometry { - let rings_list: Vec = mpoly + multipolygon_to_geometry_with_mapping(mpoly_idx, |idx| vertices[idx[0] as usize].to_vec()) +} + +/// Create a GeoJSON MultiPolygon from `nusamai_geometry::MultiPolygon` with a mapping function. +pub fn multipolygon_to_geometry_with_mapping( + mpoly: &MultiPolygon, + mapping: impl Fn(&[T]) -> Vec, +) -> geojson::Geometry { + let coords: Vec = mpoly .iter() - .map(|poly| indexed_polygon_to_rings(vertices, &poly)) + .map(|poly| { + poly.rings() + .map(|ls| { + ls.iter_closed() + .map(&mapping) // Get the actual coord values + .collect() + }) + .collect::>() + }) .collect(); - geojson::Value::MultiPolygon(rings_list).into() + geojson::Value::MultiPolygon(coords).into() } -fn indexed_polygon_to_rings( - vertices: &[[f64; 3]], - poly_idx: &Polygon<1, u32>, -) -> geojson::PolygonType { - poly_idx - .rings() - .map(|ls| { - ls.iter_closed() - .map(|idx| vertices[idx[0] as usize].to_vec()) // Get the actual coord values - .collect() - }) - .collect() +/// Create a GeoJSON MultiLineString from `nusamai_geometry::MultiLineString`. +pub fn multilinestring_to_geometry(mls: &MultiLineString<3>) -> geojson::Geometry { + multilinestring_to_geometry_with_mapping(mls, |c| c.to_vec()) } -/// Create a GeoJSON geometry from vertices and MultiLineString indices. +/// Create a GeoJSON MultiLineString from vertices and indices. pub fn indexed_multilinestring_to_geometry( vertices: &[[f64; 3]], mls_idx: &MultiLineString<1, u32>, ) -> geojson::Geometry { - let mls_coords = mls_idx + multilinestring_to_geometry_with_mapping(mls_idx, |idx| vertices[idx[0] as usize].to_vec()) +} + +/// Create a GeoJSON MultiLineString from `nusamai_geometry::MultiPolygon` with a mapping function. +pub fn multilinestring_to_geometry_with_mapping( + mls: &MultiLineString, + mapping: impl Fn(&[T]) -> Vec, +) -> geojson::Geometry { + let coords = mls .iter() .map(|ls_idx| { ls_idx .iter() - .map(|idx| vertices[idx[0] as usize].to_vec()) // Get the actual coord values + .map(&mapping) // Get the actual coord values .collect() }) .collect(); - geojson::Value::MultiLineString(mls_coords).into() + geojson::Value::MultiLineString(coords).into() +} + +/// Create a GeoJSON MultiPoint from `nusamai_geometry::MultiPoint`. +pub fn multipoint_to_geometry(mpoint: &MultiPoint<3>) -> geojson::Geometry { + multipoint_to_geometry_with_mapping(mpoint, |c| c.to_vec()) } -/// Create a GeoJSON geometry from vertices and MultiPoint indices. +/// Create a GeoJSON MultiPoint from vertices and indices. pub fn indexed_multipoint_to_geometry( vertices: &[[f64; 3]], - mpoint: &MultiPoint<1, u32>, + mpoint_idx: &MultiPoint<1, u32>, +) -> geojson::Geometry { + multipoint_to_geometry_with_mapping(mpoint_idx, |idx| vertices[idx[0] as usize].to_vec()) +} + +/// Create a GeoJSON MultiPoint from `nusamai_geometry::MultiPoint` with a mapping function. +pub fn multipoint_to_geometry_with_mapping( + mpoint: &MultiPoint, + mapping: impl Fn(&[T]) -> Vec, ) -> geojson::Geometry { - let mpoint_coords = mpoint + let coords = mpoint .iter() - .map(|p| vertices[p[0] as usize].to_vec()) // Get the actual coord values + .map(&mapping) // Get the actual coord values .collect(); - geojson::Value::MultiPoint(mpoint_coords).into() + geojson::Value::MultiPoint(coords).into() } #[cfg(test)] @@ -266,6 +286,33 @@ mod tests { }; } + #[test] + fn test_multilinestring() { + let mut mls = MultiLineString::<3>::new(); + mls.add_linestring([[11., 12., 13.], [21., 22., 23.], [31., 32., 33.]]); + mls.add_linestring([[111., 112., 113.], [121., 122., 123.], [131., 132., 133.]]); + + let geom = multilinestring_to_geometry(&mls); + let geojson::Value::MultiLineString(mls) = geom.value else { + panic!("The result is not a GeoJSON MultiPolygon"); + }; + assert_eq!( + mls, + vec![ + vec![ + vec![11., 12., 13.], + vec![21., 22., 23.], + vec![31., 32., 33.], + ], + vec![ + vec![111., 112., 113.], + vec![121., 122., 123.], + vec![131., 132., 133.], + ], + ] + ); + } + #[test] fn test_indexed_multilinestring() { let vertices = vec![ @@ -318,6 +365,27 @@ mod tests { } } + #[test] + fn test_multipoint() { + let mut mpoint = MultiPoint::<3>::new(); + mpoint.push(&[11., 12., 13.]); + mpoint.push(&[21., 22., 23.]); + mpoint.push(&[31., 32., 33.]); + + let geom = multipoint_to_geometry(&mpoint); + let geojson::Value::MultiPoint(mpoint) = geom.value else { + panic!("The result is not a GeoJSON MultiPolygon"); + }; + assert_eq!( + mpoint, + vec![ + vec![11., 12., 13.], + vec![21., 22., 23.], + vec![31., 32., 33.], + ] + ); + } + #[test] fn test_indexed_multipoint() { let vertices = vec![[0., 0., 111.], [1., 2., 222.], [3., 4., 333.]]; diff --git a/nusamai-gpkg/src/geometry.rs b/nusamai-gpkg/src/geometry.rs index a588134ca..08444b43c 100644 --- a/nusamai-gpkg/src/geometry.rs +++ b/nusamai-gpkg/src/geometry.rs @@ -2,7 +2,8 @@ //! //! cf. https://www.geopackage.org/spec130/#gpb_format -use nusamai_geometry::{MultiPolygon, Polygon}; +use nusamai_geometry::{CoordNum, MultiPolygon, Polygon}; +use std::io::Write; #[repr(u8)] pub enum WkbByteOrder { @@ -44,136 +45,80 @@ pub enum WkbGeometryType { GeometryCollectionZM = 3007, } -fn geometry_header(srs_id: i32) -> Vec { - let mut header: Vec = Vec::with_capacity(8); - header.extend_from_slice(&[0x47, 0x50]); // Magic number - header.push(0x00); // Version - header.push(0b00000001); // Flags - header.extend_from_slice(&i32::to_le_bytes(srs_id)); // SRS ID - header +fn write_geometry_header(writer: &mut W, srs_id: i32) -> std::io::Result<()> { + writer.write_all(&[0x47, 0x50])?; // Magic number + writer.write_all(&[ + 0x00, // Version + 0b00000001, // Flags + ])?; + writer.write_all(&i32::to_le_bytes(srs_id))?; // SRS ID + Ok(()) } -fn polygon_to_rings(vertices: &[[f64; 3]], poly: &Polygon<1, u32>) -> Vec>> { - let rings: Vec<_> = poly - .rings() - .map(|ls| { - let coords: Vec<_> = ls - .iter_closed() - .map(|idx| vertices[idx[0] as usize].to_vec()) // Get the actual coord values - .collect(); - coords - }) - .collect(); - - rings +fn write_polygon_body( + writer: &mut W, + poly: &Polygon, + mapping: impl Fn(&[T]) -> [f64; 3], +) -> std::io::Result<()> { + // Byte order: Little endian (1) + writer.write_all(&[WkbByteOrder::LittleEndian as u8])?; + + // Geometry type: wkbPolygonZ (1003) + writer.write_all(&(WkbGeometryType::PolygonZ as u32).to_le_bytes())?; + + // numRings + writer.write_all(&(poly.rings().count() as u32).to_le_bytes())?; + + for ring in poly.rings() { + // numPoints + writer.write_all(&(ring.iter_closed().count() as u32).to_le_bytes())?; + + for idx in ring.iter_closed() { + let [x, y, z] = mapping(idx); + writer.write_all(&f64::to_le_bytes(x))?; + writer.write_all(&f64::to_le_bytes(y))?; + writer.write_all(&f64::to_le_bytes(z))?; + } + } + Ok(()) } -pub fn multipolygon_to_bytes( +pub fn write_indexed_multipolygon( + writer: &mut W, vertices: &[[f64; 3]], mpoly: &MultiPolygon<'_, 1, u32>, srs_id: i32, -) -> Vec { - let mut bytes: Vec = geometry_header(srs_id); +) -> std::io::Result<()> { + write_geometry_header(writer, srs_id)?; + write_multipolygon_body(writer, mpoly, |idx| vertices[idx[0] as usize])?; + Ok(()) +} +fn write_multipolygon_body( + writer: &mut W, + mpoly: &MultiPolygon<'_, D, T>, + mapping: impl Fn(&[T]) -> [f64; 3], +) -> std::io::Result<()> { // Byte order: Little endian (1) - bytes.push(WkbByteOrder::LittleEndian as u8); + writer.write_all(&[WkbByteOrder::LittleEndian as u8])?; // Geometry type: wkbMultiPolygonZ (1006) - bytes.extend_from_slice(&(WkbGeometryType::MultiPolygonZ as u32).to_le_bytes()); + writer.write_all(&(WkbGeometryType::MultiPolygonZ as u32).to_le_bytes())?; // numPolygons - bytes.extend_from_slice(&(mpoly.len() as u32).to_le_bytes()); + writer.write_all(&(mpoly.len() as u32).to_le_bytes())?; for poly in mpoly { - // Byte order: Little endian (1) - bytes.push(WkbByteOrder::LittleEndian as u8); - - // Geometry type: wkbPolygonZ (1003) - bytes.extend_from_slice(&(WkbGeometryType::PolygonZ as u32).to_le_bytes()); - - let rings = polygon_to_rings(vertices, &poly); - - // numRings - bytes.extend_from_slice(&(rings.len() as u32).to_le_bytes()); - - for ring in rings { - // numPoints - bytes.extend_from_slice(&(ring.len() as u32).to_le_bytes()); - - for coord in ring { - bytes.extend_from_slice(&f64::to_le_bytes(coord[0])); - bytes.extend_from_slice(&f64::to_le_bytes(coord[1])); - bytes.extend_from_slice(&f64::to_le_bytes(coord[2])); - } - } + write_polygon_body(writer, &poly, &mapping)?; } - bytes + Ok(()) } #[cfg(test)] mod tests { use super::*; - #[test] - fn test_polygon_to_rings() { - let vertices: Vec<[f64; 3]> = vec![ - // exterior (vertex 0~3) - [0., 0., 111.], - [5., 0., 111.], - [5., 5., 111.], - [0., 5., 111.], - // interior 1 (vertex 4~7) - [1., 1., 111.], - [2., 1., 111.], - [2., 2., 111.], - [1., 2., 111.], - // interior 2 (vertex 8~11) - [3., 3., 111.], - [4., 3., 111.], - [4., 4., 111.], - [3., 4., 111.], - ]; - - let mut poly = Polygon::<'_, 1, u32>::new(); - poly.add_ring([[0], [1], [2], [3]]); - poly.add_ring([[4], [5], [6], [7]]); - poly.add_ring([[8], [9], [10], [11]]); - - let rings = polygon_to_rings(&vertices, &poly); - - assert_eq!(rings.len(), 3); - - for (i, ri) in rings.iter().enumerate() { - match i { - 0 => { - assert_eq!(ri.len(), 5); - assert_eq!(ri[0], vec![0., 0., 111.]); - assert_eq!(ri[1], vec![5., 0., 111.]); - assert_eq!(ri[2], vec![5., 5., 111.]); - assert_eq!(ri[3], vec![0., 5., 111.]); - assert_eq!(ri[4], vec![0., 0., 111.]); - } - 1 => { - assert_eq!(ri.len(), 5); - assert_eq!(ri[0], vec![1., 1., 111.]); - assert_eq!(ri[1], vec![2., 1., 111.]); - assert_eq!(ri[2], vec![2., 2., 111.]); - assert_eq!(ri[3], vec![1., 2., 111.]); - assert_eq!(ri[4], vec![1., 1., 111.]); - } - 2 => { - assert_eq!(ri.len(), 5); - assert_eq!(ri[0], vec![3., 3., 111.]); - assert_eq!(ri[1], vec![4., 3., 111.]); - assert_eq!(ri[2], vec![4., 4., 111.]); - assert_eq!(ri[3], vec![3., 4., 111.]); - } - _ => panic!("Unexpected ring index"), - } - } - } - #[test] fn test_multipolygon_to_bytes() { let vertices: Vec<[f64; 3]> = vec![ @@ -194,7 +139,8 @@ mod tests { mpoly.add_exterior([[0], [1], [2], [3], [0]]); mpoly.add_interior([[4], [5], [6], [7], [4]]); - let bytes = multipolygon_to_bytes(&vertices, &mpoly, 1234); + let mut bytes = Vec::new(); + write_indexed_multipolygon(&mut bytes, &vertices, &mpoly, 1234).unwrap(); assert_eq!(bytes.len(), 274); diff --git a/nusamai/src/sink/geojson/mod.rs b/nusamai/src/sink/geojson/mod.rs index 028c9dafa..9d73080a2 100644 --- a/nusamai/src/sink/geojson/mod.rs +++ b/nusamai/src/sink/geojson/mod.rs @@ -51,7 +51,6 @@ impl DataSinkProvider for GeoJsonSinkProvider { } } -#[derive(Default)] pub struct GeoJsonSink { output_path: PathBuf, } diff --git a/nusamai/src/sink/gpkg/mod.rs b/nusamai/src/sink/gpkg/mod.rs index e03fb4826..886b0da95 100644 --- a/nusamai/src/sink/gpkg/mod.rs +++ b/nusamai/src/sink/gpkg/mod.rs @@ -12,7 +12,7 @@ use crate::sink::{DataSink, DataSinkProvider, SinkInfo}; use crate::get_parameter_value; use crate::parameters::*; -use nusamai_gpkg::geometry::multipolygon_to_bytes; +use nusamai_gpkg::geometry::write_indexed_multipolygon; use nusamai_gpkg::GpkgHandler; pub struct GpkgSinkProvider {} @@ -49,7 +49,6 @@ impl DataSinkProvider for GpkgSinkProvider { } } -#[derive(Default)] pub struct GpkgSink { output_path: PathBuf, } @@ -81,11 +80,18 @@ impl GpkgSink { } let cityobj = parcel.cityobj; if !cityobj.geometries.multipolygon.is_empty() { - let bytes = multipolygon_to_bytes( + let mut bytes = Vec::new(); + if write_indexed_multipolygon( + &mut bytes, &cityobj.geometries.vertices, &cityobj.geometries.multipolygon, 4326, - ); + ) + .is_err() + { + // TODO: fatal error + } + if sender.blocking_send(bytes).is_err() { return Err(()); }; diff --git a/nusamai/src/sink/tiling2d/mod.rs b/nusamai/src/sink/tiling2d/mod.rs index 2af687d13..0dd47dec9 100644 --- a/nusamai/src/sink/tiling2d/mod.rs +++ b/nusamai/src/sink/tiling2d/mod.rs @@ -49,7 +49,6 @@ impl DataSinkProvider for Tiling2DSinkProvider { } } -#[derive(Default)] pub struct Tiling2DSink { output_path: PathBuf, }