From 292d00e438003bf8e2d52c4324c0368961026fec Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Sat, 6 Jan 2024 10:49:36 +0900 Subject: [PATCH 1/6] pipeline: monor refactoring and tests --- Cargo.toml | 3 + nusamai-gpkg/Cargo.toml | 1 + nusamai-gpkg/src/handler.rs | 54 ++++++++---------- nusamai/src/pipeline/feedback.rs | 6 ++ nusamai/src/pipeline/runner.rs | 4 +- nusamai/src/sink/gpkg/mod.rs | 18 ++++-- nusamai/src/sink/{noop.rs => noop/mod.rs} | 0 nusamai/src/sink/{serde.rs => serde/mod.rs} | 0 nusamai/src/source/citygml.rs | 14 +++-- nusamai/src/transform/mod.rs | 8 ++- nusamai/tests/mod.rs | 1 + nusamai/tests/noop_sink.rs | 26 --------- nusamai/tests/pipeline.rs | 4 +- nusamai/tests/serde_sink.rs | 30 ---------- nusamai/tests/sink.rs | 61 +++++++++++++++++++++ 15 files changed, 128 insertions(+), 102 deletions(-) rename nusamai/src/sink/{noop.rs => noop/mod.rs} (100%) rename nusamai/src/sink/{serde.rs => serde/mod.rs} (100%) create mode 100644 nusamai/tests/mod.rs delete mode 100644 nusamai/tests/noop_sink.rs delete mode 100644 nusamai/tests/serde_sink.rs create mode 100644 nusamai/tests/sink.rs diff --git a/Cargo.toml b/Cargo.toml index 35c51b80c..08a44c98e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,6 @@ members = [ "nusamai", ] resolver = "2" + +[profile.release] +lto = true \ No newline at end of file diff --git a/nusamai-gpkg/Cargo.toml b/nusamai-gpkg/Cargo.toml index 98d256a55..4f0f2c104 100644 --- a/nusamai-gpkg/Cargo.toml +++ b/nusamai-gpkg/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" sqlx = { version = "0.7.3", features = ["sqlite", "runtime-tokio"] } nusamai-geometry = { path = "../nusamai-geometry" } thiserror = "1.0.51" +url = "2.5.0" [dev-dependencies] tokio = { version = "1.35.1", features = ["full"] } diff --git a/nusamai-gpkg/src/handler.rs b/nusamai-gpkg/src/handler.rs index 29c1cab99..d636f3c13 100644 --- a/nusamai-gpkg/src/handler.rs +++ b/nusamai-gpkg/src/handler.rs @@ -1,9 +1,9 @@ -use sqlx::sqlite::*; +use sqlx::{sqlite::*, ConnectOptions}; use sqlx::{Acquire, Row}; -use sqlx::{Pool, Sqlite, SqlitePool}; +use sqlx::{Pool, Sqlite}; use std::path::Path; -use std::str::FromStr; use thiserror::Error; +use url::Url; pub struct GpkgHandler { pool: Pool, @@ -18,20 +18,22 @@ pub enum GpkgError { } impl GpkgHandler { - /// Create and initialize new GeoPackage database - pub async fn init(path: &str) -> Result { - if Path::new(path).exists() { - return Err(GpkgError::DatabaseExists(path.to_string())); + /// Create and initialize new GeoPackage database at the specified path + pub async fn from_path(path: &Path) -> Result { + if path.exists() { + return Err(GpkgError::DatabaseExists(format!("{:?}", path))); } + let url = Url::parse(&format!("sqlite://{}", path.to_str().unwrap())).unwrap(); + Self::from_url(&url).await + } - let db_url = format!("sqlite://{}", path); - - let conn_opts = SqliteConnectOptions::from_str(&db_url)? + /// Create and initialize new GeoPackage database at the specified URL + pub async fn from_url(url: &Url) -> Result { + let conn_opts = SqliteConnectOptions::from_url(url)? .create_if_missing(true) .synchronous(SqliteSynchronous::Normal) .journal_mode(SqliteJournalMode::Wal); - SqlitePoolOptions::new().connect_with(conn_opts).await?; - let pool = SqlitePool::connect(&db_url).await?; + let pool = SqlitePoolOptions::new().connect_with(conn_opts).await?; // Initialize the database with minimum GeoPackage schema let create_query = include_str!("sql/init.sql"); @@ -44,12 +46,12 @@ impl GpkgHandler { Ok(Self { pool }) } - /// Connect to an existing GeoPackage database - pub async fn connect(path: &str) -> Result { - let db_url = format!("sqlite://{}", path); - let pool = SqlitePool::connect(&db_url).await?; - Ok(Self { pool }) - } + ///// Connect to an existing GeoPackage database + //pub async fn connect(path: &str) -> Result { + // let db_url = format!("sqlite://{}", path); + // let pool = SqlitePool::connect(&db_url).await?; + // Ok(Self { pool }) + //} pub async fn application_id(&self) -> u32 { let result = sqlx::query("PRAGMA application_id;") @@ -94,17 +96,6 @@ impl GpkgHandler { } } -pub async fn insert_feature(pool: &Pool, bytes: &[u8]) { - sqlx::query("INSERT INTO mpoly3d (geometry) VALUES (?)") - .bind(bytes) - .execute(pool) - .await - .unwrap(); - - // TODO: MultiLineString - // TODO: MultiPoint -} - pub struct GpkgTransaction<'c> { tx: sqlx::Transaction<'c, Sqlite>, } @@ -141,8 +132,9 @@ mod tests { #[tokio::test] async fn test_init_connect() { - let handler = GpkgHandler::init("sqlite::memory:").await.unwrap(); - let _handler2 = GpkgHandler::connect("sqlite::memory:").await.unwrap(); + let handler = GpkgHandler::from_url(&Url::parse("sqlite::memory:").unwrap()) + .await + .unwrap(); let application_id = handler.application_id().await; assert_eq!(application_id, 1196444487); diff --git a/nusamai/src/pipeline/feedback.rs b/nusamai/src/pipeline/feedback.rs index 8e18cf01d..0d24059b2 100644 --- a/nusamai/src/pipeline/feedback.rs +++ b/nusamai/src/pipeline/feedback.rs @@ -27,6 +27,12 @@ impl Feedback { self.cancelled.load(Ordering::Relaxed) } + /// Request the pipeline to be cancelled + #[inline] + pub fn cancel(&self) { + self.cancelled.store(true, Ordering::Relaxed) + } + /// Send a message to the feedback channel #[inline] pub fn feedback(&self, msg: FeedbackMessage) { diff --git a/nusamai/src/pipeline/runner.rs b/nusamai/src/pipeline/runner.rs index 366dc08f8..e3621aaf1 100644 --- a/nusamai/src/pipeline/runner.rs +++ b/nusamai/src/pipeline/runner.rs @@ -79,7 +79,9 @@ impl PipelineHandle { // Wait for the pipeline to terminate pub fn join(self) { self.thread_handles.into_iter().for_each(|handle| { - handle.join().unwrap(); + if let Err(err) = handle.join() { + eprintln!("Error: {:#?}", err); + } }); } } diff --git a/nusamai/src/sink/gpkg/mod.rs b/nusamai/src/sink/gpkg/mod.rs index 70f43b442..e03fb4826 100644 --- a/nusamai/src/sink/gpkg/mod.rs +++ b/nusamai/src/sink/gpkg/mod.rs @@ -2,6 +2,8 @@ use std::path::PathBuf; +use url::Url; + use rayon::prelude::*; use crate::parameters::Parameters; @@ -39,10 +41,10 @@ impl DataSinkProvider for GpkgSinkProvider { } fn create(&self, params: &Parameters) -> Box { - let output_path = get_parameter_value!(params, "@output", FileSystemPath); + let output_path = get_parameter_value!(params, "@output", FileSystemPath).unwrap(); Box::::new(GpkgSink { - output_path: output_path.unwrap().into(), + output_path: output_path.clone(), }) } } @@ -54,9 +56,17 @@ pub struct GpkgSink { impl GpkgSink { pub async fn run_async(&mut self, upstream: Receiver, feedback: &mut Feedback) { - let mut handler = GpkgHandler::init(self.output_path.to_str().unwrap()) + let mut handler = if self.output_path.to_string_lossy().starts_with("sqlite:") { + GpkgHandler::from_url(&Url::parse(self.output_path.to_str().unwrap()).unwrap()) + .await + .unwrap() + } else { + GpkgHandler::from_url( + &Url::parse(&format!("sqlite://{}", self.output_path.to_str().unwrap())).unwrap(), + ) .await - .unwrap(); + .unwrap() + }; let (sender, mut receiver) = tokio::sync::mpsc::channel(100); diff --git a/nusamai/src/sink/noop.rs b/nusamai/src/sink/noop/mod.rs similarity index 100% rename from nusamai/src/sink/noop.rs rename to nusamai/src/sink/noop/mod.rs diff --git a/nusamai/src/sink/serde.rs b/nusamai/src/sink/serde/mod.rs similarity index 100% rename from nusamai/src/sink/serde.rs rename to nusamai/src/sink/serde/mod.rs diff --git a/nusamai/src/source/citygml.rs b/nusamai/src/source/citygml.rs index a6e7aa3a6..e8211ebe5 100644 --- a/nusamai/src/source/citygml.rs +++ b/nusamai/src/source/citygml.rs @@ -1,3 +1,5 @@ +//! CityGML (.gml) Source Provider + use std::fs; use std::io::BufRead; use std::path::Path; @@ -25,7 +27,7 @@ impl DataSourceProvider for CityGMLSourceProvider { fn info(&self) -> SourceInfo { SourceInfo { - name: "Dummy Source".to_string(), + name: "CityGML".to_string(), } } @@ -68,10 +70,10 @@ impl DataSource for CityGMLSource { fn toplevel_dispatcher( st: &mut SubTreeReader, - sink: &Sender, + downstream: &Sender, feedback: &Feedback, ) -> Result<(), ParseError> { - match st.parse_children(|st| { + let result = st.parse_children(|st| { if feedback.is_cancelled() { return Err(ParseError::Cancelled); } @@ -88,7 +90,8 @@ fn toplevel_dispatcher( if let Some(root) = cityobj.into_object() { let cityobj = CityObject { root, geometries }; - if sink.send(Parcel { cityobj }).is_err() { + if downstream.send(Parcel { cityobj }).is_err() { + feedback.cancel(); return Ok(()); } } @@ -103,7 +106,8 @@ fn toplevel_dispatcher( String::from_utf8_lossy(other) ))), } - }) { + }); + match result { Ok(_) => Ok(()), Err(e) => { println!("Err: {:?}", e); diff --git a/nusamai/src/transform/mod.rs b/nusamai/src/transform/mod.rs index 44df3c239..2e4a32ed0 100644 --- a/nusamai/src/transform/mod.rs +++ b/nusamai/src/transform/mod.rs @@ -6,11 +6,13 @@ impl Transformer for NoopTransformer { fn transform( &self, parcel: Parcel, - sender: &Sender, - _feedback: &Feedback, + downstream: &Sender, + feedback: &Feedback, ) -> Result<(), TransformError> { // no-op - sender.send(parcel)?; + if downstream.send(parcel).is_err() { + feedback.cancel(); + }; Ok(()) } } diff --git a/nusamai/tests/mod.rs b/nusamai/tests/mod.rs new file mode 100644 index 000000000..4f919315d --- /dev/null +++ b/nusamai/tests/mod.rs @@ -0,0 +1 @@ +mod sink; diff --git a/nusamai/tests/noop_sink.rs b/nusamai/tests/noop_sink.rs deleted file mode 100644 index 69825d793..000000000 --- a/nusamai/tests/noop_sink.rs +++ /dev/null @@ -1,26 +0,0 @@ -use nusamai::sink::noop::NoopSinkProvider; -use nusamai::sink::DataSinkProvider; -use nusamai::source::citygml::CityGMLSourceProvider; -use nusamai::source::DataSourceProvider; -use nusamai::transform::NoopTransformer; - -#[test] -fn run_noop_sink() { - let source_provider: Box = Box::new(CityGMLSourceProvider { - filenames: vec![ - "../nusamai-plateau/tests/data/kawasaki-shi/udx/frn/53391597_frn_6697_op.gml" - .to_string(), - ], - }); - let sink_provider: Box = Box::new(NoopSinkProvider {}); - - let source = source_provider.create(&source_provider.parameters()); - let transformer = Box::new(NoopTransformer {}); - let sink = sink_provider.create(&sink_provider.parameters()); - - // start the pipeline - let (handle, _watcher, _canceller) = nusamai::pipeline::run(source, transformer, sink); - - // wait for the pipeline to finish - handle.join(); -} diff --git a/nusamai/tests/pipeline.rs b/nusamai/tests/pipeline.rs index 57f8beaa3..a804f4ea9 100644 --- a/nusamai/tests/pipeline.rs +++ b/nusamai/tests/pipeline.rs @@ -57,11 +57,11 @@ impl Transformer for NoopTransformer { fn transform( &self, parcel: Parcel, - sender: &Sender, + downstream: &Sender, _feedback: &feedback::Feedback, ) -> Result<(), TransformError> { // no-op - sender.send(parcel)?; + downstream.send(parcel)?; Ok(()) } } diff --git a/nusamai/tests/serde_sink.rs b/nusamai/tests/serde_sink.rs deleted file mode 100644 index b58da97ee..000000000 --- a/nusamai/tests/serde_sink.rs +++ /dev/null @@ -1,30 +0,0 @@ -use nusamai::sink::serde::SerdeSinkProvider; -use nusamai::sink::DataSinkProvider; -use nusamai::source::citygml::CityGMLSourceProvider; -use nusamai::source::DataSourceProvider; -use nusamai::transform::NoopTransformer; - -#[test] -fn run_serde_sink() { - let source_provider: Box = Box::new(CityGMLSourceProvider { - filenames: vec![ - "../nusamai-plateau/tests/data/kawasaki-shi/udx/frn/53391597_frn_6697_op.gml" - .to_string(), - ], - }); - let sink_provider: Box = Box::new(SerdeSinkProvider {}); - - let source = source_provider.create(&source_provider.parameters()); - let transformer = Box::new(NoopTransformer {}); - let mut sink_params = sink_provider.parameters(); - sink_params - .update_values_with_str(std::iter::once(&("@output".into(), "/dev/null".into()))) - .unwrap(); - let sink = sink_provider.create(&sink_params); - - // start the pipeline - let (handle, _watcher, _canceller) = nusamai::pipeline::run(source, transformer, sink); - - // wait for the pipeline to finish - handle.join(); -} diff --git a/nusamai/tests/sink.rs b/nusamai/tests/sink.rs new file mode 100644 index 000000000..b4190c3d9 --- /dev/null +++ b/nusamai/tests/sink.rs @@ -0,0 +1,61 @@ +use nusamai::sink::DataSinkProvider; +use nusamai::source::citygml::CityGMLSourceProvider; +use nusamai::source::DataSourceProvider; +use nusamai::transform::NoopTransformer; + +use nusamai::sink; + +pub(crate) fn simple_run_sink(sink_provider: S, output: Option<&str>) { + let source_provider: Box = Box::new(CityGMLSourceProvider { + filenames: vec![ + "../nusamai-plateau/tests/data/kawasaki-shi/udx/frn/53391597_frn_6697_op.gml" + .to_string(), + ], + }); + assert_eq!(source_provider.info().name, "CityGML"); + + let source = source_provider.create(&source_provider.parameters()); + + let transformer = Box::new(NoopTransformer {}); + + assert!(!sink_provider.info().name.is_empty()); + let mut sink_params = sink_provider.parameters(); + if let Some(output) = output { + sink_params + .update_values_with_str(std::iter::once(&("@output".into(), output.into()))) + .unwrap(); + } + sink_params.validate().unwrap(); + let sink = sink_provider.create(&sink_params); + + let (handle, _watcher, canceller) = nusamai::pipeline::run(source, transformer, sink); + handle.join(); + + // should not be cancelled + assert!(!canceller.is_cancelled()); +} + +#[test] +fn run_serde_sink() { + simple_run_sink(sink::serde::SerdeSinkProvider {}, "/dev/null".into()); +} + +#[test] +fn run_noop_sink() { + simple_run_sink(sink::noop::NoopSinkProvider {}, None); +} + +#[test] +fn run_geojson_sink() { + simple_run_sink(sink::geojson::GeoJsonSinkProvider {}, "/dev/null".into()); +} + +#[test] +fn run_gpkg_sink() { + simple_run_sink(sink::gpkg::GpkgSinkProvider {}, "sqlite::memory:".into()); +} + +#[test] +fn run_tiling2d_sink() { + simple_run_sink(sink::tiling2d::Tiling2DSinkProvider {}, "/dev/null".into()); +} From 63874e9f56688b241fb7cd746078c1f7a50a55d9 Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Sat, 6 Jan 2024 17:09:56 +0900 Subject: [PATCH 2/6] disable lto --- Cargo.toml | 3 --- nusamai-citygml/src/geometry.rs | 16 +++++++++++++--- nusamai-citygml/src/object.rs | 2 +- nusamai-citygml/src/parser.rs | 5 +++-- nusamai-plateau/examples/parse.rs | 2 +- nusamai-plateau/tests/test_cityfurniture.rs | 6 ++++-- nusamai-plateau/tests/test_relief.rs | 4 ++-- nusamai-plateau/tests/test_transportations.rs | 4 ++-- nusamai/src/sink/geojson/mod.rs | 3 ++- 9 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 08a44c98e..35c51b80c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,3 @@ members = [ "nusamai", ] resolver = "2" - -[profile.release] -lto = true \ No newline at end of file diff --git a/nusamai-citygml/src/geometry.rs b/nusamai-citygml/src/geometry.rs index 36318c630..8f6689922 100644 --- a/nusamai-citygml/src/geometry.rs +++ b/nusamai-citygml/src/geometry.rs @@ -35,10 +35,19 @@ 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. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Debug, Default)] -pub struct Geometries { +pub struct GeometryStore { + pub crs: CRS, pub vertices: Vec<[f64; 3]>, pub multipolygon: MultiPolygon<'static, 1, u32>, pub multilinestring: MultiLineString<'static, 1, u32>, @@ -75,7 +84,7 @@ impl GeometryCollector { })); } - pub fn into_geometries(self) -> Geometries { + pub fn into_geometries(self) -> GeometryStore { let mut vertices = Vec::with_capacity(self.vertices.len()); for vbits in &self.vertices { vertices.push([ @@ -84,7 +93,8 @@ impl GeometryCollector { f64::from_bits(vbits[2]), ]); } - Geometries { + GeometryStore { + crs: CRS::JGD2011, vertices, multipolygon: self.multipolygon, multilinestring: self.multilinestring, diff --git a/nusamai-citygml/src/object.rs b/nusamai-citygml/src/object.rs index 6561e532c..2dae152f8 100644 --- a/nusamai-citygml/src/object.rs +++ b/nusamai-citygml/src/object.rs @@ -11,7 +11,7 @@ pub type Map = indexmap::IndexMap; #[derive(Debug, Deserialize, Serialize)] pub struct CityObject { pub root: Value, - pub geometries: geometry::Geometries, + pub geometries: geometry::GeometryStore, } #[derive(Debug, Serialize, Deserialize, PartialEq)] diff --git a/nusamai-citygml/src/parser.rs b/nusamai-citygml/src/parser.rs index 042788efb..41687dd0a 100644 --- a/nusamai-citygml/src/parser.rs +++ b/nusamai-citygml/src/parser.rs @@ -10,7 +10,8 @@ use url::Url; use crate::codelist::{self, CodeResolver}; use crate::geometry::{ - Geometries, GeometryCollector, GeometryParseType, GeometryRef, GeometryRefEntry, GeometryType, + GeometryCollector, GeometryParseType, GeometryRef, GeometryRefEntry, GeometryStore, + GeometryType, }; use crate::namespace::{wellknown_prefix_from_nsres, GML31_NS}; @@ -294,7 +295,7 @@ impl SubTreeReader<'_, '_, R> { &self.state.context } - pub fn collect_geometries(&mut self) -> Geometries { + pub fn collect_geometries(&mut self) -> GeometryStore { let collector = std::mem::take(&mut self.state.geometry_collector); collector.into_geometries() } diff --git a/nusamai-plateau/examples/parse.rs b/nusamai-plateau/examples/parse.rs index e78f772aa..7cc58917f 100644 --- a/nusamai-plateau/examples/parse.rs +++ b/nusamai-plateau/examples/parse.rs @@ -8,7 +8,7 @@ use nusamai_citygml::{object::Value, CityGMLElement, CityGMLReader, ParseError, #[derive(Debug, Deserialize, Serialize)] struct TopLevelCityObject { root: Value, - geometries: nusamai_citygml::Geometries, + geometries: nusamai_citygml::GeometryStore, } fn example_toplevel_dispatcher( diff --git a/nusamai-plateau/tests/test_cityfurniture.rs b/nusamai-plateau/tests/test_cityfurniture.rs index add2e7674..2579db103 100644 --- a/nusamai-plateau/tests/test_cityfurniture.rs +++ b/nusamai-plateau/tests/test_cityfurniture.rs @@ -3,14 +3,16 @@ use std::path::Path; use url::Url; -use nusamai_citygml::{CityGMLElement, CityGMLReader, Code, Geometries, ParseError, SubTreeReader}; +use nusamai_citygml::{ + CityGMLElement, CityGMLReader, Code, GeometryStore, ParseError, SubTreeReader, +}; use nusamai_plateau::models::cityfurniture::CityFurniture; use nusamai_plateau::models::TopLevelCityObject; #[derive(Default, Debug)] struct ParsedData { cityfurnitures: Vec, - geometries: Vec, + geometries: Vec, } fn toplevel_dispatcher(st: &mut SubTreeReader) -> Result { diff --git a/nusamai-plateau/tests/test_relief.rs b/nusamai-plateau/tests/test_relief.rs index b9db1ef4d..0d2129ac9 100644 --- a/nusamai-plateau/tests/test_relief.rs +++ b/nusamai-plateau/tests/test_relief.rs @@ -3,13 +3,13 @@ use std::path::Path; use url::Url; -use nusamai_citygml::{CityGMLElement, CityGMLReader, Geometries, ParseError, SubTreeReader}; +use nusamai_citygml::{CityGMLElement, CityGMLReader, GeometryStore, ParseError, SubTreeReader}; use nusamai_plateau::models::{relief, ReliefFeature, TopLevelCityObject}; #[derive(Default, Debug)] struct ParsedData { relief: Vec, - geometries: Vec, + geometries: Vec, } fn toplevel_dispatcher(st: &mut SubTreeReader) -> Result { diff --git a/nusamai-plateau/tests/test_transportations.rs b/nusamai-plateau/tests/test_transportations.rs index a5a5cbbce..12f1f988a 100644 --- a/nusamai-plateau/tests/test_transportations.rs +++ b/nusamai-plateau/tests/test_transportations.rs @@ -4,7 +4,7 @@ use std::path::Path; use url::Url; use nusamai_citygml::{ - CityGMLElement, CityGMLReader, Code, Geometries, Measure, ParseError, SubTreeReader, + CityGMLElement, CityGMLReader, Code, GeometryStore, Measure, ParseError, SubTreeReader, }; use nusamai_plateau::models::Road; use nusamai_plateau::models::TopLevelCityObject; @@ -12,7 +12,7 @@ use nusamai_plateau::models::TopLevelCityObject; #[derive(Default, Debug)] struct ParsedData { roads: Vec, - geometries: Vec, + geometries: Vec, } fn toplevel_dispatcher(st: &mut SubTreeReader) -> Result { diff --git a/nusamai/src/sink/geojson/mod.rs b/nusamai/src/sink/geojson/mod.rs index 4c3d8855f..0890d1166 100644 --- a/nusamai/src/sink/geojson/mod.rs +++ b/nusamai/src/sink/geojson/mod.rs @@ -195,7 +195,8 @@ mod tests { ]; let mut mpoly = MultiPolygon::<'_, 1, u32>::new(); mpoly.add_exterior([[0], [1], [2], [3], [0]]); - let geometries = nusamai_citygml::Geometries { + let geometries = nusamai_citygml::GeometryStore { + crs: nusamai_citygml::CRS::WGS84, vertices, multipolygon: mpoly, multilinestring: Default::default(), From 4eafe35cfd7d7e6ec2968146393458c541c4e012 Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Mon, 8 Jan 2024 10:37:59 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Transformer=E3=81=A7=E3=81=AE=E4=BB=AE?= =?UTF-8?q?=E3=81=AESwap=20X-Y=E3=80=81GeoJSON,=20Gpkg=20=E3=83=A6?= =?UTF-8?q?=E3=83=BC=E3=83=86=E3=82=A3=E3=83=AA=E3=83=86=E3=82=A3=E3=81=AE?= =?UTF-8?q?=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3?= =?UTF-8?q?=E3=82=B0=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(); From 84295253d1ce7e1aed3ea33e95ad3cbf797ffce7 Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Mon, 8 Jan 2024 17:43:59 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=E9=AB=98=E9=80=9F=E3=81=AAHasher=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E3=81=86=20(SipHash=E3=81=AE=E3=81=8B=E3=82=8F?= =?UTF-8?q?=E3=82=8A=E3=81=AB)=20(#156)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HashMap, IndexMap, etc. を多用する場面で、 Hasher を ahash に切り替える。 ### 背景 Rust の HashMap / HashSet はデフォルトで SipHash-1-3 (`std::hash::SipHash`) というハッシュ関数を使う。SipHashはHashDos攻撃への耐性が高いためにデフォルトのHasherとして採用されている。しかし高い HashDos 耐性が必要なく、hashを多用する場面ではより軽量なHasherに切り替えることができる(切り替えることがしばしば行われる)。 - [ahash](https://crates.io/crates/ahash) - それなりの HashDos 耐性があり、パフォーマンスもよい - [rust-lang/hashbrown](https://crates.io/crates/hashbrown) (Rustの現在のHashMap実装の元になった crate) のデフォルトのHasherは aHash なので、Hasherを切り替える代わりに、これを HashMap の置き換えとして使える。 - [fxhash](https://crates.io/crates/fxhash) - [rust-lang/rustc-hash](https://crates.io/crates/rustc-hash) - rustc で使われている Hasher --- nusamai-3dtiles/Cargo.toml | 2 +- nusamai-citygml/Cargo.toml | 3 ++- nusamai-citygml/macros/src/derive.rs | 4 ++-- nusamai-citygml/src/geometry.rs | 2 +- nusamai-citygml/src/object.rs | 6 +++--- nusamai-citygml/src/values.rs | 2 +- nusamai-geojson/Cargo.toml | 2 +- nusamai-gltf/Cargo.toml | 2 +- nusamai-plateau/Cargo.toml | 1 + nusamai-plateau/src/codelist/resolver.rs | 8 +++++--- nusamai-plateau/src/codelist/xml.rs | 4 ++-- nusamai/Cargo.toml | 2 +- 12 files changed, 21 insertions(+), 17 deletions(-) diff --git a/nusamai-3dtiles/Cargo.toml b/nusamai-3dtiles/Cargo.toml index 9e78f034c..a7e8805e0 100644 --- a/nusamai-3dtiles/Cargo.toml +++ b/nusamai-3dtiles/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] serde = { version = "1.0.192", features = ["derive"] } -serde_json = { version = "1.0.108", features = ["float_roundtrip"] } +serde_json = { version = "1.0.108", features = ["indexmap", "float_roundtrip"] } serde_repr = "0.1.17" nusamai-gltf = { path = "../nusamai-gltf" } diff --git a/nusamai-citygml/Cargo.toml b/nusamai-citygml/Cargo.toml index 3623db1d7..aa736f729 100644 --- a/nusamai-citygml/Cargo.toml +++ b/nusamai-citygml/Cargo.toml @@ -8,6 +8,7 @@ default = ["serde"] serde = ["dep:serde", "serde_json", "nusamai-geometry/serde"] [dependencies] +ahash = "0.8.7" chrono = { version = "0.4.31", features = ["serde"], default-features = false } indexmap = { version = "2.1", features = ["serde"] } macros = { path = "./macros" } @@ -15,6 +16,6 @@ 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 } +serde_json = { version = "1.0.108", features = ["indexmap"], optional = true } thiserror = "1.0" url = "2.5.0" diff --git a/nusamai-citygml/macros/src/derive.rs b/nusamai-citygml/macros/src/derive.rs index 7b9ce6c94..8e344e5a3 100644 --- a/nusamai-citygml/macros/src/derive.rs +++ b/nusamai-citygml/macros/src/derive.rs @@ -240,7 +240,7 @@ fn generate_citygml_impl_for_struct( typename: #typename.into(), id: #id_value, attributes: { - let mut attributes = ::nusamai_citygml::object::Map::new(); + let mut attributes = ::nusamai_citygml::object::Map::default(); #(#into_object_stmts)* attributes }, @@ -255,7 +255,7 @@ fn generate_citygml_impl_for_struct( ::nusamai_citygml::object::Data { typename: #typename.into(), attributes: { - let mut attributes = ::nusamai_citygml::object::Map::new(); + let mut attributes = ::nusamai_citygml::object::Map::default(); #(#into_object_stmts)* attributes }, diff --git a/nusamai-citygml/src/geometry.rs b/nusamai-citygml/src/geometry.rs index d5fa568af..16465fc0a 100644 --- a/nusamai-citygml/src/geometry.rs +++ b/nusamai-citygml/src/geometry.rs @@ -54,7 +54,7 @@ pub struct GeometryStore { #[derive(Default)] pub(crate) struct GeometryCollector { - pub vertices: indexmap::IndexSet<[u64; 3]>, + pub vertices: indexmap::IndexSet<[u64; 3], ahash::RandomState>, pub multipolygon: MultiPolygon<'static, 1, u32>, pub multilinestring: MultiLineString<'static, 1, u32>, pub multipoint: MultiPoint<'static, 1, u32>, diff --git a/nusamai-citygml/src/object.rs b/nusamai-citygml/src/object.rs index 2dae152f8..26351b132 100644 --- a/nusamai-citygml/src/object.rs +++ b/nusamai-citygml/src/object.rs @@ -6,7 +6,7 @@ use crate::Measure; use chrono::NaiveDate; use serde::{Deserialize, Serialize}; -pub type Map = indexmap::IndexMap; +pub type Map = indexmap::IndexMap; #[derive(Debug, Deserialize, Serialize)] pub struct CityObject { @@ -136,7 +136,7 @@ mod tests { let value = obj.to_attribute_json(); assert_eq!(value, json!(["test", 1])); - let mut attributes = Map::new(); + let mut attributes = Map::default(); attributes.insert("String".into(), Value::String("test".into())); attributes.insert("Integer".into(), Value::Integer(1)); let obj = Value::Feature(Feature { @@ -158,7 +158,7 @@ mod tests { } ); - let mut attributes = Map::new(); + let mut attributes = Map::default(); attributes.insert("String".into(), Value::String("test".into())); attributes.insert("Integer".into(), Value::Integer(1)); let obj = Value::Data(Data { diff --git a/nusamai-citygml/src/values.rs b/nusamai-citygml/src/values.rs index a530eabb8..f20c91d28 100644 --- a/nusamai-citygml/src/values.rs +++ b/nusamai-citygml/src/values.rs @@ -372,7 +372,7 @@ impl CityGMLElement for GenericAttribute { } fn into_object(self) -> Option { - let mut map = object::Map::new(); + let mut map = object::Map::default(); map.extend( self.string_attrs .into_iter() diff --git a/nusamai-geojson/Cargo.toml b/nusamai-geojson/Cargo.toml index 3220a6fb7..88492c1a5 100644 --- a/nusamai-geojson/Cargo.toml +++ b/nusamai-geojson/Cargo.toml @@ -8,4 +8,4 @@ edition = "2021" [dependencies] geojson = "0.24.1" nusamai-geometry = { path = "../nusamai-geometry" } -serde_json = "1.0.108" +serde_json = { version = "1.0.108", features = ["indexmap"] } diff --git a/nusamai-gltf/Cargo.toml b/nusamai-gltf/Cargo.toml index 22443a4b6..a8b114f95 100644 --- a/nusamai-gltf/Cargo.toml +++ b/nusamai-gltf/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] serde = { version = "1.0.192", features = ["derive"] } -serde_json = { version = "1.0.108", features = ["float_roundtrip"] } +serde_json = { version = "1.0.108", features = ["indexmap", "float_roundtrip"] } serde_repr = "0.1.17" [dev-dependencies] diff --git a/nusamai-plateau/Cargo.toml b/nusamai-plateau/Cargo.toml index 5bc35127d..d4b38783e 100644 --- a/nusamai-plateau/Cargo.toml +++ b/nusamai-plateau/Cargo.toml @@ -13,6 +13,7 @@ nusamai-citygml = { path = "../nusamai-citygml", features = ["serde"]} chrono = { version = "0.4.31", features = ["serde"], default-features = false } url = "2.5.0" stretto = "0.8.2" +hashbrown = "0.14.3" [dev-dependencies] zstd = { version = "0.13.0", features = ["zdict_builder"] } diff --git a/nusamai-plateau/src/codelist/resolver.rs b/nusamai-plateau/src/codelist/resolver.rs index f432a3dc6..b894cb46f 100644 --- a/nusamai-plateau/src/codelist/resolver.rs +++ b/nusamai-plateau/src/codelist/resolver.rs @@ -1,11 +1,13 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::path::PathBuf; use super::xml::{parse_dictionary, Definition}; -use nusamai_citygml::codelist::CodeResolver; -use nusamai_citygml::ParseError; +use hashbrown::HashMap; use stretto::Cache; use url::Url; +use nusamai_citygml::codelist::CodeResolver; +use nusamai_citygml::ParseError; + pub struct Resolver { cache: Cache>, } diff --git a/nusamai-plateau/src/codelist/xml.rs b/nusamai-plateau/src/codelist/xml.rs index 7044f4fec..f031dc717 100644 --- a/nusamai-plateau/src/codelist/xml.rs +++ b/nusamai-plateau/src/codelist/xml.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use hashbrown::HashMap; use std::io::BufRead; use quick_xml::events::Event; @@ -113,7 +113,7 @@ pub fn parse_dictionary( let mut depth = 0; let mut buf = Vec::new(); let mut buf2 = Vec::new(); - let mut definitions = HashMap::new(); + let mut definitions = HashMap::default(); loop { match reader.read_event_into(&mut buf) { diff --git a/nusamai/Cargo.toml b/nusamai/Cargo.toml index 5b0c40dfe..d25184dbb 100644 --- a/nusamai/Cargo.toml +++ b/nusamai/Cargo.toml @@ -19,7 +19,7 @@ nusamai-geojson = { path = "../nusamai-geojson" } nusamai-geometry = { path = "../nusamai-geometry" } nusamai-projection = { path = "../nusamai-projection" } geojson = "0.24.1" -serde_json = "1.0.108" +serde_json = { version = "1.0.108", features = ["indexmap"] } url = "2.5.0" nusamai-gpkg = { path = "../nusamai-gpkg" } tokio = { version = "1.35.1", features = ["full"] } From 0f7adb2550b0645bf1e928f3ba2051c95bdf49a6 Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Tue, 9 Jan 2024 01:14:54 +0900 Subject: [PATCH 5/6] =?UTF-8?q?refactor(geom):=20Iterator::chain=20?= =?UTF-8?q?=E3=81=AE=E4=BD=BF=E7=94=A8=E3=82=92=E5=8F=96=E3=82=8A=E9=99=A4?= =?UTF-8?q?=E3=81=8F=20(#157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Rust Performance Book によると、「hot iterators での Iterator::chain の使用は可能なら避けるとよい」とのことなので、除去しておく。 https://nnethercote.github.io/perf-book/iterators.html#chaining > [chain](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.chain) can be very convenient, but it can also be slower than a single iterator. It may be worth avoiding for hot iterators, if possible. --- nusamai-citygml/src/object.rs | 37 +++++++------- nusamai-geojson/src/conversion.rs | 2 +- nusamai-geometry/src/compact/linestring.rs | 4 +- .../src/compact/multi_linestring.rs | 4 +- nusamai-geometry/src/compact/multi_point.rs | 2 +- nusamai-geometry/src/compact/multi_polygon.rs | 2 +- nusamai-geometry/src/compact/polygon.rs | 51 +++++++++++++------ nusamai-gltf/examples/geometry_to_gltf.rs | 2 +- nusamai-gpkg/src/geometry.rs | 6 +-- nusamai-projection/src/jprect.rs | 2 +- nusamai/src/sink/geojson/mod.rs | 2 +- 11 files changed, 68 insertions(+), 46 deletions(-) diff --git a/nusamai-citygml/src/object.rs b/nusamai-citygml/src/object.rs index 26351b132..2d6883647 100644 --- a/nusamai-citygml/src/object.rs +++ b/nusamai-citygml/src/object.rs @@ -70,24 +70,25 @@ impl Value { // } } Array(a) => serde_json::Value::Array(a.iter().map(Value::to_attribute_json).collect()), - Feature(feat) => serde_json::Value::from_iter( - feat.attributes - .iter() - .map(|(k, v)| (k.clone(), v.to_attribute_json())) - .chain( - std::iter::once(("type".into(), feat.typename.clone().into())) - .chain(std::iter::once(("id".into(), feat.id.clone().into()))), - ), - ), - Data(feat) => serde_json::Value::from_iter( - feat.attributes - .iter() - .map(|(k, v)| (k.clone(), v.to_attribute_json())) - .chain(std::iter::once(( - "type".into(), - feat.typename.clone().into(), - ))), - ), + Feature(feat) => { + let mut m = serde_json::Map::from_iter( + feat.attributes + .iter() + .map(|(k, v)| (k.clone(), v.to_attribute_json())), + ); + m.insert("type".into(), feat.typename.clone().into()); + m.insert("id".into(), feat.id.clone().into()); + serde_json::Value::Object(m) + } + Data(feat) => { + let mut m = serde_json::Map::from_iter( + feat.attributes + .iter() + .map(|(k, v)| (k.clone(), v.to_attribute_json())), + ); + m.insert("type".into(), feat.typename.clone().into()); + serde_json::Value::Object(m) + } } } } diff --git a/nusamai-geojson/src/conversion.rs b/nusamai-geojson/src/conversion.rs index 137495968..f36ce2bc4 100644 --- a/nusamai-geojson/src/conversion.rs +++ b/nusamai-geojson/src/conversion.rs @@ -185,7 +185,7 @@ mod tests { [4., 3., 333.], ]; - let mut mpoly = MultiPolygon::<'_, 1, u32>::new(); + let mut mpoly = MultiPolygon::<1, u32>::new(); // 1st polygon mpoly.add_exterior([[0], [1], [2], [3], [0]]); mpoly.add_interior([[4], [5], [6], [7], [4]]); diff --git a/nusamai-geometry/src/compact/linestring.rs b/nusamai-geometry/src/compact/linestring.rs index 454671eb0..c49e8b229 100644 --- a/nusamai-geometry/src/compact/linestring.rs +++ b/nusamai-geometry/src/compact/linestring.rs @@ -32,7 +32,7 @@ impl<'a, const D: usize, T: CoordNum> LineString<'a, D, T> { } /// この LineString の座標列のイテレータを得る - pub fn iter(&self) -> Iter<'_, D, T> { + pub fn iter(&self) -> Iter { Iter { slice: &self.coords, pos: 0, @@ -41,7 +41,7 @@ impl<'a, const D: usize, T: CoordNum> LineString<'a, D, T> { } /// 始点と終点を閉じた座標列のイテレータを得る - pub fn iter_closed(&self) -> Iter<'_, D, T> { + pub fn iter_closed(&self) -> Iter { Iter { slice: &self.coords, pos: 0, diff --git a/nusamai-geometry/src/compact/multi_linestring.rs b/nusamai-geometry/src/compact/multi_linestring.rs index df74b3304..1e34162aa 100644 --- a/nusamai-geometry/src/compact/multi_linestring.rs +++ b/nusamai-geometry/src/compact/multi_linestring.rs @@ -52,7 +52,7 @@ impl<'a, const D: usize, T: CoordNum> MultiLineString<'a, D, T> { } } - pub fn iter(&self) -> Iter<'_, D, T> { + pub fn iter(&self) -> Iter { Iter { all_coords: &self.all_coords, coords_spans: &self.coords_spans, @@ -219,7 +219,7 @@ mod tests { #[test] fn test_transform() { { - let mut mlines: MultiLineString<'_, 2> = MultiLineString2::new(); + let mut mlines: MultiLineString<2> = MultiLineString2::new(); mlines.add_linestring([[0., 0.], [5., 0.], [5., 5.], [0., 5.]]); let new_mlines = mlines.transform(|[x, y]| [x + 2., y + 1.]); assert_eq!( diff --git a/nusamai-geometry/src/compact/multi_point.rs b/nusamai-geometry/src/compact/multi_point.rs index e486d0cbb..d71775ae6 100644 --- a/nusamai-geometry/src/compact/multi_point.rs +++ b/nusamai-geometry/src/compact/multi_point.rs @@ -31,7 +31,7 @@ impl<'a, const D: usize, T: CoordNum> MultiPoint<'a, D, T> { self.as_ref() } - pub fn iter(&self) -> Iter<'_, D, T> { + pub fn iter(&self) -> Iter { Iter { slice: &self.coords, pos: 0, diff --git a/nusamai-geometry/src/compact/multi_polygon.rs b/nusamai-geometry/src/compact/multi_polygon.rs index beb47a3e6..8a7fa59d3 100644 --- a/nusamai-geometry/src/compact/multi_polygon.rs +++ b/nusamai-geometry/src/compact/multi_polygon.rs @@ -113,7 +113,7 @@ impl<'a, const D: usize, T: CoordNum> MultiPolygon<'a, D, T> { } /// Returns an iterator over the polygons - pub fn iter(&self) -> Iter<'_, D, T> { + pub fn iter(&self) -> Iter { Iter { mpoly: self, pos: 0, diff --git a/nusamai-geometry/src/compact/polygon.rs b/nusamai-geometry/src/compact/polygon.rs index 1e7f6098d..d32d189a0 100644 --- a/nusamai-geometry/src/compact/polygon.rs +++ b/nusamai-geometry/src/compact/polygon.rs @@ -63,23 +63,13 @@ impl<'a, const D: usize, T: CoordNum> Polygon<'a, D, T> { } /// Returns an iterator over the interior rings of the polygon. - pub fn interiors(&self) -> impl Iterator> { - self.hole_indices - .windows(2) - .map(|a| (a[0] as usize * D, a[1] as usize * D)) - .chain(match self.hole_indices.is_empty() { - true => None, - false => Some(( - self.hole_indices[self.hole_indices.len() - 1] as usize * D, - self.coords.len(), - )), - }) - .map(|(start, end)| LineString::from_raw(self.coords[start..end].into())) + pub fn interiors(&self) -> Iter { + Iter { poly: self, pos: 1 } } /// Returns an iterator over the exterior and interior rings of the polygon. - pub fn rings(&self) -> impl Iterator> { - std::iter::once(self.exterior()).chain(self.interiors()) + pub fn rings(&self) -> Iter { + Iter { poly: self, pos: 0 } } pub fn clear(&mut self) { @@ -118,6 +108,37 @@ impl<'a, const D: usize, T: CoordNum> Polygon<'a, D, T> { } } +pub struct Iter<'a, const D: usize, T: CoordNum> { + poly: &'a Polygon<'a, D, T>, + pos: usize, +} + +impl<'a, const D: usize, T: CoordNum> Iterator for Iter<'a, D, T> { + type Item = LineString<'a, D, T>; + + fn next(&mut self) -> Option { + if self.pos < self.poly.hole_indices.len() + 1 { + let start = if self.pos == 0 { + 0 + } else { + self.poly.hole_indices[self.pos - 1] as usize * D + }; + + let end = if self.pos == self.poly.hole_indices.len() { + self.poly.coords.len() + } else { + self.poly.hole_indices[self.pos] as usize * D + }; + + let line = LineString::from_raw(self.poly.coords[start..end].into()); + self.pos += 1; + Some(line) + } else { + None + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -206,7 +227,7 @@ mod tests { #[test] fn test_transform() { { - let mut poly: Polygon<'_, 2> = Polygon2::new(); + let mut poly: Polygon<2> = Polygon2::new(); poly.add_ring([[0., 0.], [5., 0.], [5., 5.], [0., 5.]]); let new_poly = poly.transform(|[x, y]| [x + 2., y + 1.]); assert_eq!( diff --git a/nusamai-gltf/examples/geometry_to_gltf.rs b/nusamai-gltf/examples/geometry_to_gltf.rs index 996b1b5cc..a98a4be9d 100644 --- a/nusamai-gltf/examples/geometry_to_gltf.rs +++ b/nusamai-gltf/examples/geometry_to_gltf.rs @@ -444,7 +444,7 @@ fn make_gltf_json(triangles: &Triangles) -> String { gltf.to_string().unwrap() } -fn calc_center(all_mpolys: &Vec>) -> (f64, f64) { +fn calc_center(all_mpolys: &Vec>) -> (f64, f64) { // 中心の経緯度を求める let (mu_lat, mu_lng) = { let (mut mu_lat, mut mu_lng) = (0.0, 0.0); diff --git a/nusamai-gpkg/src/geometry.rs b/nusamai-gpkg/src/geometry.rs index 08444b43c..d80d6c77f 100644 --- a/nusamai-gpkg/src/geometry.rs +++ b/nusamai-gpkg/src/geometry.rs @@ -86,7 +86,7 @@ fn write_polygon_body( pub fn write_indexed_multipolygon( writer: &mut W, vertices: &[[f64; 3]], - mpoly: &MultiPolygon<'_, 1, u32>, + mpoly: &MultiPolygon<1, u32>, srs_id: i32, ) -> std::io::Result<()> { write_geometry_header(writer, srs_id)?; @@ -96,7 +96,7 @@ pub fn write_indexed_multipolygon( fn write_multipolygon_body( writer: &mut W, - mpoly: &MultiPolygon<'_, D, T>, + mpoly: &MultiPolygon, mapping: impl Fn(&[T]) -> [f64; 3], ) -> std::io::Result<()> { // Byte order: Little endian (1) @@ -134,7 +134,7 @@ mod tests { [1., 2., 111.], ]; - let mut mpoly = MultiPolygon::<'_, 1, u32>::new(); + let mut mpoly = MultiPolygon::<1, u32>::new(); // 1st polygon mpoly.add_exterior([[0], [1], [2], [3], [0]]); mpoly.add_interior([[4], [5], [6], [7], [4]]); diff --git a/nusamai-projection/src/jprect.rs b/nusamai-projection/src/jprect.rs index 9200186e0..842b60086 100644 --- a/nusamai-projection/src/jprect.rs +++ b/nusamai-projection/src/jprect.rs @@ -1,4 +1,4 @@ -//! Japan Plane Reculangular Coordinate Systems +//! Japan Plane Rectangular Coordinate Systems use crate::{ellipsoid::grs80, etmerc::ExtendedTransverseMercatorProjection}; diff --git a/nusamai/src/sink/geojson/mod.rs b/nusamai/src/sink/geojson/mod.rs index 4c161c718..ac7413c94 100644 --- a/nusamai/src/sink/geojson/mod.rs +++ b/nusamai/src/sink/geojson/mod.rs @@ -193,7 +193,7 @@ mod tests { [5., 5., 111.], [0., 5., 111.], ]; - let mut mpoly = MultiPolygon::<'_, 1, u32>::new(); + let mut mpoly = MultiPolygon::<1, u32>::new(); mpoly.add_exterior([[0], [1], [2], [3], [0]]); let geometries = nusamai_citygml::GeometryStore { epsg: EPSG_JGD2011_GEOGRAPHIC_3D, From ed9b97ca86ec17c489e209ddcc1caa3f76fd6866 Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Tue, 9 Jan 2024 11:21:47 +0900 Subject: [PATCH 6/6] (citygml::object) Use Cow --- nusamai-citygml/src/object.rs | 51 +++++++++++++++-------------------- nusamai-citygml/src/values.rs | 2 +- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/nusamai-citygml/src/object.rs b/nusamai-citygml/src/object.rs index 2d6883647..f1454a2dc 100644 --- a/nusamai-citygml/src/object.rs +++ b/nusamai-citygml/src/object.rs @@ -1,5 +1,7 @@ //! Object representation of the city objects. +use std::borrow::Cow; + use crate::geometry::{self, GeometryRef}; use crate::values::{Code, Point, URI}; use crate::Measure; @@ -16,7 +18,7 @@ pub struct CityObject { #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Feature { - pub typename: String, + pub typename: Cow<'static, str>, pub id: Option, pub attributes: Map, pub geometries: Option, @@ -24,7 +26,7 @@ pub struct Feature { #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Data { - pub typename: String, + pub typename: Cow<'static, str>, pub attributes: Map, } @@ -51,7 +53,7 @@ impl Value { pub fn to_attribute_json(&self) -> serde_json::Value { use Value::*; match &self { - String(s) => serde_json::Value::String(s.clone()), + String(s) => serde_json::Value::String(s.into()), Code(c) => serde_json::Value::String(c.value().to_owned()), Integer(i) => serde_json::Value::Number((*i).into()), Double(d) => serde_json::Value::Number(serde_json::Number::from_f64(*d).unwrap()), @@ -74,19 +76,19 @@ impl Value { let mut m = serde_json::Map::from_iter( feat.attributes .iter() - .map(|(k, v)| (k.clone(), v.to_attribute_json())), + .map(|(k, v)| (k.into(), v.to_attribute_json())), ); m.insert("type".into(), feat.typename.clone().into()); m.insert("id".into(), feat.id.clone().into()); serde_json::Value::Object(m) } - Data(feat) => { + Data(data) => { let mut m = serde_json::Map::from_iter( - feat.attributes + data.attributes .iter() - .map(|(k, v)| (k.clone(), v.to_attribute_json())), + .map(|(k, v)| (k.into(), v.to_attribute_json())), ); - m.insert("type".into(), feat.typename.clone().into()); + m.insert("type".into(), data.typename.clone().into()); serde_json::Value::Object(m) } } @@ -102,40 +104,31 @@ mod tests { #[test] fn to_attribute_json() { let obj = Value::String("test".into()); - let value = obj.to_attribute_json(); - assert_eq!(value, json!("test")); + assert_eq!(obj.to_attribute_json(), json!("test")); let obj = Value::Code(Code::new("12345".into(), "12345".into())); - let value = obj.to_attribute_json(); - assert_eq!(value, json!("12345")); + assert_eq!(obj.to_attribute_json(), json!("12345")); let obj = Value::Integer(12345); - let value = obj.to_attribute_json(); - assert_eq!(value, json!(12345)); + assert_eq!(obj.to_attribute_json(), json!(12345)); let obj = Value::Double(1.0); - let value = obj.to_attribute_json(); - assert_eq!(value, json!(1.0)); + assert_eq!(obj.to_attribute_json(), json!(1.0)); let obj = Value::Measure(Measure { value: 1.0 }); - let value = obj.to_attribute_json(); - assert_eq!(value, json!(1.0)); + assert_eq!(obj.to_attribute_json(), json!(1.0)); let obj = Value::Boolean(true); - let value = obj.to_attribute_json(); - assert_eq!(value, json!(true)); + assert_eq!(obj.to_attribute_json(), json!(true)); let obj = Value::URI(URI::new("http://example.com")); - let value = obj.to_attribute_json(); - assert_eq!(value, json!("http://example.com")); + assert_eq!(obj.to_attribute_json(), json!("http://example.com")); let obj = Value::Date(NaiveDate::from_ymd_opt(2020, 1, 1).unwrap()); - let value = obj.to_attribute_json(); - assert_eq!(value, json!("2020-01-01")); + assert_eq!(obj.to_attribute_json(), json!("2020-01-01")); let obj = Value::Array(vec![Value::String("test".into()), Value::Integer(1)]); - let value = obj.to_attribute_json(); - assert_eq!(value, json!(["test", 1])); + assert_eq!(obj.to_attribute_json(), json!(["test", 1])); let mut attributes = Map::default(); attributes.insert("String".into(), Value::String("test".into())); @@ -146,9 +139,8 @@ mod tests { attributes, geometries: None, }); - let value = obj.to_attribute_json(); assert_eq!( - value, + obj.to_attribute_json(), json! { { "type": "test", @@ -166,9 +158,8 @@ mod tests { typename: "test".into(), attributes, }); - let value = obj.to_attribute_json(); assert_eq!( - value, + obj.to_attribute_json(), json! { { "type": "test", diff --git a/nusamai-citygml/src/values.rs b/nusamai-citygml/src/values.rs index f20c91d28..8e1d809cd 100644 --- a/nusamai-citygml/src/values.rs +++ b/nusamai-citygml/src/values.rs @@ -413,7 +413,7 @@ impl CityGMLElement for GenericAttribute { }), ); Some(Value::Data(object::Data { - typename: "gen:genericAttribute".to_string(), + typename: "gen:genericAttribute".into(), attributes: map, })) }