ecitygml-io 0.0.2-alpha.5

IO operations for processing CityGML data.
Documentation
use crate::Error;
use crate::gml::codec::transportation::abstract_transportation_space::{
    deserialize_abstract_transportation_space, serialize_abstract_transportation_space,
};
use crate::gml::util::CityGmlElement;
use ecitygml_core::model::transportation::values::IntersectionClassValue;
use ecitygml_core::model::transportation::{AsAbstractTransportationSpace, Intersection};
use egml::io::codec::basic::GmlCode;
use egml::io::util::{
    Formatting, XmlNode, XmlNodeContent, extract_xml_element_spans, serialize_inner,
};
use egml::model::basic_types::Code;
use quick_xml::de;
use serde::{Deserialize, Serialize};

pub fn deserialize_intersection(xml_document: &[u8]) -> Result<Intersection, Error> {
    let spans = extract_xml_element_spans(xml_document)?;
    let (abstract_transportation_space_result, parsed_result) = rayon::join(
        || deserialize_abstract_transportation_space(xml_document, &spans),
        || de::from_reader::<_, GmlIntersection>(xml_document).map_err(Error::from),
    );
    let abstract_transportation_space = abstract_transportation_space_result?;
    let parsed = parsed_result?;

    let mut intersection =
        Intersection::from_abstract_transportation_space(abstract_transportation_space);
    intersection.set_class_opt(
        parsed
            .class
            .map(Code::from)
            .map(IntersectionClassValue::from),
    );

    Ok(intersection)
}

pub fn serialize_intersection(
    intersection: &Intersection,
    formatting: Formatting,
) -> Result<XmlNode, Error> {
    let mut xml_node_parts = serialize_abstract_transportation_space(
        intersection.abstract_transportation_space(),
        formatting,
    )?;

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

    Ok(XmlNode::new(
        CityGmlElement::Intersection.into(),
        xml_node_parts,
    ))
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct GmlIntersection {
    #[serde(
        rename(serialize = "tran:class", deserialize = "class"),
        skip_serializing_if = "Option::is_none"
    )]
    pub class: Option<GmlCode>,
}

impl From<&Intersection> for GmlIntersection {
    fn from(item: &Intersection) -> Self {
        Self {
            class: item
                .class()
                .map(IntersectionClassValue::code)
                .map(Into::into),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ecitygml_core::model::core::{
        AbstractSpaceBoundaryKind, AbstractThematicSurfaceKind, AsAbstractCityObject,
        AsAbstractFeature, AsAbstractSpace,
    };
    use ecitygml_core::model::transportation::{
        AsAbstractTransportationSpace, AuxiliaryTrafficArea, GranularityValue, TrafficArea,
    };
    use egml::model::base::Id;

    #[test]
    fn test_deserialize_basic_intersection() {
        let xml_document = b"
        <tran:Intersection gml:id=\"UUID_9a9fc5a0-b252-3d63-ac79-b3141175f152\">
          <genericAttribute>
            <gen:StringAttribute>
              <gen:name>identifier_new_section</gen:name>
              <gen:value>abc</gen:value>
            </gen:StringAttribute>
          </genericAttribute>
          <tran:trafficSpace>
            <tran:TrafficSpace gml:id=\"UUID_ff91145b-98e8-388b-b4d1-b94624f806db\">
              <genericAttribute>
                <gen:StringAttribute>
                  <gen:name>identifier_roadId</gen:name>
                  <gen:value>4516050</gen:value>
                </gen:StringAttribute>
              </genericAttribute>
              <tran:function>2</tran:function>
              <tran:usage>1</tran:usage>
              <tran:granularity>lane</tran:granularity>
              <tran:trafficDirection>forwards</tran:trafficDirection>
              <tran:predecessor xlink:href=\"#UUID_35e15191-e911-320b-bbe7-004b237a024a\"/>
              <tran:successor xlink:href=\"#UUID_efb6f11e-0d92-3d1f-8208-7ddbea02e829\"/>
            </tran:TrafficSpace>
          </tran:trafficSpace>
        </tran:Intersection>";

        let intersection = deserialize_intersection(xml_document).expect("should work");

        assert_eq!(
            intersection.feature_id(),
            &Id::try_from("UUID_9a9fc5a0-b252-3d63-ac79-b3141175f152").expect("should work")
        );

        assert!(intersection.lod2_multi_surface().is_none());
        assert_eq!(intersection.generic_attributes().len(), 1);
        assert!(intersection.auxiliary_traffic_spaces().is_empty());
        assert_eq!(intersection.traffic_spaces().len(), 1);
        let traffic_space = intersection
            .traffic_spaces()
            .first()
            .unwrap()
            .object()
            .unwrap();

        assert_eq!(
            traffic_space.feature_id(),
            &Id::try_from("UUID_ff91145b-98e8-388b-b4d1-b94624f806db").expect("should work")
        );
        assert_eq!(traffic_space.granularity(), &GranularityValue::Lane);

        let traffic_areas: Vec<&TrafficArea> = traffic_space
            .boundaries()
            .iter()
            .flat_map(|x| x.object())
            .filter_map(|x| match x {
                AbstractSpaceBoundaryKind::AbstractThematicSurfaceKind(
                    AbstractThematicSurfaceKind::TrafficArea(x),
                ) => Some(x),
                _ => None,
            })
            .collect();
        assert!(traffic_areas.is_empty());
    }
}