Skip to content

Commit

Permalink
improve gpkg geom gen
Browse files Browse the repository at this point in the history
  • Loading branch information
ciscorn committed Jan 7, 2024
1 parent 6f9e5b8 commit d34fee7
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 115 deletions.
280 changes: 168 additions & 112 deletions nusamai-gpkg/src/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,141 +2,196 @@
//!
//! cf. https://www.geopackage.org/spec130/#gpb_format
use nusamai_geometry::{MultiPolygon, Polygon};

fn geometry_header(srs_id: i32) -> Vec<u8> {
let mut header: Vec<u8> = 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
use nusamai_geometry::{CoordNum, MultiPolygon, Polygon};
use std::io::Write;

#[repr(u8)]
enum WkbByteOrder {
// Big endian (XDR)
BigEndian = 0,
// Little endian (NDR)
LittleEndian = 1,
}

fn polygon_to_rings(vertices: &[[f64; 3]], poly: &Polygon<1, u32>) -> Vec<Vec<Vec<f64>>> {
let linestrings = std::iter::once(poly.exterior()).chain(poly.interiors());
#[repr(u32)]
pub enum WkbGeometryType {
Point = 1,
LineString = 2,
Polygon = 3,
MultiPoint = 4,
MultiLineString = 5,
MultiPolygon = 6,
GeometryCollection = 7,
PointZ = 1001,
LineStringZ = 1002,
PolygonZ = 1003,
MultiPointZ = 1004,
MultiLineStringZ = 1005,
MultiPolygonZ = 1006,
GeometryCollectionZ = 1007,
PointM = 2001,
LineStringM = 2002,
PolygonM = 2003,
MultiPointM = 2004,
MultiLineStringM = 2005,
MultiPolygonM = 2006,
GeometryCollectionM = 2007,
PointZM = 3001,
LineStringZM = 3002,
PolygonZM = 3003,
MultiPointZM = 3004,
MultiLineStringZM = 3005,
MultiPolygonZM = 3006,
GeometryCollectionZM = 3007,
}

fn write_geometry_header<W: Write>(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(())
}

let rings: Vec<_> = linestrings
.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();
// fn polygon_to_rings(vertices: &[[f64; 3]], poly: &Polygon<1, u32>) -> Vec<Vec<Vec<f64>>> {
// 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<W: Write, const D: usize, T: CoordNum>(
writer: &mut W,
poly: &Polygon<D, T>,
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())?;

rings
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<W: Write>(
writer: &mut W,
vertices: &[[f64; 3]],
mpoly: &MultiPolygon<'_, 1, u32>,
srs_id: i32,
) -> Vec<u8> {
let mut bytes: Vec<u8> = 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(())
}

// Byte order: Little endian
bytes.push(0x01);
fn write_multipolygon_body<W: Write, const D: usize, T: CoordNum>(
writer: &mut W,
mpoly: &MultiPolygon<'_, D, T>,
mapping: impl Fn(&[T]) -> [f64; 3],
) -> std::io::Result<()> {
// Byte order: Little endian (1)
writer.write_all(&[WkbByteOrder::LittleEndian as u8])?;

// Geometry type: wkbMultiPolygonZ (1006)
bytes.extend_from_slice(&1006_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
bytes.push(0x01);

// Geometry type: wkbPolygonZ (1003)
bytes.extend_from_slice(&1003_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 {
let x = f64::to_le_bytes(coord[1]); // FIXME: lon,lat order
bytes.extend_from_slice(&x);
let y = f64::to_le_bytes(coord[0]); // FIXME: lon,lat order
bytes.extend_from_slice(&y);
let z = f64::to_le_bytes(coord[2]);
bytes.extend_from_slice(&z);
}
}
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_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() {
Expand All @@ -158,7 +213,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);

Expand Down
13 changes: 10 additions & 3 deletions nusamai/src/sink/gpkg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,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 {}
Expand Down Expand Up @@ -71,11 +71,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(());
};
Expand Down

0 comments on commit d34fee7

Please sign in to comment.