ecitygml-io 0.0.2-alpha.5

IO operations for processing CityGML data.
Documentation
use crate::Error;
use crate::gml::codec::core::abstract_point_cloud_property::{
    deserialize_abstract_point_cloud_property, serialize_abstract_point_cloud_property,
};
use crate::gml::codec::core::qualified_area_property::GmlAreaProperty;
use crate::gml::codec::core::{
    deserialize_abstract_space_boundary, serialize_abstract_space_boundary,
};
use crate::gml::util::{CityGmlElement, CombinedCityGmlElement, collect_gml_child_lenient};
use ecitygml_core::model::core::{
    AbstractThematicSurface, AsAbstractSpaceBoundary, AsAbstractThematicSurface,
    AsAbstractThematicSurfaceMut,
};
use egml::io::codec::geometry::aggregates::{
    deserialize_multi_curve_property, deserialize_multi_surface_property,
    serialize_multi_curve_property, serialize_multi_surface_property,
};
use egml::io::util::{
    Formatting, XmlElementSpans, XmlNodeContent, XmlNodeParts, collect_child, serialize_inner,
};
use quick_xml::de;
use serde::{Deserialize, Serialize};

pub fn deserialize_abstract_thematic_surface(
    xml_document: &[u8],
    spans: &XmlElementSpans<CombinedCityGmlElement>,
) -> Result<AbstractThematicSurface, Error> {
    let mut abstract_space_boundary_result = None;
    let mut parsed_result = None;
    let mut lod0_multi_surface_result = None;
    let mut lod1_multi_surface_result = None;
    let mut lod2_multi_surface_result = None;
    let mut lod3_multi_surface_result = None;
    let mut lod0_multi_curve_result = None;
    let mut point_cloud_result = None;

    rayon::scope(|s| {
        s.spawn(|_| {
            abstract_space_boundary_result =
                Some(deserialize_abstract_space_boundary(xml_document, spans));
        });
        s.spawn(|_| {
            parsed_result = Some(
                de::from_reader::<_, GmlAbstractThematicSurface>(xml_document).map_err(Error::from),
            );
        });
        s.spawn(|_| {
            lod0_multi_surface_result = Some(collect_gml_child_lenient(
                xml_document,
                spans,
                CityGmlElement::Lod0MultiSurfaceProperty.into(),
                deserialize_multi_surface_property,
            ));
        });
        s.spawn(|_| {
            lod1_multi_surface_result = Some(collect_gml_child_lenient(
                xml_document,
                spans,
                CityGmlElement::Lod1MultiSurfaceProperty.into(),
                deserialize_multi_surface_property,
            ));
        });
        s.spawn(|_| {
            lod2_multi_surface_result = Some(collect_gml_child_lenient(
                xml_document,
                spans,
                CityGmlElement::Lod2MultiSurfaceProperty.into(),
                deserialize_multi_surface_property,
            ));
        });
        s.spawn(|_| {
            lod3_multi_surface_result = Some(collect_gml_child_lenient(
                xml_document,
                spans,
                CityGmlElement::Lod3MultiSurfaceProperty.into(),
                deserialize_multi_surface_property,
            ));
        });
        s.spawn(|_| {
            lod0_multi_curve_result = Some(collect_gml_child_lenient(
                xml_document,
                spans,
                CityGmlElement::Lod0MultiCurveProperty.into(),
                deserialize_multi_curve_property,
            ));
        });
        s.spawn(|_| {
            point_cloud_result = Some(collect_child(
                xml_document,
                spans,
                CityGmlElement::AbstractPointCloudProperty.into(),
                deserialize_abstract_point_cloud_property,
            ));
        });
    });

    let abstract_space_boundary =
        abstract_space_boundary_result.expect("rayon::scope guarantees all spawns complete")?;
    let parsed = parsed_result.expect("rayon::scope guarantees all spawns complete")?;
    let lod0_multi_surface =
        lod0_multi_surface_result.expect("rayon::scope guarantees all spawns complete");
    let lod1_multi_surface =
        lod1_multi_surface_result.expect("rayon::scope guarantees all spawns complete");
    let lod2_multi_surface =
        lod2_multi_surface_result.expect("rayon::scope guarantees all spawns complete");
    let lod3_multi_surface =
        lod3_multi_surface_result.expect("rayon::scope guarantees all spawns complete");
    let lod0_multi_curve =
        lod0_multi_curve_result.expect("rayon::scope guarantees all spawns complete");
    let point_cloud = point_cloud_result.expect("rayon::scope guarantees all spawns complete")?;

    let mut abstract_thematic_surface =
        AbstractThematicSurface::from_abstract_space_boundary(abstract_space_boundary);
    abstract_thematic_surface.set_areas(parsed.areas.into_iter().map(Into::into).collect());
    abstract_thematic_surface.set_lod0_multi_surface(lod0_multi_surface);
    abstract_thematic_surface.set_lod1_multi_surface(lod1_multi_surface);
    abstract_thematic_surface.set_lod2_multi_surface(lod2_multi_surface);
    abstract_thematic_surface.set_lod3_multi_surface(lod3_multi_surface);
    abstract_thematic_surface.set_lod0_multi_curve(lod0_multi_curve);
    abstract_thematic_surface.set_point_cloud(point_cloud);

    Ok(abstract_thematic_surface)
}

pub fn serialize_abstract_thematic_surface(
    abstract_thematic_surface: &AbstractThematicSurface,
    formatting: Formatting,
) -> Result<XmlNodeParts, Error> {
    let mut xml_node_parts = serialize_abstract_space_boundary(
        abstract_thematic_surface.abstract_space_boundary(),
        formatting,
    )?;

    if let Some(raw) = serialize_inner(
        GmlAbstractThematicSurface::from(abstract_thematic_surface),
        formatting,
    )? {
        xml_node_parts.content.push(XmlNodeContent::Raw(raw));
    }

    if let Some(prop) = abstract_thematic_surface.lod0_multi_surface() {
        xml_node_parts.content.push(
            serialize_multi_surface_property(
                prop,
                formatting,
                CityGmlElement::Lod0MultiSurfaceProperty.into(),
            )
            .map_err(Error::from)?
            .into(),
        );
    }
    if let Some(prop) = abstract_thematic_surface.lod1_multi_surface() {
        xml_node_parts.content.push(
            serialize_multi_surface_property(
                prop,
                formatting,
                CityGmlElement::Lod1MultiSurfaceProperty.into(),
            )
            .map_err(Error::from)?
            .into(),
        );
    }
    if let Some(prop) = abstract_thematic_surface.lod2_multi_surface() {
        xml_node_parts.content.push(
            serialize_multi_surface_property(
                prop,
                formatting,
                CityGmlElement::Lod2MultiSurfaceProperty.into(),
            )
            .map_err(Error::from)?
            .into(),
        );
    }
    if let Some(prop) = abstract_thematic_surface.lod3_multi_surface() {
        xml_node_parts.content.push(
            serialize_multi_surface_property(
                prop,
                formatting,
                CityGmlElement::Lod3MultiSurfaceProperty.into(),
            )
            .map_err(Error::from)?
            .into(),
        );
    }
    if let Some(prop) = abstract_thematic_surface.lod0_multi_curve() {
        xml_node_parts.content.push(
            serialize_multi_curve_property(
                prop,
                formatting,
                CityGmlElement::Lod0MultiCurveProperty.into(),
            )
            .map_err(Error::from)?
            .into(),
        );
    }
    xml_node_parts.content.extend(
        abstract_thematic_surface
            .point_cloud()
            .iter()
            .map(|x| {
                serialize_abstract_point_cloud_property(x, formatting).map(XmlNodeContent::from)
            })
            .collect::<Result<Vec<_>, _>>()?,
    );

    Ok(xml_node_parts)
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct GmlAbstractThematicSurface {
    #[serde(rename = "area", default)]
    pub areas: Vec<GmlAreaProperty>,
}

impl From<&AbstractThematicSurface> for GmlAbstractThematicSurface {
    fn from(item: &AbstractThematicSurface) -> Self {
        Self {
            areas: item.areas().iter().map(GmlAreaProperty::from).collect(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ecitygml_core::model::core::{
        AsAbstractCityObject, AsAbstractFeature, AsAbstractThematicSurface,
    };
    use egml::io::util::extract_xml_element_spans;
    use egml::model::base::Id;

    #[test]
    fn test_deserialize_with_lod2_multi_surface() {
        let xml_document = b"\
<con:WallSurface gml:id=\"test-id-123\">
  <gml:name>Outer Wall 1 (West)</gml:name>
  <lod2MultiSurface>
    <gml:MultiSurface>
      <gml:surfaceMember>
        <gml:Polygon gml:id=\"PolyID7350_878_759628_120742\">
          <gml:exterior>
            <gml:LinearRing gml:id=\"PolyID7350_878_759628_120742_0\">
              <gml:pos>457842.0 5439088.0 118.317691453624</gml:pos>
              <gml:pos>457842.0 5439093.0 115.430940107676</gml:pos>
              <gml:pos>457842.0 5439093.0 111.8</gml:pos>
              <gml:pos>457842.0 5439083.0 111.8</gml:pos>
              <gml:pos>457842.0 5439083.0 115.430940107676</gml:pos>
              <gml:pos>457842.0 5439088.0 118.317691453624</gml:pos>
            </gml:LinearRing>
          </gml:exterior>
        </gml:Polygon>
      </gml:surfaceMember>
    </gml:MultiSurface>
  </lod2MultiSurface>
</con:WallSurface>";

        let spans = extract_xml_element_spans(xml_document).expect("should work");
        let abstract_thematic_surface =
            deserialize_abstract_thematic_surface(xml_document, &spans).expect("should work");

        assert_eq!(
            abstract_thematic_surface.feature_id(),
            &Id::try_from("test-id-123").expect("should work")
        );
        assert!(abstract_thematic_surface.lod2_multi_surface().is_some());
        assert_eq!(abstract_thematic_surface.generic_attributes().len(), 0);
    }

    #[test]
    fn test_deserialize_abstract_thematic_surface_with_area() {
        let xml_document = b"\
<con:WallSurface gml:id=\"test-id-123\">
  <area>
    <QualifiedArea>
      <area uom=\"m2\">5.0</area>
      <typeOfArea>RoadArea</typeOfArea>
    </QualifiedArea>
  </area>
</con:WallSurface>";

        let spans = extract_xml_element_spans(xml_document).expect("should work");
        let abstract_thematic_surface =
            deserialize_abstract_thematic_surface(xml_document, &spans).expect("should work");

        assert_eq!(abstract_thematic_surface.areas().len(), 1);

        let area = &abstract_thematic_surface.areas()[0];
        assert_eq!(area.area().value(), 5.0);
        assert_eq!(area.area().uom(), "m2");
        assert_eq!(area.type_of_area().code().value(), "RoadArea");
    }

    #[test]
    fn test_serialize_abstract_thematic_surface_with_area() {
        let xml_document = b"\
<con:WallSurface gml:id=\"test-id-123\">
  <area>
    <QualifiedArea>
      <area uom=\"m2\">5.0</area>
      <typeOfArea>WallAreaMeasured</typeOfArea>
    </QualifiedArea>
  </area>
</con:WallSurface>";

        let spans = extract_xml_element_spans(xml_document).expect("should work");
        let abstract_thematic_surface =
            deserialize_abstract_thematic_surface(xml_document, &spans).expect("should work");

        let xml_node_parts =
            serialize_abstract_thematic_surface(&abstract_thematic_surface, Formatting::Compact)
                .expect("should serialize");
        let xml = egml::io::util::XmlNode::new("con:WallSurface", xml_node_parts)
            .to_string(Formatting::Compact)
            .expect("should convert to string");

        assert!(xml.contains("<area>") || xml.contains("<area "));
        assert!(xml.contains("QualifiedArea"));
        assert!(xml.contains("uom=\"m2\""));
        assert!(xml.contains('5'));
        assert!(xml.contains("typeOfArea"));
        assert!(xml.contains("WallAreaMeasured"));
    }

    #[test]
    fn test_round_trip_abstract_thematic_surface_with_area() {
        let xml_document = b"\
<con:WallSurface gml:id=\"test-id-123\">
  <area>
    <QualifiedArea>
      <area uom=\"m2\">5.0</area>
      <typeOfArea>WallAreaMeasured</typeOfArea>
    </QualifiedArea>
  </area>
</con:WallSurface>";

        let spans = extract_xml_element_spans(xml_document).expect("should work");
        let abstract_thematic_surface =
            deserialize_abstract_thematic_surface(xml_document, &spans).expect("should work");

        let xml_node_parts =
            serialize_abstract_thematic_surface(&abstract_thematic_surface, Formatting::Compact)
                .expect("should serialize");
        let xml = egml::io::util::XmlNode::new("con:WallSurface", xml_node_parts)
            .to_string(Formatting::Compact)
            .expect("should convert to string");

        let round_tripped_spans = extract_xml_element_spans(xml.as_bytes()).expect("should work");
        let round_tripped =
            deserialize_abstract_thematic_surface(xml.as_bytes(), &round_tripped_spans)
                .expect("should work");

        assert_eq!(abstract_thematic_surface.areas(), round_tripped.areas());
    }
}