From 4eafe35cfd7d7e6ec2968146393458c541c4e012 Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Mon, 8 Jan 2024 10:37:59 +0900 Subject: [PATCH] =?UTF-8?q?Transformer=E3=81=A7=E3=81=AE=E4=BB=AE=E3=81=AE?= =?UTF-8?q?Swap=20X-Y=E3=80=81GeoJSON,=20Gpkg=20=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=83=86=E3=82=A3=E3=83=AA=E3=83=86=E3=82=A3=E3=81=AE=E3=83=AA?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= =?UTF-8?q?=20(#153)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 変更内容 ### 仮のTransformerで「とりあえず」の変換処理を行う - x-y 入れかえ処理を入れておく - これにともない GeoJSON, GeoPackage ドライバ内での x-y 入れかえは除去する - WGS 84 にしておく処理も追加 #155 ### Gpkg, GeoJSON 周りのリファクタリング - `nusamai-geojson` に "indexed" でないふつうのジオメトリをGeoJSON Valueに変換する関数を追加(tiling2d sink で使う)。 - #154 もこのPRにマージ - Gpkg のバイナリジオメトリの構築で、 `Vec` でなく `std::io::Write` trait を使う(`Vec`はWrite)。一時的なVecの生成も除去。 --- Closes: #125 Closes: #76 --- nusamai-citygml/Cargo.toml | 1 + nusamai-citygml/src/geometry.rs | 23 ++- nusamai-geojson/src/conversion.rs | 208 ++++++++++++++++++++++----- nusamai-gpkg/src/geometry.rs | 224 ++++++++++++++---------------- nusamai-projection/src/crs.rs | 7 + nusamai-projection/src/lib.rs | 1 + nusamai-projection/src/vshift.rs | 4 +- nusamai/Cargo.toml | 1 + nusamai/src/main.rs | 4 +- nusamai/src/sink/geojson/mod.rs | 14 +- nusamai/src/sink/gpkg/mod.rs | 14 +- nusamai/src/sink/tiling2d/mod.rs | 23 +-- nusamai/src/transform/mod.rs | 33 ++++- nusamai/tests/sink.rs | 4 +- 14 files changed, 356 insertions(+), 205 deletions(-) create mode 100644 nusamai-projection/src/crs.rs diff --git a/nusamai-citygml/Cargo.toml b/nusamai-citygml/Cargo.toml index dcd6d8c28..3623db1d7 100644 --- a/nusamai-citygml/Cargo.toml +++ b/nusamai-citygml/Cargo.toml @@ -12,6 +12,7 @@ chrono = { version = "0.4.31", features = ["serde"], default-features = false } indexmap = { version = "2.1", features = ["serde"] } macros = { path = "./macros" } nusamai-geometry = { path = "../nusamai-geometry", features = ["serde"]} +nusamai-projection = { path = "../nusamai-projection"} quick-xml = "0.31" serde = { version = "1.0", features = ["derive"], optional = true } serde_json = { version = "1.0.108", optional = true } diff --git a/nusamai-citygml/src/geometry.rs b/nusamai-citygml/src/geometry.rs index 8f6689922..d5fa568af 100644 --- a/nusamai-citygml/src/geometry.rs +++ b/nusamai-citygml/src/geometry.rs @@ -1,4 +1,5 @@ use nusamai_geometry::{MultiLineString, MultiPoint, MultiPolygon}; +use nusamai_projection::crs::*; #[derive(Debug, Clone, Copy)] pub enum GeometryParseType { @@ -35,28 +36,24 @@ pub struct GeometryRefEntry { pub type GeometryRef = Vec; -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[derive(Debug, Default)] -pub enum CRS { - #[default] - WGS84, - JGD2011, -} - -/// Geometries in a toplevel city object and its children. +/// Geometries in a city object and all its children. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Debug, Default)] pub struct GeometryStore { - pub crs: CRS, + /// EPSG code of the Coordinate Reference System (CRS) + pub epsg: EPSGCode, + /// Shared vertex buffer for all geometries pub vertices: Vec<[f64; 3]>, + /// All polygons, referenced by `GeometryRef` pub multipolygon: MultiPolygon<'static, 1, u32>, + /// All line-strings of , referenced by `GeometryRef` pub multilinestring: MultiLineString<'static, 1, u32>, + /// All points, referenced by `GeometryRef` pub multipoint: MultiPoint<'static, 1, u32>, } -/// Store for collecting vertices and polygons from GML. #[derive(Default)] -pub struct GeometryCollector { +pub(crate) struct GeometryCollector { pub vertices: indexmap::IndexSet<[u64; 3]>, pub multipolygon: MultiPolygon<'static, 1, u32>, pub multilinestring: MultiLineString<'static, 1, u32>, @@ -94,7 +91,7 @@ impl GeometryCollector { ]); } GeometryStore { - crs: CRS::JGD2011, + epsg: EPSG_JGD2011_GEOGRAPHIC_3D, vertices, multipolygon: self.multipolygon, multilinestring: self.multilinestring, diff --git a/nusamai-geojson/src/conversion.rs b/nusamai-geojson/src/conversion.rs index fa9afa7a6..137495968 100644 --- a/nusamai-geojson/src/conversion.rs +++ b/nusamai-geojson/src/conversion.rs @@ -1,53 +1,91 @@ -use nusamai_geometry::{MultiLineString, MultiPoint, MultiPolygon, Polygon}; +use nusamai_geometry::{CoordNum, MultiLineString, MultiPoint, MultiPolygon}; -/// Create a GeoJSON geometry from nusamai_citygml::CityObject's `multipolygon` geometry -pub fn multipolygon_to_geojson_geometry( +/// Create a GeoJSON MultiPolygon from `nusamai_geometry::MultiPolygon`. +pub fn multipolygon_to_geometry(mpoly: &MultiPolygon<3>) -> geojson::Geometry { + multipolygon_to_geometry_with_mapping(mpoly, |c| c.to_vec()) +} + +/// 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 { + 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 ring_list: Vec = mpoly + let coords: Vec = mpoly .iter() - .map(|poly| 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(ring_list).into() + geojson::Value::MultiPolygon(coords).into() } -fn polygon_to_rings(vertices: &[[f64; 3]], poly: &Polygon<1, u32>) -> geojson::PolygonType { - poly.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 nusamai_citygml::CityObject's `multilinestring` geometry -pub fn multilinestring_to_geojson_geometry( +/// Create a GeoJSON MultiLineString from vertices and indices. +pub fn indexed_multilinestring_to_geometry( vertices: &[[f64; 3]], - mls: &MultiLineString<1, u32>, + mls_idx: &MultiLineString<1, u32>, ) -> geojson::Geometry { - let mls_coords: Vec = mls + 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| { - ls.iter() - .map(|idx| vertices[idx[0] as usize].to_vec()) // Get the actual coord values + .map(|ls_idx| { + ls_idx + .iter() + .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 nusamai_citygml::CityObject's `multipoint` geometry -pub fn multipoint_to_geojson_geometry( +/// 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: Vec = 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)] @@ -56,6 +94,64 @@ mod tests { #[test] fn test_multipolygon() { + let mut mpoly = MultiPolygon::<3>::new(); + // 1st polygon + mpoly.add_exterior([ + [0., 0., 0.], + [0., 10., 0.], + [10., 10., 0.], + [10., 0., 0.], + [0., 0., 0.], // closed + ]); + // polygon + mpoly.add_exterior([ + [10., 10., 0.], + [10., 20., 0.], + [20., 20., 0.], + [20., 10., 0.], // not closed + ]); + mpoly.add_interior([ + [15., 15., 0.], + [18., 10., 0.], + [18., 18., 0.], + [15., 18., 0.], + ]); + let geom = multipolygon_to_geometry(&mpoly); + let geojson::Value::MultiPolygon(mpoly) = geom.value else { + panic!("The result is not a GeoJSON MultiPolygon"); + }; + assert_eq!( + mpoly, + vec![ + vec![vec![ + vec![0., 0., 0.], + vec![0., 10., 0.], + vec![10., 10., 0.], + vec![10., 0., 0.], + vec![0., 0., 0.], + ]], + vec![ + vec![ + vec![10., 10., 0.], + vec![10., 20., 0.], + vec![20., 20., 0.], + vec![20., 10., 0.], + vec![10., 10., 0.], + ], + vec![ + vec![15., 15., 0.], + vec![18., 10., 0.], + vec![18., 18., 0.], + vec![15., 18., 0.], + vec![15., 15., 0.], + ], + ], + ], + ); + } + + #[test] + fn test_indexed_multipolygon() { let vertices: Vec<[f64; 3]> = vec![ // 1st polygon, exterior (vertex 0~3) [0., 0., 111.], @@ -100,7 +196,7 @@ mod tests { // 3rd polygon mpoly.add_exterior([[20], [21], [22], [23], [20]]); - let geojson_geometry = multipolygon_to_geojson_geometry(&vertices, &mpoly); + let geojson_geometry = indexed_multipolygon_to_geometry(&vertices, &mpoly); assert!(geojson_geometry.bbox.is_none()); assert!(geojson_geometry.foreign_members.is_none()); @@ -192,6 +288,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![ // 1st linestring [0., 0., 111.], @@ -210,7 +333,7 @@ mod tests { mls.add_linestring([[2], [3]]); mls.add_linestring([[4], [5], [6]]); - let geojson_geometry = multilinestring_to_geojson_geometry(&vertices, &mls); + let geojson_geometry = indexed_multilinestring_to_geometry(&vertices, &mls); assert!(geojson_geometry.bbox.is_none()); assert!(geojson_geometry.foreign_members.is_none()); @@ -244,13 +367,34 @@ 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.]]; let mut mpoint = MultiPoint::<1, u32>::new(); mpoint.push(&[0]); mpoint.push(&[1]); mpoint.push(&[2]); - let geojson_geometry = multipoint_to_geojson_geometry(&vertices, &mpoint); + let geojson_geometry = indexed_multipoint_to_geometry(&vertices, &mpoint); assert!(geojson_geometry.bbox.is_none()); assert!(geojson_geometry.foreign_members.is_none()); diff --git a/nusamai-gpkg/src/geometry.rs b/nusamai-gpkg/src/geometry.rs index 36df02422..08444b43c 100644 --- a/nusamai-gpkg/src/geometry.rs +++ b/nusamai-gpkg/src/geometry.rs @@ -2,142 +2,123 @@ //! //! cf. https://www.geopackage.org/spec130/#gpb_format -use nusamai_geometry::{MultiPolygon, Polygon}; - -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 +use nusamai_geometry::{CoordNum, MultiPolygon, Polygon}; +use std::io::Write; + +#[repr(u8)] +pub enum WkbByteOrder { + // Big endian (XDR) + BigEndian = 0, + // Little endian (NDR) + LittleEndian = 1, } -fn polygon_to_rings(vertices: &[[f64; 3]], poly: &Polygon<1, u32>) -> Vec>> { - 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(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 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])?; - 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(); + // Geometry type: wkbPolygonZ (1003) + writer.write_all(&(WkbGeometryType::PolygonZ as u32).to_le_bytes())?; - rings + // 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(()) +} - // Byte order: Little endian - bytes.push(0x01); +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) + 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_multipolygon_to_bytes() { let vertices: Vec<[f64; 3]> = vec![ @@ -158,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); @@ -195,8 +177,8 @@ mod tests { assert_eq!(bytes[46..=53].to_vec(), &111_f64.to_le_bytes()); // 2nd point - assert_eq!(bytes[54..=61].to_vec(), &0_f64.to_le_bytes()); - assert_eq!(bytes[62..=69].to_vec(), &5_f64.to_le_bytes()); + assert_eq!(bytes[54..=61].to_vec(), &5_f64.to_le_bytes()); + assert_eq!(bytes[62..=69].to_vec(), &0_f64.to_le_bytes()); assert_eq!(bytes[70..=77].to_vec(), &111_f64.to_le_bytes()); // 3rd point @@ -205,8 +187,8 @@ mod tests { assert_eq!(bytes[94..=101].to_vec(), &111_f64.to_le_bytes()); // 4th point - assert_eq!(bytes[102..=109].to_vec(), &5_f64.to_le_bytes()); - assert_eq!(bytes[110..=117].to_vec(), &0_f64.to_le_bytes()); + assert_eq!(bytes[102..=109].to_vec(), &0_f64.to_le_bytes()); + assert_eq!(bytes[110..=117].to_vec(), &5_f64.to_le_bytes()); assert_eq!(bytes[118..=125].to_vec(), &111_f64.to_le_bytes()); // 5th point @@ -224,8 +206,8 @@ mod tests { assert_eq!(bytes[170..=177].to_vec(), &111_f64.to_le_bytes()); // 2nd point - assert_eq!(bytes[178..=185].to_vec(), &1_f64.to_le_bytes()); - assert_eq!(bytes[186..=193].to_vec(), &2_f64.to_le_bytes()); + assert_eq!(bytes[178..=185].to_vec(), &2_f64.to_le_bytes()); + assert_eq!(bytes[186..=193].to_vec(), &1_f64.to_le_bytes()); assert_eq!(bytes[194..=201].to_vec(), &111_f64.to_le_bytes()); // 3rd point @@ -234,8 +216,8 @@ mod tests { assert_eq!(bytes[218..=225].to_vec(), &111_f64.to_le_bytes()); // 4th point - assert_eq!(bytes[226..=233].to_vec(), &2_f64.to_le_bytes()); - assert_eq!(bytes[234..=241].to_vec(), &1_f64.to_le_bytes()); + assert_eq!(bytes[226..=233].to_vec(), &1_f64.to_le_bytes()); + assert_eq!(bytes[234..=241].to_vec(), &2_f64.to_le_bytes()); assert_eq!(bytes[242..=249].to_vec(), &111_f64.to_le_bytes()); // 5th point diff --git a/nusamai-projection/src/crs.rs b/nusamai-projection/src/crs.rs new file mode 100644 index 000000000..eb187c0a4 --- /dev/null +++ b/nusamai-projection/src/crs.rs @@ -0,0 +1,7 @@ +pub type EPSGCode = u16; + +pub const EPSG_WGS84_GEOGRAPHIC_2D: EPSGCode = 4326; +pub const EPSG_WGS84_GEOGRAPHIC_3D: EPSGCode = 4979; +pub const EPSG_WGS84_GEOCENTRIC: EPSGCode = 4978; +pub const EPSG_JGD2011_GEOGRAPHIC_2D: EPSGCode = 6668; +pub const EPSG_JGD2011_GEOGRAPHIC_3D: EPSGCode = 6697; diff --git a/nusamai-projection/src/lib.rs b/nusamai-projection/src/lib.rs index 95824ca11..186d2ed5b 100644 --- a/nusamai-projection/src/lib.rs +++ b/nusamai-projection/src/lib.rs @@ -1,4 +1,5 @@ pub mod cartesian; +pub mod crs; pub mod ellipsoid; pub mod error; pub mod etmerc; diff --git a/nusamai-projection/src/vshift.rs b/nusamai-projection/src/vshift.rs index fa98f6d85..1323f7589 100644 --- a/nusamai-projection/src/vshift.rs +++ b/nusamai-projection/src/vshift.rs @@ -7,7 +7,7 @@ pub struct JGD2011ToWGS84 { impl JGD2011ToWGS84 { /// Create a new instance with the embed geoid model data. - pub fn from_embed_model() -> Self { + pub fn from_embedded_model() -> Self { const EMBEDDED_MODEL: &[u8] = include_bytes!("../examples/data/gsigeo2011_ver2_2.bin.lz4"); let decompressed = &lz4_flex::decompress_size_prepended(EMBEDDED_MODEL).unwrap(); let mut reader = std::io::Cursor::new(decompressed); @@ -31,7 +31,7 @@ mod tests { #[test] fn fixtures() { let (lng_jgd, lat_jgd, elevation) = (138.2839817085188, 37.12378643088312, 0.); - let jgd_to_wgs = JGD2011ToWGS84::from_embed_model(); + let jgd_to_wgs = JGD2011ToWGS84::from_embedded_model(); let (lng_wgs, lat_wgs, ellips_height) = jgd_to_wgs.convert(lng_jgd, lat_jgd, elevation); assert!((ellips_height - 39.47387115961899).abs() < 1e-8); // (lng, lat) must not change. diff --git a/nusamai/Cargo.toml b/nusamai/Cargo.toml index 4f41b8f05..5b0c40dfe 100644 --- a/nusamai/Cargo.toml +++ b/nusamai/Cargo.toml @@ -17,6 +17,7 @@ bincode = "1.3.3" lz4_flex = "0.11.1" nusamai-geojson = { path = "../nusamai-geojson" } nusamai-geometry = { path = "../nusamai-geometry" } +nusamai-projection = { path = "../nusamai-projection" } geojson = "0.24.1" serde_json = "1.0.108" url = "2.5.0" diff --git a/nusamai/src/main.rs b/nusamai/src/main.rs index b1d0afd37..0b4000ea8 100644 --- a/nusamai/src/main.rs +++ b/nusamai/src/main.rs @@ -10,7 +10,7 @@ use nusamai::sink::{ use nusamai::sink::{DataSink, DataSinkProvider}; use nusamai::source::citygml::CityGMLSourceProvider; use nusamai::source::{DataSource, DataSourceProvider}; -use nusamai::transform::NoopTransformer; +use nusamai::transform::DummyTransformer; #[derive(clap::Parser)] #[command(author, version, about, long_about = None)] @@ -116,7 +116,7 @@ fn run( sink: Box, canceller: &mut Arc>, ) { - let transformer = Box::new(NoopTransformer {}); + let transformer = Box::::default(); // start the pipeline let (handle, watcher, inner_canceller) = nusamai::pipeline::run(source, transformer, sink); diff --git a/nusamai/src/sink/geojson/mod.rs b/nusamai/src/sink/geojson/mod.rs index 0890d1166..4c161c718 100644 --- a/nusamai/src/sink/geojson/mod.rs +++ b/nusamai/src/sink/geojson/mod.rs @@ -13,8 +13,8 @@ use crate::sink::{DataSink, DataSinkProvider, SinkInfo}; use nusamai_citygml::object::CityObject; use nusamai_geojson::conversion::{ - multilinestring_to_geojson_geometry, multipoint_to_geojson_geometry, - multipolygon_to_geojson_geometry, + indexed_multilinestring_to_geometry, indexed_multipoint_to_geometry, + indexed_multipolygon_to_geometry, }; pub struct GeoJsonSinkProvider {} @@ -51,7 +51,6 @@ impl DataSinkProvider for GeoJsonSinkProvider { } } -#[derive(Default)] pub struct GeoJsonSink { output_path: PathBuf, } @@ -133,7 +132,7 @@ pub fn toplevel_cityobj_to_geojson_features(obj: &CityObject) -> Vec Vec Vec::new(); mpoly.add_exterior([[0], [1], [2], [3], [0]]); let geometries = nusamai_citygml::GeometryStore { - crs: nusamai_citygml::CRS::WGS84, + epsg: EPSG_JGD2011_GEOGRAPHIC_3D, vertices, multipolygon: mpoly, multilinestring: Default::default(), 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 21757ae17..0dd47dec9 100644 --- a/nusamai/src/sink/tiling2d/mod.rs +++ b/nusamai/src/sink/tiling2d/mod.rs @@ -12,6 +12,7 @@ use crate::pipeline::{Feedback, Receiver}; use crate::sink::{DataSink, DataSinkProvider, SinkInfo}; use nusamai_citygml::object::CityObject; +use nusamai_geojson::conversion::multipolygon_to_geometry; use nusamai_geometry::{MultiPolygon3, Polygon3}; pub struct Tiling2DSinkProvider {} @@ -48,7 +49,6 @@ impl DataSinkProvider for Tiling2DSinkProvider { } } -#[derive(Default)] pub struct Tiling2DSink { output_path: PathBuf, } @@ -284,27 +284,14 @@ pub fn toplevel_cityobj_to_geojson_features(obj: &CityObject) -> Vec>() - }) - .collect(); - - let mpoly_geojson_feat = geojson::Feature { + let geometry = multipolygon_to_geometry(&new_mpoly); + geojson_features.push(geojson::Feature { bbox: None, - geometry: Some(geojson::Geometry { - bbox: None, - value: geojson::Value::MultiPolygon(mpoly), - foreign_members: None, - }), + geometry: Some(geometry), id: None, properties: properties.clone(), foreign_members: None, - }; - geojson_features.push(mpoly_geojson_feat); + }); } // NOTE: Not supported (yet) diff --git a/nusamai/src/transform/mod.rs b/nusamai/src/transform/mod.rs index 2e4a32ed0..98966995f 100644 --- a/nusamai/src/transform/mod.rs +++ b/nusamai/src/transform/mod.rs @@ -1,15 +1,40 @@ use crate::pipeline::{Feedback, Parcel, Sender, TransformError, Transformer}; +use nusamai_projection::crs::*; +use nusamai_projection::vshift::JGD2011ToWGS84; -pub struct NoopTransformer {} +pub struct DummyTransformer { + jgd2wgs: JGD2011ToWGS84, +} + +impl Default for DummyTransformer { + fn default() -> Self { + Self { + jgd2wgs: JGD2011ToWGS84::from_embedded_model(), + } + } +} -impl Transformer for NoopTransformer { +impl Transformer for DummyTransformer { fn transform( &self, - parcel: Parcel, + mut parcel: Parcel, downstream: &Sender, feedback: &Feedback, ) -> Result<(), TransformError> { - // no-op + // 仮実装 + parcel.cityobj.geometries.vertices.iter_mut().for_each(|v| { + // Swap x and y (lat, lng -> lng, lat) + let (lng, lat, height) = (v[1], v[0], v[2]); + + // JGD2011 to WGS 84 (elevation to ellipsoidal height) + (v[0], v[1], v[2]) = self.jgd2wgs.convert(lng, lat, height); + }); + + // Ensure that the original CRS is JGD2011 and the new CRS is WGS 84 + assert_eq!(parcel.cityobj.geometries.epsg, EPSG_JGD2011_GEOGRAPHIC_3D); + parcel.cityobj.geometries.epsg = EPSG_WGS84_GEOGRAPHIC_3D; + + // Send to the next stage if downstream.send(parcel).is_err() { feedback.cancel(); }; diff --git a/nusamai/tests/sink.rs b/nusamai/tests/sink.rs index b4190c3d9..3d36afa43 100644 --- a/nusamai/tests/sink.rs +++ b/nusamai/tests/sink.rs @@ -1,7 +1,7 @@ use nusamai::sink::DataSinkProvider; use nusamai::source::citygml::CityGMLSourceProvider; use nusamai::source::DataSourceProvider; -use nusamai::transform::NoopTransformer; +use nusamai::transform::DummyTransformer; use nusamai::sink; @@ -16,7 +16,7 @@ pub(crate) fn simple_run_sink(sink_provider: S, output: Opt let source = source_provider.create(&source_provider.parameters()); - let transformer = Box::new(NoopTransformer {}); + let transformer = Box::::default(); assert!(!sink_provider.info().name.is_empty()); let mut sink_params = sink_provider.parameters();