-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
宣言的なスキーマ定義コードをもとに、Rust の proc_macro を使って、XMLツリーを探索するコードを自動生成します。 実現方法がある程度固まってきたので、このへんでいったんマージしたいです。 ジオメトリの扱いも一定程度実装してある。アピアランス周り(色、テクスチャ)は全く考慮していません。 現時点ではテストは未整備。 ``` ├── citygml -- CityGMLパーサの本体 │ ├── macros -- CityGMLパーサを利用したXMLの探索を自動導出 (derive) する手続きマクロ │ │ └── src │ └── src ├── examples -- .gml ファイルをパースするサンプルコード └── src └── models -- 上記のパーサの仕組みを利用した、code-first なスキーマ定義 ``` 実行例: ``` cargo run --example parse --release -- /path/to/13100_tokyo23-ku_2022_citygml_1_lod4-2_op/udx/bldg/53393680_bldg_6697_lod4.0_op.gml ``` (興味のある方向け)マクロの展開の様子は以下のようにすれば見れます: ``` cargo install cargo-expand cargo expand models::building --no-default-feature ```
- Loading branch information
Showing
30 changed files
with
2,241 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
[package] | ||
name = "nusamai-plateau" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[features] | ||
default = ["serde"] | ||
|
||
[dependencies] | ||
quick-xml = "0.31" | ||
serde = { version = "1.0.193", features = ["derive"], optional = true } | ||
citygml = { path = "./citygml", features = ["serde"]} | ||
|
||
[dev-dependencies] | ||
zstd = { version = "0.13.0", features = ["zdict_builder"] } | ||
bincode = { version = "2.0.0-rc", default-features = false, features = ["std", "serde"] } | ||
clap = { version = "4.4", features = ["derive"] } | ||
serde = { version = "1.0.193", features = ["derive"] } | ||
serde_json = "1.0.108" | ||
lz4_flex = "0.11.1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "citygml" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[features] | ||
default = ["serde"] | ||
serde = ["dep:serde", "nusamai-geometry/serde"] | ||
|
||
[dependencies] | ||
indexmap = "2.1" | ||
macros = { path = "./macros" } | ||
nusamai-geometry = { path = "../../nusamai-geometry", features = ["serde"]} | ||
quick-xml = "0.31" | ||
serde = { version = "1.0", features = ["derive"], optional = true } | ||
thiserror = "1.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[package] | ||
name = "macros" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
proc-macro2 = "1.0" | ||
quote = "1.0" | ||
syn = { version = "2.0", features = ["full"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
extern crate proc_macro; | ||
|
||
use proc_macro2::TokenStream; | ||
use quote::{format_ident, quote}; | ||
use syn::{parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Error, LitByteStr}; | ||
|
||
const CITYGML_ATTR_IDENT: &str = "citygml"; | ||
|
||
fn generate_citygml_struct_model( | ||
derive_input: &DeriveInput, | ||
struct_data: &DataStruct, | ||
) -> Result<TokenStream, Error> { | ||
let mut attribute_arms = Vec::new(); | ||
let mut chlid_arms = Vec::new(); | ||
|
||
for field in &struct_data.fields { | ||
let Some(field_ident) = &field.ident else { | ||
continue; | ||
}; | ||
let field_ty = &field.ty; | ||
for attr in &field.attrs { | ||
if !attr.path().is_ident(CITYGML_ATTR_IDENT) { | ||
continue; | ||
} | ||
attr.parse_nested_meta(|meta| { | ||
if meta.path.is_ident("path") { | ||
let path: LitByteStr = meta.value()?.parse()?; | ||
|
||
if path.value().starts_with(b"@") { | ||
// XML attributes (e.g. @gml:id) | ||
attribute_arms.push(quote! { | ||
#path => { | ||
self.id = <#field_ty as citygml::CityGMLAttribute>::parse_attr_value( | ||
std::str::from_utf8(value).unwrap(), | ||
)?; | ||
Ok(()) | ||
} | ||
}); | ||
} else { | ||
// XML child elements (e.g. bldg:measuredHeight) | ||
chlid_arms.push(quote! { | ||
#path => <#field_ty as CityGMLElement>::parse(&mut self.#field_ident, st), | ||
}); | ||
} | ||
Ok(()) | ||
} | ||
else if meta.path.is_ident("auto_geom") { | ||
let prefix: LitByteStr = meta.value()?.parse()?; | ||
|
||
let mut add_arm = |lod: u8, name: &[u8], geomtype: &str | { | ||
let mut c = prefix.value().clone(); | ||
c.push(b':'); | ||
c.extend(name); | ||
let pat = LitByteStr::new(c.as_ref(), prefix.span()); | ||
let geomtype = format_ident!("{}", geomtype); | ||
|
||
chlid_arms.push(quote! { | ||
#pat => st.parse_geometric_attr(&mut self.#field_ident, #lod, ::citygml::geometric::GeometryParseType::#geomtype), | ||
}); | ||
}; | ||
|
||
add_arm(1, b"lod1Solid", "Solid"); | ||
add_arm(1, b"lod1MultiSurface", "MultiSurface"); | ||
add_arm(2, b"lod2MultiSurface", "MultiSurface"); | ||
add_arm(3, b"lod3MultiSurface", "MultiSurface"); | ||
add_arm(4, b"lod4MultiSurface", "MultiSurface"); | ||
add_arm(1, b"lod1Geometry", "Geometry"); | ||
add_arm(2, b"lod2Geometry", "Geometry"); | ||
add_arm(3, b"lod3Geometry", "Geometry"); | ||
add_arm(4, b"lod4Geometry", "Geometry"); | ||
add_arm(1, b"tin", "Triangulated"); | ||
|
||
Ok(()) | ||
} else { | ||
Err(meta.error("unrecognized attribute")) | ||
} | ||
})?; | ||
} | ||
} | ||
|
||
let (impl_generics, ty_generics, where_clause) = &derive_input.generics.split_for_impl(); | ||
let struct_name = &derive_input.ident; | ||
|
||
let attr_parsing = (!attribute_arms.is_empty()).then(|| { | ||
quote! { | ||
st.parse_attributes(|name, value| match name { | ||
#(#attribute_arms)* | ||
_ => Ok(()), | ||
})?; | ||
} | ||
}); | ||
|
||
Ok(quote! { | ||
impl #impl_generics ::citygml::CityGMLElement for #struct_name #ty_generics #where_clause { | ||
fn parse<R: std::io::BufRead>(&mut self, st: &mut ::citygml::SubTreeReader<R>) -> Result<(), ::citygml::ParseError> { | ||
#attr_parsing | ||
|
||
st.parse_children(|st| { | ||
match st.current_path() { | ||
#(#chlid_arms)* | ||
_ => Ok(()), | ||
} | ||
}) | ||
} | ||
} | ||
}) | ||
} | ||
|
||
fn generate_citygml_enum_model( | ||
derive_input: &DeriveInput, | ||
enum_data: &DataEnum, | ||
) -> Result<TokenStream, Error> { | ||
let mut child_arms = Vec::new(); | ||
for variant in &enum_data.variants { | ||
if variant.fields.len() > 1 { | ||
return Err(Error::new_spanned( | ||
variant, | ||
"variant must not have two or more fields", | ||
)); | ||
} | ||
if variant.fields.is_empty() { | ||
continue; | ||
} | ||
let field = variant.fields.iter().next().unwrap(); | ||
let field_ty = &field.ty; | ||
let variant_ident = &variant.ident; | ||
|
||
for attr in &variant.attrs { | ||
if !attr.path().is_ident(CITYGML_ATTR_IDENT) { | ||
continue; | ||
} | ||
attr.parse_nested_meta(|meta| { | ||
if meta.path.is_ident("path") { | ||
let value = meta.value()?; | ||
let path: LitByteStr = value.parse()?; | ||
|
||
let arm = quote! { | ||
#path => { | ||
let mut v: #field_ty = Default::default(); | ||
<#field_ty as CityGMLElement>::parse(&mut v, st)?; | ||
*self = Self::#variant_ident(v); | ||
Ok(()) | ||
} | ||
}; | ||
child_arms.push(arm); | ||
} | ||
Ok(()) | ||
})?; | ||
} | ||
} | ||
|
||
let (impl_generics, ty_generics, where_clause) = &derive_input.generics.split_for_impl(); | ||
let struct_name = &derive_input.ident; | ||
|
||
let tokens = quote! { | ||
impl #impl_generics ::citygml::CityGMLElement for #struct_name #ty_generics #where_clause { | ||
fn parse<R: ::std::io::BufRead>(&mut self, st: &mut ::citygml::SubTreeReader<R>) -> Result<(), ::citygml::ParseError> { | ||
st.parse_children(|st| { | ||
match st.current_path() { | ||
#(#child_arms)* | ||
_ => Ok(()), | ||
} | ||
}) | ||
} | ||
} | ||
}; | ||
|
||
Ok(tokens) | ||
} | ||
|
||
fn generate_citygml_model(derive_input: &DeriveInput) -> Result<TokenStream, Error> { | ||
match &derive_input.data { | ||
Data::Struct(data) => generate_citygml_struct_model(derive_input, data), | ||
Data::Enum(data) => generate_citygml_enum_model(derive_input, data), | ||
_ => Err(Error::new_spanned( | ||
derive_input, | ||
"target must be struct or enum", | ||
)), | ||
} | ||
} | ||
|
||
#[proc_macro_derive(CityGMLElement, attributes(citygml))] | ||
pub fn derive_citygml_model(token: proc_macro::TokenStream) -> proc_macro::TokenStream { | ||
match generate_citygml_model(&parse_macro_input!(token)) { | ||
Ok(tokens) => tokens, | ||
Err(e) => e.to_compile_error(), | ||
} | ||
.into() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
use nusamai_geometry::{MultiLineString, MultiPolygon}; | ||
|
||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] | ||
#[derive(Debug, Clone, Copy)] | ||
pub enum GeometryParseType { | ||
Geometry, | ||
Solid, | ||
MultiSurface, | ||
MultiCurve, | ||
Triangulated, | ||
} | ||
|
||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] | ||
#[derive(Debug, Clone, Copy, Default)] | ||
pub enum GeometryType { | ||
#[default] | ||
Unknown, | ||
Solid, | ||
Surface, | ||
Triangle, | ||
} | ||
|
||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] | ||
#[derive(Debug)] | ||
pub struct GeometryRefEntry { | ||
#[serde(rename = "type")] | ||
pub ty: GeometryType, | ||
pub lod: u8, | ||
pub pos: u32, | ||
pub len: u32, | ||
} | ||
|
||
pub type GeometryRef = Vec<GeometryRefEntry>; | ||
|
||
/// 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 vertices: Vec<[f64; 3]>, | ||
pub polygons: MultiPolygon<'static, 1, u32>, | ||
pub linestrings: MultiLineString<'static, 1, u32>, | ||
} | ||
|
||
/// Store for collecting vertices and polygons from GML. | ||
#[derive(Default)] | ||
pub struct GeometryCollector { | ||
pub vertices: indexmap::IndexSet<[u64; 3]>, | ||
pub multi_polygon: MultiPolygon<'static, 1, u32>, | ||
pub multi_linestring: MultiLineString<'static, 1, u32>, | ||
} | ||
|
||
impl GeometryCollector { | ||
pub fn add_exterior_ring(&mut self, iter: impl Iterator<Item = [f64; 3]>) -> usize { | ||
self.multi_polygon.add_exterior(iter.map(|v| { | ||
// ... | ||
let vbits = [v[0].to_bits(), v[1].to_bits(), v[2].to_bits()]; | ||
let (index, _) = self.vertices.insert_full(vbits); | ||
[index as u32] | ||
})); | ||
|
||
self.multi_polygon.len() - 1 | ||
} | ||
|
||
pub fn add_interior_ring(&mut self, iter: impl Iterator<Item = [f64; 3]>) { | ||
self.multi_polygon.add_interior(iter.map(|v| { | ||
// ... | ||
let vbits = [v[0].to_bits(), v[1].to_bits(), v[2].to_bits()]; | ||
let (index, _) = self.vertices.insert_full(vbits); | ||
[index as u32] | ||
})); | ||
} | ||
|
||
pub fn to_geometries(&self) -> Geometries { | ||
let mut vertices = Vec::with_capacity(self.vertices.len()); | ||
for vbits in &self.vertices { | ||
vertices.push([ | ||
f64::from_bits(vbits[0]), | ||
f64::from_bits(vbits[1]), | ||
f64::from_bits(vbits[2]), | ||
]); | ||
} | ||
Geometries { | ||
vertices, | ||
polygons: self.multi_polygon.clone(), | ||
linestrings: self.multi_linestring.clone(), | ||
} | ||
} | ||
|
||
pub fn clear(&mut self) { | ||
self.vertices.clear(); | ||
self.multi_polygon.clear(); | ||
self.multi_linestring.clear(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
pub mod geometric; | ||
pub mod model; | ||
pub mod namespace; | ||
pub mod parser; | ||
|
||
pub use geometric::*; | ||
pub use model::*; | ||
pub use namespace::*; | ||
pub use parser::*; | ||
|
||
pub use macros::CityGMLElement; |
Oops, something went wrong.