ecitygml-io 0.0.2-alpha.5

IO operations for processing CityGML data.
Documentation
use crate::Error;
use crate::gml::codec::core::abstract_appearance_property::{
    deserialize_abstract_appearance_property, serialize_abstract_appearance_property,
};
use crate::gml::codec::core::transformation_matrix_4x4::GmlTransformationMatrix4x4;
use crate::gml::util::{CityGmlElement, collect_gml_child};
use ecitygml_core::model::core::ImplicitGeometry;
use ecitygml_core::model::core::values::MimeTypeValue;
use egml::io::codec::basic::GmlCode;
use egml::io::codec::geometry::primitives::{deserialize_point_property, serialize_point_property};
use egml::io::codec::geometry::{
    deserialize_abstract_geometry_property, serialize_abstract_geometry_property,
};
use egml::io::util::{
    Formatting, XmlNode, XmlNodeContent, XmlNodeParts, collect_children, extract_xml_element_spans,
    serialize_inner,
};
use egml::model::base::Id;
use egml::model::basic_types::Code;
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicU64, Ordering};

static NEXT_ID: AtomicU64 = AtomicU64::new(0);
fn generate_object_id() -> Id {
    let n = NEXT_ID.fetch_add(1, Ordering::Relaxed);
    Id::try_from(format!("ecitygml-gen-object-{n}"))
        .expect("ecitygml-gen-object-{n} is always a valid xsd:ID")
}

pub fn deserialize_implicit_geometry(xml_document: &[u8]) -> Result<ImplicitGeometry, Error> {
    let spans = extract_xml_element_spans(xml_document)?;

    let mut parsed_result = None;
    let mut reference_point_result = None;
    let mut relative_geometry_result = None;
    let mut appearances_result = None;

    rayon::scope(|s| {
        s.spawn(|_| {
            parsed_result = Some(
                quick_xml::de::from_reader::<_, GmlImplicitGeometry>(xml_document)
                    .map_err(Error::from),
            );
        });
        s.spawn(|_| {
            reference_point_result = Some(collect_gml_child(
                xml_document,
                &spans,
                CityGmlElement::ReferencePointProperty.into(),
                deserialize_point_property,
            ));
        });
        s.spawn(|_| {
            relative_geometry_result = Some(collect_gml_child(
                xml_document,
                &spans,
                CityGmlElement::RelativeGeometryProperty.into(),
                deserialize_abstract_geometry_property,
            ));
        });
        s.spawn(|_| {
            appearances_result = Some(collect_children(
                xml_document,
                &spans,
                CityGmlElement::AbstractAppearanceProperty.into(),
                deserialize_abstract_appearance_property,
            ));
        });
    });

    let parsed = parsed_result.expect("rayon::scope guarantees all spawns complete")?;
    let reference_point = reference_point_result
        .expect("rayon::scope guarantees all spawns complete")?
        .ok_or(Error::ElementNotFound("reference point not found".into()))?;
    let relative_geometry =
        relative_geometry_result.expect("rayon::scope guarantees all spawns complete")?;
    let appearances = appearances_result.expect("rayon::scope guarantees all spawns complete")?;

    let object_id: Id = if let Some(object_id) = parsed.object_id {
        Id::try_from(object_id)?
    } else {
        generate_object_id()
    };

    let mut implicit_geometry: ImplicitGeometry = ImplicitGeometry::new(
        object_id,
        parsed.transformation_matrix.try_into()?,
        reference_point,
    );
    implicit_geometry.set_mime_type_opt(parsed.mime_type.map(Code::from).map(MimeTypeValue::from));
    implicit_geometry.set_library_object_opt(parsed.library_object);
    implicit_geometry.set_relative_geometry_opt(relative_geometry);
    implicit_geometry.set_appearances(appearances);

    Ok(implicit_geometry)
}

pub fn serialize_implicit_geometry(
    implicit_geometry: &ImplicitGeometry,
    formatting: Formatting,
) -> Result<XmlNode, Error> {
    let mut xml_node_parts = XmlNodeParts::empty();

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

    xml_node_parts.content.push(
        serialize_point_property(
            implicit_geometry.reference_point(),
            formatting,
            CityGmlElement::ReferencePointProperty.into(),
        )
        .map_err(Error::from)?
        .into(),
    );

    if let Some(prop) = implicit_geometry.relative_geometry() {
        xml_node_parts.content.push(
            serialize_abstract_geometry_property(
                prop,
                formatting,
                CityGmlElement::RelativeGeometryProperty.into(),
            )
            .map_err(Error::from)?
            .into(),
        );
    }

    for prop in implicit_geometry.appearances() {
        xml_node_parts.content.push(XmlNodeContent::Child(
            serialize_abstract_appearance_property(prop, formatting)?,
        ));
    }

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

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct GmlImplicitGeometry {
    #[serde(rename = "objectId", skip_serializing_if = "Option::is_none")]
    pub object_id: Option<String>,

    #[serde(rename = "transformationMatrix")]
    pub transformation_matrix: GmlTransformationMatrix4x4,

    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
    pub mime_type: Option<GmlCode>,

    #[serde(rename = "libraryObject", skip_serializing_if = "Option::is_none")]
    pub library_object: Option<String>,
}

impl From<&ImplicitGeometry> for GmlImplicitGeometry {
    fn from(item: &ImplicitGeometry) -> Self {
        GmlImplicitGeometry {
            object_id: Some(item.object_id().clone().into()),
            transformation_matrix: (*item.transformation_matrix()).into(),
            mime_type: item.mime_type().map(MimeTypeValue::code).map(Into::into),
            library_object: item.library_object().map(Into::into),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::gml::codec::core::implicit_geometry::{
        deserialize_implicit_geometry, serialize_implicit_geometry,
    };
    use egml::io::util::Formatting;

    #[test]
    fn test_deserialize_implicit_geometry_basic() {
        let xml_document = b"<ImplicitGeometry>
    <transformationMatrix>-0.5894514707536183 -0.8078037903020735 0.0 0.0 0.8078037903020735 -0.5894514707536183 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0</transformationMatrix>
    <referencePoint>
        <gml:Point>
            <gml:pos srsDimension=\"3\">678298.3706294019 5403791.857383491 366.9430094360463</gml:pos>
        </gml:Point>
    </referencePoint>
</ImplicitGeometry>";

        let implicit_geometry = deserialize_implicit_geometry(xml_document).expect("should work");

        assert_eq!(
            implicit_geometry
                .reference_point()
                .object()
                .expect("must exist")
                .pos()
                .x(),
            678298.3706294019
        );
        assert_eq!(
            implicit_geometry
                .reference_point()
                .object()
                .expect("must exist")
                .pos()
                .y(),
            5403791.857383491
        );
        assert_eq!(
            implicit_geometry
                .reference_point()
                .object()
                .expect("must exist")
                .pos()
                .z(),
            366.9430094360463
        );
    }

    #[test]
    fn test_serialize_implicit_geometry_reference_point() {
        let xml_document = b"<ImplicitGeometry>
    <transformationMatrix>-0.5894514707536183 -0.8078037903020735 0.0 0.0 0.8078037903020735 -0.5894514707536183 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0</transformationMatrix>
    <referencePoint>
        <gml:Point>
            <gml:pos srsDimension=\"3\">678298.3706294019 5403791.857383491 366.9430094360463</gml:pos>
        </gml:Point>
    </referencePoint>
</ImplicitGeometry>";

        let implicit_geometry =
            deserialize_implicit_geometry(xml_document).expect("should deserialize");
        let xml = serialize_implicit_geometry(&implicit_geometry, Formatting::Compact)
            .expect("should serialize")
            .to_string(Formatting::Compact)
            .expect("should convert to string");

        assert!(xml.contains("referencePoint"), "missing <referencePoint>");
        assert!(
            xml.contains("678298.3706294019"),
            "missing reference point x coordinate"
        );
        assert!(
            xml.contains("5403791.857383491"),
            "missing reference point y coordinate"
        );
        assert!(
            xml.contains("366.9430094360463"),
            "missing reference point z coordinate"
        );
    }

    #[test]
    fn test_deserialize_implicit_geometry_with_relative_geometry() {
        let xml_document = b"<ImplicitGeometry>
  <transformationMatrix>10.289999999999994 0.0 0.0 0.0 0.0 10.289999999999994 0.0 0.0 0.0 0.0 10.289999999999994 0.0 0.0 0.0 0.0 1.0</transformationMatrix>
  <referencePoint>
    <gml:Point>
      <gml:pos>689949.3 5324001.1 586.3806</gml:pos>
    </gml:Point>
  </referencePoint>
  <relativeGeometry>
    <gml:MultiGeometry srsName=\"urn:ogc:def:crs:EPSG::25832\" srsDimension=\"3\" gml:id=\"fme-gen-1b18fb8f-dbee-460c-bd8e-4e3229066ea2\">
      <gml:geometryMember>
        <gml:MultiGeometry>
          <gml:geometryMember>
            <gml:Polygon>
              <gml:exterior>
                <gml:LinearRing>
                  <gml:posList>1.1102230246251565E-16 -0.08104761325536736 0.40000000000000024 5.551115123125783E-17 -0.25873011317535366 0.42868290603009473 -0.18294981752345554 -0.1829498175234554 0.42868290603009473 -0.057309316931854926 -0.057309316931854926 0.40000000000000024 1.1102230246251565E-16 -0.08104761325536736 0.40000000000000024</gml:posList>
                </gml:LinearRing>
              </gml:exterior>
            </gml:Polygon>
          </gml:geometryMember>
          <gml:geometryMember>
            <gml:Polygon>
              <gml:exterior>
                <gml:LinearRing>
                  <gml:posList>-0.057309316931854926 -0.057309316931854926 0.40000000000000024 -0.18294981752345554 -0.1829498175234554 0.42868290603009473 -0.25873011317535366 0.0 0.42868290603009473 -0.0810476132553673 0.0 0.40000000000000024 -0.057309316931854926 -0.057309316931854926 0.40000000000000024</gml:posList>
                </gml:LinearRing>
              </gml:exterior>
            </gml:Polygon>
          </gml:geometryMember>
          <gml:geometryMember>
            <gml:Polygon>
              <gml:exterior>
                <gml:LinearRing>
                  <gml:posList>-0.24026196252347953 0.24026196252347976 0.49950174129794134 -0.24748737341529176 0.2474873734152918 0.6403170178482882 5.551115123125783E-17 0.3500000000000002 0.6403170178482882 5.551115123125783E-17 0.3397817259230812 0.49950174129794134 -0.24026196252347953 0.24026196252347976 0.49950174129794134</gml:posList>
                </gml:LinearRing>
              </gml:exterior>
            </gml:Polygon>
          </gml:geometryMember>
        </gml:MultiGeometry>
      </gml:geometryMember>
    </gml:MultiGeometry>
  </relativeGeometry>
</ImplicitGeometry>";

        let implicit_geometry =
            deserialize_implicit_geometry(xml_document).expect("should deserialize");

        let reference_point = implicit_geometry
            .reference_point()
            .object()
            .expect("reference point must exist");
        assert_eq!(reference_point.pos().x(), 689949.3);
        assert_eq!(reference_point.pos().y(), 5324001.1);
        assert_eq!(reference_point.pos().z(), 586.3806);

        assert_eq!(
            implicit_geometry.transformation_matrix().matrix()[(0, 0)],
            10.289999999999994
        );

        assert!(
            implicit_geometry.relative_geometry().is_some(),
            "relative geometry must be present"
        );
    }

    #[test]
    fn test_serialize_implicit_geometry_with_relative_geometry() {
        let xml_document = b"<ImplicitGeometry>
  <transformationMatrix>10.289999999999994 0.0 0.0 0.0 0.0 10.289999999999994 0.0 0.0 0.0 0.0 10.289999999999994 0.0 0.0 0.0 0.0 1.0</transformationMatrix>
  <referencePoint>
    <gml:Point>
      <gml:pos>689949.3 5324001.1 586.3806</gml:pos>
    </gml:Point>
  </referencePoint>
  <relativeGeometry>
    <gml:MultiGeometry srsName=\"urn:ogc:def:crs:EPSG::25832\" srsDimension=\"3\" gml:id=\"fme-gen-1b18fb8f-dbee-460c-bd8e-4e3229066ea2\">
      <gml:geometryMember>
        <gml:MultiGeometry>
          <gml:geometryMember>
            <gml:Polygon>
              <gml:exterior>
                <gml:LinearRing>
                  <gml:posList>1.1102230246251565E-16 -0.08104761325536736 0.40000000000000024 5.551115123125783E-17 -0.25873011317535366 0.42868290603009473 -0.18294981752345554 -0.1829498175234554 0.42868290603009473 -0.057309316931854926 -0.057309316931854926 0.40000000000000024 1.1102230246251565E-16 -0.08104761325536736 0.40000000000000024</gml:posList>
                </gml:LinearRing>
              </gml:exterior>
            </gml:Polygon>
          </gml:geometryMember>
          <gml:geometryMember>
            <gml:Polygon>
              <gml:exterior>
                <gml:LinearRing>
                  <gml:posList>-0.057309316931854926 -0.057309316931854926 0.40000000000000024 -0.18294981752345554 -0.1829498175234554 0.42868290603009473 -0.25873011317535366 0.0 0.42868290603009473 -0.0810476132553673 0.0 0.40000000000000024 -0.057309316931854926 -0.057309316931854926 0.40000000000000024</gml:posList>
                </gml:LinearRing>
              </gml:exterior>
            </gml:Polygon>
          </gml:geometryMember>
          <gml:geometryMember>
            <gml:Polygon>
              <gml:exterior>
                <gml:LinearRing>
                  <gml:posList>-0.24026196252347953 0.24026196252347976 0.49950174129794134 -0.24748737341529176 0.2474873734152918 0.6403170178482882 5.551115123125783E-17 0.3500000000000002 0.6403170178482882 5.551115123125783E-17 0.3397817259230812 0.49950174129794134 -0.24026196252347953 0.24026196252347976 0.49950174129794134</gml:posList>
                </gml:LinearRing>
              </gml:exterior>
            </gml:Polygon>
          </gml:geometryMember>
        </gml:MultiGeometry>
      </gml:geometryMember>
    </gml:MultiGeometry>
  </relativeGeometry>
</ImplicitGeometry>";

        let implicit_geometry =
            deserialize_implicit_geometry(xml_document).expect("should deserialize");
        let xml = serialize_implicit_geometry(&implicit_geometry, Formatting::Compact)
            .expect("should serialize")
            .to_string(Formatting::Compact)
            .expect("should convert to string");

        assert!(xml.contains("referencePoint"), "missing <referencePoint>");
        assert!(xml.contains("689949.3"), "missing reference point x");
        assert!(xml.contains("5324001.1"), "missing reference point y");
        assert!(xml.contains("586.3806"), "missing reference point z");
        assert!(
            xml.contains("relativeGeometry"),
            "missing <relativeGeometry>"
        );
        assert!(xml.contains("MultiGeometry"), "missing <MultiGeometry>");
        assert!(xml.contains("Polygon"), "missing <Polygon>");
    }
}