ecitygml-io 0.0.2-alpha.5

IO operations for processing CityGML data.
Documentation
use crate::Error;
use crate::gml::codec::generics::GmlGenericAttributeProperty;
use crate::gml::util::CityGmlElement;
use ecitygml_core::model::core::CityObjectRelation;
use ecitygml_core::model::generics::GenericAttributeKind;
use egml::io::codec::base::GmlReference;
use egml::io::codec::basic::GmlCode;
use egml::io::util::{Formatting, XmlNode, XmlNodeContent, XmlNodeParts, serialize_inner};
use egml::model::basic_types::Code;
use quick_xml::de;
use serde::{Deserialize, Serialize};

pub fn deserialize_city_object_relation(xml_document: &[u8]) -> Result<CityObjectRelation, Error> {
    let parsed: GmlCityObjectRelation = de::from_reader(xml_document)?;
    parsed.try_into()
}

pub fn serialize_city_object_relation(
    relation: &CityObjectRelation,
    formatting: Formatting,
) -> Result<XmlNode, Error> {
    let mut parts = XmlNodeParts::empty();
    if let Some(raw) = serialize_inner(GmlCityObjectRelation::from(relation), formatting)? {
        parts.content.push(XmlNodeContent::Raw(raw));
    }

    Ok(XmlNode::new(
        CityGmlElement::CityObjectRelation.into(),
        parts,
    ))
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct GmlCityObjectRelation {
    #[serde(rename = "relationType")]
    pub relation_type: GmlCode,

    #[serde(rename = "relatedTo")]
    pub related_to: GmlReference,

    #[serde(rename = "genericAttribute", default)]
    pub generic_attributes: Vec<GmlGenericAttributeProperty>,
}

impl TryFrom<GmlCityObjectRelation> for CityObjectRelation {
    type Error = Error;

    fn try_from(item: GmlCityObjectRelation) -> Result<Self, Self::Error> {
        let generic_attributes: Result<Vec<GenericAttributeKind>, _> = item
            .generic_attributes
            .into_iter()
            .map(|p| GenericAttributeKind::try_from(p.content))
            .collect();

        let mut relation = CityObjectRelation::new(
            Code::from(item.relation_type).into(),
            item.related_to.try_into()?,
        );
        relation.set_generic_attributes(generic_attributes?);
        Ok(relation)
    }
}

impl From<&CityObjectRelation> for GmlCityObjectRelation {
    fn from(item: &CityObjectRelation) -> Self {
        Self {
            relation_type: item.relation_type().code().into(),
            related_to: item.relation_to().into(),
            generic_attributes: item
                .generic_attributes()
                .iter()
                .map(|x| GmlGenericAttributeProperty { content: x.into() })
                .collect(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use egml::model::base::HasAssociationAttributes;
    use egml::model::xlink::HRef;
    use quick_xml::de;

    fn full_xml() -> &'static str {
        "<CityObjectRelation>\
          <genericAttribute>\
            <StringAttribute>\
              <name>identifier_roadObjectId</name>\
              <value>4970002</value>\
            </StringAttribute>\
          </genericAttribute>\
          <genericAttribute>\
            <StringAttribute>\
              <name>identifier_roadObjectName</name>\
              <value>tree</value>\
            </StringAttribute>\
          </genericAttribute>\
          <genericAttribute>\
            <StringAttribute>\
              <name>identifier_roadId</name>\
              <value>1970000</value>\
            </StringAttribute>\
          </genericAttribute>\
          <relationType>relatedVegetation</relationType>\
          <relatedTo href=\"#UUID_e8b543d5-43f3-3b07-94ee-c8e76419b29f\"/>\
        </CityObjectRelation>"
    }

    #[test]
    fn test_deserialize_gml_city_object_relation() {
        let gml: GmlCityObjectRelation =
            de::from_reader(full_xml().as_ref()).expect("should parse");

        assert_eq!(gml.relation_type.value, "relatedVegetation");
        assert_eq!(
            gml.related_to.association.href,
            Some("#UUID_e8b543d5-43f3-3b07-94ee-c8e76419b29f".to_string())
        );
        assert_eq!(gml.generic_attributes.len(), 3);
    }

    #[test]
    fn test_convert_to_city_object_relation() {
        let gml: GmlCityObjectRelation =
            de::from_reader(full_xml().as_ref()).expect("should parse");
        let relation: CityObjectRelation = gml.try_into().expect("should convert");

        assert_eq!(relation.relation_type().value(), "relatedVegetation");
        assert_eq!(
            relation.relation_to().href().expect("should have href"),
            &HRef::from("#UUID_e8b543d5-43f3-3b07-94ee-c8e76419b29f")
        );
        assert_eq!(relation.generic_attributes().len(), 3);
    }

    #[test]
    fn test_generic_attribute_names() {
        let gml: GmlCityObjectRelation =
            de::from_reader(full_xml().as_ref()).expect("should parse");
        let relation: CityObjectRelation = gml.try_into().expect("should convert");

        let names: Vec<&str> = relation
            .generic_attributes()
            .iter()
            .map(|a| a.name())
            .collect();

        assert!(names.contains(&"identifier_roadObjectId"));
        assert!(names.contains(&"identifier_roadObjectName"));
        assert!(names.contains(&"identifier_roadId"));
    }

    #[test]
    fn test_deserialize_without_generic_attributes() {
        let xml = "<CityObjectRelation>\
          <relationType>adjecent</relationType>\
          <relatedTo href=\"#UUID_abc123\"/>\
        </CityObjectRelation>";

        let gml: GmlCityObjectRelation = de::from_reader(xml.as_ref()).expect("should parse");
        let relation: CityObjectRelation = gml.try_into().expect("should convert");

        assert_eq!(relation.relation_type().value(), "adjecent");
        assert_eq!(
            relation.relation_to().href().expect("should have an href"),
            &HRef::from("#UUID_abc123")
        );
        assert!(relation.generic_attributes().is_empty());
    }

    #[test]
    fn test_missing_relation_type_returns_error() {
        let xml = "<CityObjectRelation>\
          <relatedTo href=\"#UUID_abc123\"/>\
        </CityObjectRelation>";

        let result: Result<GmlCityObjectRelation, _> = de::from_reader(xml.as_ref());
        assert!(result.is_err());
    }

    #[test]
    fn test_round_trip() {
        let gml: GmlCityObjectRelation =
            de::from_reader(full_xml().as_ref()).expect("should parse");
        let relation: CityObjectRelation = gml.try_into().expect("should convert");
        let gml_back = GmlCityObjectRelation::from(&relation);

        assert_eq!(gml_back.relation_type.value, "relatedVegetation");
        assert_eq!(
            gml_back
                .related_to
                .association
                .href
                .expect("should have an href")
                .as_str(),
            "#UUID_e8b543d5-43f3-3b07-94ee-c8e76419b29f"
        );
        assert_eq!(gml_back.generic_attributes.len(), 3);
    }
}