diff --git a/nusamai-citygml/macros/src/derive.rs b/nusamai-citygml/macros/src/derive.rs index 67a1b49c8..2be5d3f0b 100644 --- a/nusamai-citygml/macros/src/derive.rs +++ b/nusamai-citygml/macros/src/derive.rs @@ -4,7 +4,7 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use syn::{parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Error, LitByteStr, LitStr}; -use crate::ElementType; +use crate::StereoType; const CITYGML_ATTR_IDENT: &str = "citygml"; @@ -38,7 +38,8 @@ fn generate_citygml_impl_for_struct( let mut id_value = quote!(None); let struct_ident = &derive_input.ident; let mut typename = String::from(stringify!(derive_input.ident)); - let mut ty = ElementType::Feature; + let mut ty = StereoType::Feature; + let mut allow_extra = true; // FIXME for attr in &derive_input.attrs { if !attr.path().is_ident(CITYGML_ATTR_IDENT) { @@ -48,23 +49,30 @@ fn generate_citygml_impl_for_struct( if meta.path.is_ident("name") { let name: LitStr = meta.value()?.parse()?; typename = name.value(); - } - if meta.path.is_ident("type") { + Ok(()) + } else if meta.path.is_ident("type") { let ty_ident: Ident = meta.value()?.parse()?; ty = match ty_ident.to_string().as_str() { - "feature" => ElementType::Feature, - "data" => ElementType::Data, + "feature" => StereoType::Feature, + "data" => StereoType::Data, _ => { return Err(meta.error("feature or data expected")); } }; + Ok(()) + } else if meta.path.is_ident("allow_extra") { + allow_extra = true; + Ok(()) + } else { + Ok(()) } - Ok(()) })?; } // Scan struct fields for field in &struct_data.fields { + let mut into_obj_generated = false; + let Some(field_ident) = &field.ident else { continue; }; @@ -87,6 +95,10 @@ fn generate_citygml_impl_for_struct( else if meta.path.is_ident("path") { let path: LitByteStr = meta.value()?.parse()?; + if path.value().iter().filter(|c| c == &&b'/').count() > 1 { + return Err(meta.error("path must not contain more than one '/'")); + } + if path.value().starts_with(b"@") { // XML attributes (e.g. @gml:id) attribute_arms.push(quote! { @@ -112,25 +124,39 @@ fn generate_citygml_impl_for_struct( } } else { // XML child elements (e.g. bldg:measuredHeight) + + // if the path contains '/', add the first path as a 'noop' arm. + if let Some(pos) = path.value().iter().position(|&x| x == b'/') { + let prefix = LitByteStr::new(&path.value()[..pos], path.span()); + chlid_arms.push( + quote! { + #prefix => Ok(()), + } + ); + }; + chlid_arms.push( quote! { #path => <#field_ty as CityGMLElement>::parse(&mut self.#field_ident, st), } ); - // Use the first path component as the attribute name - // e.g. "bldg:interiorRoom/bldg:Room" -> "bldg:interiorRoom" - let path_value = path.value(); - let pos_slash = path_value.iter().position(|&x| x == b'/').unwrap_or(path_value.len()); - let name = std::str::from_utf8(&path_value[..pos_slash]).unwrap(); + if !into_obj_generated { + // Use the first path component as the attribute name + // e.g. "bldg:interiorRoom/bldg:Room" -> "bldg:interiorRoom" + let path_value = path.value(); + let pos_slash = path_value.iter().position(|&x| x == b'/').unwrap_or(path_value.len()); + let name = std::str::from_utf8(&path_value[..pos_slash]).unwrap(); - into_object_stmts.push( - quote! { - if let Some(v) = self.#field_ident.into_object() { - attributes.insert(#name.into(), v); + into_object_stmts.push( + quote! { + if let Some(v) = self.#field_ident.into_object() { + attributes.insert(#name.into(), v); + } } - } - ) + ); + into_obj_generated = true; + } } Ok(()) } @@ -178,7 +204,7 @@ fn generate_citygml_impl_for_struct( Ok(()) } else { - Err(meta.error("unrecognized attribute")) + Err(meta.error("unrecognized argument")) } })?; } @@ -196,7 +222,7 @@ fn generate_citygml_impl_for_struct( }); let into_object_impl = match ty { - ElementType::Feature => { + StereoType::Feature => { quote! { Some(::nusamai_citygml::object::Value::Feature( ::nusamai_citygml::object::Feature { @@ -212,7 +238,7 @@ fn generate_citygml_impl_for_struct( )) } } - ElementType::Data => { + StereoType::Data => { quote! { Some(::nusamai_citygml::object::Value::Data( ::nusamai_citygml::object::Data { @@ -230,11 +256,20 @@ fn generate_citygml_impl_for_struct( }; let element_type = match ty { - ElementType::Feature => quote! { ::nusamai_citygml::ElementType::FeatureType }, - ElementType::Data => quote! { ::nusamai_citygml::ElementType::DataType }, + StereoType::Feature => quote! { ::nusamai_citygml::ElementType::FeatureType }, + StereoType::Data => quote! { ::nusamai_citygml::ElementType::DataType }, _ => unreachable!(), }; + let extra_arm = match allow_extra { + true => quote! { Ok(()) }, + false => quote! { + Err(::nusamai_citygml::ParseError::SchemaViolation( + format!("unexpected element: {}", String::from_utf8_lossy(st.current_path())), + )) + }, + }; + Ok(quote! { impl #impl_generics ::nusamai_citygml::CityGMLElement for #struct_ident #ty_generics #where_clause { const ELEMENT_TYPE: ::nusamai_citygml::ElementType = #element_type; @@ -245,7 +280,7 @@ fn generate_citygml_impl_for_struct( st.parse_children(|st| { match st.current_path() { #(#chlid_arms)* - _ => Ok(()), + _ => #extra_arm, } }) } diff --git a/nusamai-citygml/macros/src/lib.rs b/nusamai-citygml/macros/src/lib.rs index e26c9e498..11a4cd5ab 100644 --- a/nusamai-citygml/macros/src/lib.rs +++ b/nusamai-citygml/macros/src/lib.rs @@ -1,3 +1,5 @@ +//! Parsing utilities for CityGML 2.0 (and possibly 3.0) + extern crate proc_macro; mod derive; @@ -12,21 +14,21 @@ pub fn derive_citygml_element(token: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn citygml_feature(args: TokenStream, input: TokenStream) -> TokenStream { - type_attrs::citygml_type(ElementType::Feature, args, input) + type_attrs::citygml_type(StereoType::Feature, args, input) } #[proc_macro_attribute] pub fn citygml_data(args: TokenStream, input: TokenStream) -> TokenStream { - type_attrs::citygml_type(ElementType::Data, args, input) + type_attrs::citygml_type(StereoType::Data, args, input) } #[proc_macro_attribute] pub fn citygml_property(args: TokenStream, input: TokenStream) -> TokenStream { - type_attrs::citygml_type(ElementType::Property, args, input) + type_attrs::citygml_type(StereoType::Property, args, input) } #[derive(Clone, Copy)] -pub(crate) enum ElementType { +pub(crate) enum StereoType { Feature, Data, Property, diff --git a/nusamai-citygml/macros/src/type_attrs.rs b/nusamai-citygml/macros/src/type_attrs.rs index d012c8f1b..3821385a2 100644 --- a/nusamai-citygml/macros/src/type_attrs.rs +++ b/nusamai-citygml/macros/src/type_attrs.rs @@ -1,4 +1,4 @@ -use crate::ElementType; +use crate::StereoType; use proc_macro2::TokenStream; use quote::quote; use syn::meta::ParseNestedMeta; @@ -17,7 +17,11 @@ impl FeatureArgs { if meta.path.is_ident("name") { let s: LitStr = meta.value()?.parse()?; self.prefix = Some(LitByteStr::new( - s.value().split_once(':').unwrap().0.as_bytes(), + s.value() + .split_once(':') + .ok_or_else(|| meta.error("ns prefix is missing"))? + .0 + .as_bytes(), s.span(), )); self.name = Some(s); @@ -29,7 +33,7 @@ impl FeatureArgs { } pub(crate) fn citygml_type( - ty: ElementType, + ty: StereoType, args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { @@ -57,7 +61,7 @@ fn add_named_field(fields: &mut syn::FieldsNamed, body: TokenStream) { .push(syn::Field::parse_named.parse2(body).unwrap()) } -fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Result<(), Error> { +fn modify(ty: &StereoType, args: &FeatureArgs, input: &mut DeriveInput) -> Result<(), Error> { match &args.name { Some(name) => { input.attrs.push(syn::parse_quote! { @@ -68,13 +72,13 @@ fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Resu }; input.attrs.push(match &ty { - ElementType::Feature => { + StereoType::Feature => { syn::parse_quote! { #[citygml(type = feature)] } } - ElementType::Data => { + StereoType::Data => { syn::parse_quote! { #[citygml(type = data)] } } - ElementType::Property => { + StereoType::Property => { syn::parse_quote! { #[citygml(type = property)] } } }); @@ -84,12 +88,12 @@ fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Resu // for #[citygml_feature] and #[citygml_data] match ty { - ElementType::Feature | ElementType::Data => {} + StereoType::Feature | StereoType::Data => {} _ => return Err(Error::new_spanned(input, "target must be struct")), } if let syn::Fields::Named(ref mut fields) = data.fields { - if let ElementType::Feature = ty { + if let StereoType::Feature = ty { // for #[citygml_feature] let prefix = args.prefix.as_ref().unwrap(); @@ -132,28 +136,28 @@ fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Resu add_named_field( fields, quote! { - #[citygml(path = b"gml:creationDate")] + #[citygml(path = b"core:creationDate")] pub creation_date: Option // TODO: DateTime (CityGML 3.0) }, ); add_named_field( fields, quote! { - #[citygml(path = b"gml:terminationDate")] + #[citygml(path = b"core:terminationDate")] pub termination_date: Option // TODO: DateTime (CityGML 3.0) }, ); add_named_field( fields, quote! { - #[citygml(path = b"gml:validFrom")] + #[citygml(path = b"core:validFrom")] pub valid_from: Option // TODO: DateTime (CityGML 3.0) }, ); add_named_field( fields, quote! { - #[citygml(path = b"gml:validTo")] + #[citygml(path = b"core:validTo")] pub valid_to: Option // TODO: DateTime (CityGML 3.0) }, ); @@ -161,7 +165,7 @@ fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Resu } } Data::Enum(_data) => match ty { - ElementType::Property => { + StereoType::Property => { // for #[citygml_property] _data.variants.push(parse_quote! { #[default] diff --git a/nusamai-citygml/src/attribute.rs b/nusamai-citygml/src/attribute.rs new file mode 100644 index 000000000..d688d4cae --- /dev/null +++ b/nusamai-citygml/src/attribute.rs @@ -0,0 +1,19 @@ +use crate::parser::ParseError; + +pub trait CityGMLAttribute: Sized { + fn parse_attr_value(value: &str) -> Result; +} + +impl CityGMLAttribute for String { + #[inline] + fn parse_attr_value(value: &str) -> Result { + Ok(value.to_string()) + } +} + +impl CityGMLAttribute for Option { + #[inline] + fn parse_attr_value(value: &str) -> Result { + Ok(Some(::parse_attr_value(value)?)) + } +} diff --git a/nusamai-citygml/src/geometry.rs b/nusamai-citygml/src/geometry.rs index 918c95b0b..36318c630 100644 --- a/nusamai-citygml/src/geometry.rs +++ b/nusamai-citygml/src/geometry.rs @@ -12,7 +12,7 @@ pub enum GeometryParseType { } #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum GeometryType { #[default] Unknown, @@ -24,7 +24,7 @@ pub enum GeometryType { } #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct GeometryRefEntry { #[serde(rename = "type")] pub ty: GeometryType, diff --git a/nusamai-citygml/src/lib.rs b/nusamai-citygml/src/lib.rs index b7c75240b..8867cc6bc 100644 --- a/nusamai-citygml/src/lib.rs +++ b/nusamai-citygml/src/lib.rs @@ -1,3 +1,4 @@ +pub mod attribute; pub mod codelist; pub mod geometry; pub mod namespace; @@ -5,6 +6,7 @@ pub mod object; pub mod parser; pub mod values; +pub use attribute::*; pub use geometry::*; pub use macros::*; pub use namespace::*; diff --git a/nusamai-citygml/src/namespace.rs b/nusamai-citygml/src/namespace.rs index 0d358c51b..071b46477 100644 --- a/nusamai-citygml/src/namespace.rs +++ b/nusamai-citygml/src/namespace.rs @@ -1,10 +1,12 @@ use quick_xml::name::{Namespace, ResolveResult}; -/// Normalize a XML namaespace URI to a well-known prefix. +pub const GML31_NS: Namespace = Namespace(b"http://www.opengis.net/gml"); + +/// Normalizes `quick_xml::name::ResolveResult` to the well-known prefix. /// /// e.g. `"http://www.opengis.net/citygml/2.0"` -> `"core:"` #[inline] -pub fn normalize_ns_prefix<'a>(ns: &ResolveResult<'a>) -> &'a [u8] { +pub fn wellknown_prefix_from_nsres<'a>(ns: &ResolveResult<'a>) -> &'a [u8] { match ns { ResolveResult::Bound(Namespace(name)) => { const OPENGIS_PREFIX: &[u8] = b"http://www.opengis.net/"; @@ -27,6 +29,7 @@ pub fn normalize_ns_prefix<'a>(ns: &ResolveResult<'a>) -> &'a [u8] { b"cityobjectgroup/2.0" => b"grp:", b"landuse/2.0" => b"luse:", b"transportation/2.0" => b"tran:", + b"texturedsurface/2.0" => b"tex:", // deprecated b"vegetation/2.0" => b"veg:", b"waterbody/2.0" => b"wtr:", b"tunnel/2.0" => b"tun:", @@ -46,6 +49,12 @@ pub fn normalize_ns_prefix<'a>(ns: &ResolveResult<'a>) -> &'a [u8] { b"urf/2.0" => b"urf:", _ => b"unsupported:", } + } else if name == b"urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" { + // OASIS xAL 2.0 + b"xAL:" + } else if name == b"http://www.w3.org/1999/xlink" { + // xlink + b"xlink:" } else { // PLATEAU 1.x match *name { @@ -65,3 +74,83 @@ pub fn normalize_ns_prefix<'a>(ns: &ResolveResult<'a>) -> &'a [u8] { ResolveResult::Unknown(_name) => b"unknown:", } } + +#[cfg(test)] +mod tests { + #[test] + fn normalized_prefix() { + use super::*; + use quick_xml::{events::Event, NsReader}; + + let data = r#" + + + + + + + + + + + + + + + + + + + + + + + + + + + + "#; + + let mut reader = NsReader::from_str(data); + reader.trim_text(true); + reader.expand_empty_elements(true); + loop { + match reader.read_resolved_event() { + Ok((ns, Event::Start(ref e))) => { + let wellknown = std::str::from_utf8(wellknown_prefix_from_nsres(&ns)).unwrap(); + let localname = e.local_name(); + let expected = String::from_utf8_lossy(localname.as_ref()) + ":"; + assert_eq!(wellknown, expected); + } + Ok((_, Event::Eof)) => break, + Ok(_) => {} + Err(e) => panic!("{:?}", e), + } + } + } +} diff --git a/nusamai-citygml/src/object.rs b/nusamai-citygml/src/object.rs index 6f39521cb..ac7d77234 100644 --- a/nusamai-citygml/src/object.rs +++ b/nusamai-citygml/src/object.rs @@ -2,39 +2,41 @@ use crate::geometry::{self, GeometryRef}; use crate::values::{Code, Point, URI}; +use crate::Measure; use chrono::NaiveDate; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::iter; +pub type Map = std::collections::HashMap; + #[derive(Debug, Deserialize, Serialize)] pub struct CityObject { pub root: Value, pub geometries: geometry::Geometries, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Feature { pub typename: String, pub id: Option, - pub attributes: HashMap, + pub attributes: Map, pub geometries: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Data { pub typename: String, - pub attributes: HashMap, + pub attributes: Map, } /// Nodes for the "Object" representation of the city object. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub enum Value { String(String), Code(Code), Integer(i64), Double(f64), - Measure(f64), + Measure(Measure), Boolean(bool), URI(URI), Date(NaiveDate), @@ -54,7 +56,7 @@ impl Value { 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()), - Measure(m) => serde_json::Value::Number(serde_json::Number::from_f64(*m).unwrap()), + Measure(m) => serde_json::Value::Number(serde_json::Number::from_f64(m.value).unwrap()), Boolean(b) => serde_json::Value::Bool(*b), URI(u) => serde_json::Value::String(u.value().clone()), Date(d) => serde_json::Value::String(d.to_string()), // ISO 8601 @@ -112,7 +114,7 @@ mod tests { let value = obj.to_attribute_json(); assert_eq!(value, json!(1.0)); - let obj = Value::Measure(1.0); + let obj = Value::Measure(Measure { value: 1.0 }); let value = obj.to_attribute_json(); assert_eq!(value, json!(1.0)); @@ -132,7 +134,7 @@ mod tests { let value = obj.to_attribute_json(); assert_eq!(value, json!(["test", 1])); - let mut attributes = HashMap::new(); + let mut attributes = Map::new(); attributes.insert("String".into(), Value::String("test".into())); attributes.insert("Integer".into(), Value::Integer(1)); let obj = Value::Feature(Feature { @@ -154,7 +156,7 @@ mod tests { } ); - let mut attributes = HashMap::new(); + let mut attributes = Map::new(); 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/parser.rs b/nusamai-citygml/src/parser.rs index 94f61a686..fbaf92b49 100644 --- a/nusamai-citygml/src/parser.rs +++ b/nusamai-citygml/src/parser.rs @@ -1,19 +1,18 @@ -use crate::codelist::{self, CodeResolver}; use std::io::BufRead; use std::str; -use url::Url; -use crate::geometry::{ - Geometries, GeometryCollector, GeometryParseType, GeometryRef, GeometryRefEntry, GeometryType, -}; -use crate::namespace::normalize_ns_prefix; use quick_xml::events::{BytesStart, Event}; use quick_xml::name::Namespace; use quick_xml::name::ResolveResult::Bound; use quick_xml::NsReader; use thiserror::Error; +use url::Url; -const GML_NS: Namespace = Namespace(b"http://www.opengis.net/gml"); +use crate::codelist::{self, CodeResolver}; +use crate::geometry::{ + Geometries, GeometryCollector, GeometryParseType, GeometryRef, GeometryRefEntry, GeometryType, +}; +use crate::namespace::{wellknown_prefix_from_nsres, GML31_NS}; #[derive(Error, Debug)] pub enum ParseError { @@ -122,7 +121,7 @@ impl<'a> CityGMLReader<'a> { let (nsres, localname) = reader.resolve_element(start.name()); state.path_stack_indices.push(state.path_buf.len()); state.path_buf.push(b'/'); - state.path_buf.extend(normalize_ns_prefix(&nsres)); + state.path_buf.extend(wellknown_prefix_from_nsres(&nsres)); state.path_buf.extend(localname.as_ref()); state.current_start = Some(start.into_owned()); return Ok(SubTreeReader { @@ -172,7 +171,7 @@ impl SubTreeReader<'_, '_, R> { match self.reader.read_event_into(&mut self.state.buf1) { Ok(Event::Start(start)) => { let (nsres, localname) = self.reader.resolve_element(start.name()); - let ns = normalize_ns_prefix(&nsres); + let ns = wellknown_prefix_from_nsres(&nsres); // Append "/{ns_prefix}:{localname}" to the path stack self.state @@ -215,7 +214,7 @@ impl SubTreeReader<'_, '_, R> { self.state.buf1.push(b'@'); for attr in start.attributes().flatten() { let (nsres, localname) = self.reader.resolve_attribute(attr.key); - self.state.buf1.extend(normalize_ns_prefix(&nsres)); + self.state.buf1.extend(wellknown_prefix_from_nsres(&nsres)); self.state.buf1.extend(localname.as_ref()); logic( self.state.buf1.as_ref(), // attribute path "@nsprefix:name" @@ -251,8 +250,18 @@ impl SubTreeReader<'_, '_, R> { Ok(()) } + /// Gets the current sub-tree path to the current element. pub fn current_path(&self) -> &[u8] { - &self.state.path_buf[self.path_start + 1..] + if self.path_start + 1 < self.state.path_buf.len() { + &self.state.path_buf[self.path_start + 1..] + } else { + b"" + } + } + + /// Gets the current absolute path from the root to the current element. + pub fn current_absolute_path(&self) -> &[u8] { + &self.state.path_buf } /// Expect a XML text content and return it. @@ -325,7 +334,7 @@ impl SubTreeReader<'_, '_, R> { ) -> Result<(), ParseError> { let poly_begin = self.state.geometry_collector.multipolygon.len(); - if expect_start(self.reader, &mut self.state.buf1, GML_NS, b"MultiSurface")? { + if expect_start(self.reader, &mut self.state.buf1, GML31_NS, b"MultiSurface")? { self.parse_multi_surface()?; expect_end(self.reader, &mut self.state.buf1)?; } @@ -345,7 +354,7 @@ impl SubTreeReader<'_, '_, R> { fn parse_solid_prop(&mut self, geomrefs: &mut GeometryRef, lod: u8) -> Result<(), ParseError> { let poly_begin = self.state.geometry_collector.multipolygon.len(); - if expect_start(self.reader, &mut self.state.buf1, GML_NS, b"Solid")? { + if expect_start(self.reader, &mut self.state.buf1, GML31_NS, b"Solid")? { self.parse_solid()?; expect_end(self.reader, &mut self.state.buf1)?; } @@ -374,22 +383,22 @@ impl SubTreeReader<'_, '_, R> { let poly_begin = self.state.geometry_collector.multipolygon.len(); let geomtype = match (nsres, localname.as_ref()) { - (Bound(GML_NS), b"Solid") => { + (Bound(GML31_NS), b"Solid") => { self.parse_solid()?; GeometryType::Solid } - (Bound(GML_NS), b"MultiSurface") => { + (Bound(GML31_NS), b"MultiSurface") => { self.parse_multi_surface()?; GeometryType::Surface } - (Bound(GML_NS), b"CompositeSurface") => { + (Bound(GML31_NS), b"CompositeSurface") => { self.parse_composite_surface()?; GeometryType::Surface } - (Bound(GML_NS), b"OrientableSurface") => todo!(), - (Bound(GML_NS), b"Polygon") => todo!(), - (Bound(GML_NS), b"TriangulatedSurface") => todo!(), - (Bound(GML_NS), b"Tin") => todo!(), + (Bound(GML31_NS), b"OrientableSurface") => todo!(), + (Bound(GML31_NS), b"Polygon") => todo!(), + (Bound(GML31_NS), b"TriangulatedSurface") => todo!(), + (Bound(GML31_NS), b"Tin") => todo!(), _ => { return Err(ParseError::SchemaViolation(format!( "Unexpected element <{}>", @@ -433,10 +442,10 @@ impl SubTreeReader<'_, '_, R> { Ok(Event::Start(start)) => { let (nsres, localname) = self.reader.resolve_element(start.name()); match (nsres, localname.as_ref()) { - (Bound(GML_NS), b"TriangulatedSurface") => { + (Bound(GML31_NS), b"TriangulatedSurface") => { self.parse_triangulated_surface()? } - (Bound(GML_NS), b"Tin") => self.parse_triangulated_surface()?, + (Bound(GML31_NS), b"Tin") => self.parse_triangulated_surface()?, _ => { return Err(ParseError::SchemaViolation(format!( "Unexpected element <{}>", @@ -469,7 +478,7 @@ impl SubTreeReader<'_, '_, R> { } fn parse_solid(&mut self) -> Result<(), ParseError> { - if expect_start(self.reader, &mut self.state.buf1, GML_NS, b"exterior")? { + if expect_start(self.reader, &mut self.state.buf1, GML31_NS, b"exterior")? { self.parse_surface_prop()?; expect_end(self.reader, &mut self.state.buf1)?; } @@ -480,7 +489,7 @@ impl SubTreeReader<'_, '_, R> { if expect_start( self.reader, &mut self.state.buf1, - GML_NS, + GML31_NS, b"trianglePatches", )? { self.parse_triangle_patch_array()?; @@ -495,7 +504,7 @@ impl SubTreeReader<'_, '_, R> { Ok(Event::Start(start)) => { let (nsres, localname) = self.reader.resolve_element(start.name()); match (nsres, localname.as_ref()) { - (Bound(GML_NS), b"Triangle") => self.parse_polygon()?, + (Bound(GML31_NS), b"Triangle") => self.parse_polygon()?, _ => { return Err(ParseError::SchemaViolation(format!( "Unexpected element <{}>", @@ -522,7 +531,7 @@ impl SubTreeReader<'_, '_, R> { Ok(Event::Start(start)) => { let (nsres, localname) = self.reader.resolve_element(start.name()); match (nsres, localname.as_ref()) { - (Bound(GML_NS), b"surfaceMember") => self.parse_surface_prop()?, + (Bound(GML31_NS), b"surfaceMember") => self.parse_surface_prop()?, _ => return Err(ParseError::SchemaViolation("Unexpected element".into())), } } @@ -544,7 +553,7 @@ impl SubTreeReader<'_, '_, R> { Ok(Event::Start(start)) => { let (nsres, localname) = self.reader.resolve_element(start.name()); match (nsres, localname.as_ref()) { - (Bound(GML_NS), b"surfaceMember") => self.parse_surface_prop()?, + (Bound(GML31_NS), b"surfaceMember") => self.parse_surface_prop()?, _ => { return Err(ParseError::SchemaViolation(format!( "Unexpected element <{}>", @@ -571,9 +580,9 @@ impl SubTreeReader<'_, '_, R> { Ok(Event::Start(start)) => { let (nsres, localname) = self.reader.resolve_element(start.name()); match (nsres, localname.as_ref()) { - (Bound(GML_NS), b"Polygon") => self.parse_polygon()?, - (Bound(GML_NS), b"CompositeSurface") => self.parse_composite_surface()?, - (Bound(GML_NS), b"OrientableSurface") => { + (Bound(GML31_NS), b"Polygon") => self.parse_polygon()?, + (Bound(GML31_NS), b"CompositeSurface") => self.parse_composite_surface()?, + (Bound(GML31_NS), b"OrientableSurface") => { // TODO: OrientableSurface println!("OrientableSurface is not supported"); self.reader @@ -607,7 +616,7 @@ impl SubTreeReader<'_, '_, R> { let mut is_exterior = true; loop { match self.reader.read_resolved_event_into(&mut self.state.buf1) { - Ok((Bound(GML_NS), Event::Start(start))) => { + Ok((Bound(GML31_NS), Event::Start(start))) => { depth += 1; match (depth, start.local_name().as_ref()) { (2, b"exterior") => { diff --git a/nusamai-citygml/src/values.rs b/nusamai-citygml/src/values.rs index 7aae10a4a..f899cad9d 100644 --- a/nusamai-citygml/src/values.rs +++ b/nusamai-citygml/src/values.rs @@ -1,11 +1,13 @@ -use crate::object::Value; +use crate::object::{self, Value}; use crate::parser::{ParseError, SubTreeReader}; use crate::{CityGMLElement, ElementType}; pub use chrono::NaiveDate; use serde::{Deserialize, Serialize}; use std::io::BufRead; +// type aliases pub type Date = chrono::NaiveDate; +pub type Length = Measure; // Length is almost same as Measure pub type GYear = String; // TODO? pub type GYearMonth = String; // TODO? pub type MeasureOrNullList = String; // TODO? @@ -26,12 +28,12 @@ impl CityGMLElement for String { } } -#[derive(Debug, serde::Serialize, serde::Deserialize, Default)] +#[derive(Debug, serde::Serialize, serde::Deserialize, Default, PartialEq, Eq)] pub struct URI(String); impl URI { - pub fn new(s: String) -> Self { - Self(s) + pub fn new(s: &str) -> Self { + Self(s.into()) } pub fn value(&self) -> &String { &self.0 @@ -212,8 +214,6 @@ pub struct Measure { // pub uom: Option, } -pub type Length = Measure; - impl CityGMLElement for Measure { const ELEMENT_TYPE: ElementType = ElementType::BasicType; @@ -233,7 +233,7 @@ impl CityGMLElement for Measure { } fn into_object(self) -> Option { - Some(Value::Measure(self.value)) + Some(Value::Measure(self)) } } @@ -260,7 +260,7 @@ impl CityGMLElement for Date { } } -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Default, Deserialize, Serialize, PartialEq)] pub struct Point { // TODO } @@ -325,21 +325,3 @@ impl CityGMLElement for Vec { } } } - -pub trait CityGMLAttribute: Sized { - fn parse_attr_value(value: &str) -> Result; -} - -impl CityGMLAttribute for String { - #[inline] - fn parse_attr_value(value: &str) -> Result { - Ok(value.to_string()) - } -} - -impl CityGMLAttribute for Option { - #[inline] - fn parse_attr_value(value: &str) -> Result { - Ok(Some(::parse_attr_value(value)?)) - } -} diff --git a/nusamai-plateau/src/models/cityobjectgroup.rs b/nusamai-plateau/src/models/cityobjectgroup.rs index e66fb48dc..f8ca797ba 100644 --- a/nusamai-plateau/src/models/cityobjectgroup.rs +++ b/nusamai-plateau/src/models/cityobjectgroup.rs @@ -1,6 +1,46 @@ -use nusamai_citygml::{citygml_feature, CityGMLElement}; +use super::iur::uro; +use nusamai_citygml::{citygml_feature, CityGMLElement, Code, GYear}; #[citygml_feature(name = "grp:CityObjectGroup")] pub struct CityObjectGroup { - // .. + #[citygml(path = b"grp:class")] + pub class: Option, + + #[citygml(path = b"grp:function")] + pub function: Vec, + + #[citygml(path = b"grp:usage")] + pub usage: Vec, + + // + // TODO: not implemented yet + #[citygml(path = b"grp:groupMember")] + pub group_member: Vec, + // + // TODO: not implemented yet + #[citygml(path = b"grp:parent")] + pub parent: Option, + // + // TODO: + // #[citygml(path = b"grp:geometry")] + // pub geometry: Option, // -> gml:_Geometry + // + + #[citygml(path = b"uro:fiscalYearOfPublication")] + pub fiscal_year_of_publication: Vec, + + #[citygml(path = b"uro:ifcBuildingStoreyAttribute")] + pub ifc_building_storey_attribute: Vec, // -> uro:IfcAttribute + + #[citygml(path = b"uro:indoorStoreyAttribute")] + pub indoor_storey_attribute: Vec, // -> uro:IndoorAttribute + + #[citygml(path = b"uro:language")] + pub language: Vec, +} + +#[citygml_feature(name = "grp:_CityObjectOrRef")] +pub struct CityObjectOrRef { + #[citygml(path = b"@xlink:href")] + href: Option, } diff --git a/nusamai-plateau/src/models/core.rs b/nusamai-plateau/src/models/core.rs index 2d350e2ad..dc6a3b277 100644 --- a/nusamai-plateau/src/models/core.rs +++ b/nusamai-plateau/src/models/core.rs @@ -1,6 +1,7 @@ use nusamai_citygml::citygml_data; #[citygml_data(name = "core:Address")] +#[citygml(allow_extra)] pub struct Address { // TODO } diff --git a/nusamai-plateau/src/models/iur/uro/building.rs b/nusamai-plateau/src/models/iur/uro/building.rs index 2669c7094..ea169106d 100644 --- a/nusamai-plateau/src/models/iur/uro/building.rs +++ b/nusamai-plateau/src/models/iur/uro/building.rs @@ -151,6 +151,9 @@ pub struct BuildingDataQualityAttribute { #[citygml(path = b"uro:appearanceSrcDesc")] pub appearance_src_desc: Vec, + #[citygml(path = b"uro:lod1HeightType", required)] + pub lod1_height_type: Option, + #[citygml(path = b"uro:lodType")] pub lod_type: Vec, } diff --git a/nusamai-plateau/src/models/transportation.rs b/nusamai-plateau/src/models/transportation.rs index bbbf9d772..ac6d86084 100644 --- a/nusamai-plateau/src/models/transportation.rs +++ b/nusamai-plateau/src/models/transportation.rs @@ -22,6 +22,7 @@ pub struct Road { pub auxiliary_traffic_area: Vec, #[citygml(path = b"uro:tranDataQualityAttribute/uro:TransportationDataQualityAttribute")] + #[citygml(path = b"uro:roadDataQualityAttribute/uro:RoadDataQualityAttribute")] pub tran_data_quality_attribute: Option, #[citygml(path = b"uro:tranFacilityAttribute")] diff --git a/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_GeometrySrcDesc.xml b/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_GeometrySrcDesc.xml new file mode 100755 index 000000000..88cb410a4 --- /dev/null +++ b/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_GeometrySrcDesc.xml @@ -0,0 +1,64 @@ + + + RoadDataQualityAttribute_GeometrySrcDesc + + + 現地測量 + 1 + + + + + 地上レーザ測量 + 2 + + + + + 車載写真レーザ測量 + 3 + + + + + UAV写真測量 + 4 + + + + + 空中写真測量 + 5 + + + + + 既成図数値化 + 6 + + + + + 修正測量 + 7 + + + + + 航空レーザ測量 + 8 + + + + + 現地調査 + 9 + + + + + 推定 + 0 + + + diff --git a/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_appearanceSrcDesc.xml b/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_appearanceSrcDesc.xml new file mode 100755 index 000000000..65f5eaa91 --- /dev/null +++ b/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_appearanceSrcDesc.xml @@ -0,0 +1,28 @@ + + + RoadDataQualityAttribute_appearanceSrcDesc + + + 空中写真 + 1 + + + + + MMS画像 + 2 + + + + + 現地写真 + 3 + + + + + 疑似テクスチャ + 4 + + + diff --git a/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_geometrySrcDesc.xml b/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_geometrySrcDesc.xml new file mode 100755 index 000000000..88cb410a4 --- /dev/null +++ b/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_geometrySrcDesc.xml @@ -0,0 +1,64 @@ + + + RoadDataQualityAttribute_GeometrySrcDesc + + + 現地測量 + 1 + + + + + 地上レーザ測量 + 2 + + + + + 車載写真レーザ測量 + 3 + + + + + UAV写真測量 + 4 + + + + + 空中写真測量 + 5 + + + + + 既成図数値化 + 6 + + + + + 修正測量 + 7 + + + + + 航空レーザ測量 + 8 + + + + + 現地調査 + 9 + + + + + 推定 + 0 + + + diff --git a/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_srcScale.xml b/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_srcScale.xml new file mode 100755 index 000000000..f200c8042 --- /dev/null +++ b/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_srcScale.xml @@ -0,0 +1,22 @@ + + + RoadDataQualityAttribute_srcScale + + + 地図情報レベル2500 + 1 + + + + + 地図情報レベル1000 + 2 + + + + + 地図情報レベル500 + 3 + + + diff --git a/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_thematicSrcDesc.xml b/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_thematicSrcDesc.xml new file mode 100755 index 000000000..1b46df844 --- /dev/null +++ b/nusamai-plateau/tests/data/numazu-shi/codelists/RoadDataQualityAttribute_thematicSrcDesc.xml @@ -0,0 +1,52 @@ + + + RoadDataQualityAttribute_thematicSrcDesc + + + 都市計画基礎調査 + 1 + + + + + 道路基盤地図情報 + 2 + + + + + 道路台帳 + 3 + + + + + 道路施設台帳 + 4 + + + + + 統計調査 + 5 + + + + + 写真判読 + 6 + + + + + 現地調査 + 7 + + + + + GISデータ演算 + 8 + + + diff --git a/nusamai-plateau/tests/test_building_lod4.rs b/nusamai-plateau/tests/test_building_lod4.rs index 10553faf8..354efd21c 100644 --- a/nusamai-plateau/tests/test_building_lod4.rs +++ b/nusamai-plateau/tests/test_building_lod4.rs @@ -54,7 +54,7 @@ fn example_toplevel_dispatcher( } #[test] -fn simple_read() { +fn read_building_lod4() { let reader = std::io::BufReader::new( zstd::stream::Decoder::new( std::fs::File::open(