use crate::error::Converter;
use crate::xml;
use crate::{
CartesianBounds, ColorLimits, DateTime, IndexBounds, IntensityLimits, Record, RecordDataType,
RecordName, Result, SphericalBounds, Transform,
};
use roxmltree::{Document, Node};
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct PointCloud {
pub guid: Option<String>,
pub file_offset: u64,
pub records: u64,
pub prototype: Vec<Record>,
pub original_guids: Option<Vec<String>>,
pub name: Option<String>,
pub description: Option<String>,
pub cartesian_bounds: Option<CartesianBounds>,
pub spherical_bounds: Option<SphericalBounds>,
pub index_bounds: Option<IndexBounds>,
pub intensity_limits: Option<IntensityLimits>,
pub color_limits: Option<ColorLimits>,
pub transform: Option<Transform>,
pub acquisition_start: Option<DateTime>,
pub acquisition_end: Option<DateTime>,
pub sensor_vendor: Option<String>,
pub sensor_model: Option<String>,
pub sensor_serial: Option<String>,
pub sensor_hw_version: Option<String>,
pub sensor_sw_version: Option<String>,
pub sensor_fw_version: Option<String>,
pub temperature: Option<f64>,
pub humidity: Option<f64>,
pub atmospheric_pressure: Option<f64>,
}
impl PointCloud {
pub(crate) fn vec_from_document(document: &Document) -> Result<Vec<Self>> {
let mut pointclouds = Vec::new();
if let Some(data3d_node) = document.descendants().find(|n| n.has_tag_name("data3D")) {
for n in data3d_node.children() {
if n.has_tag_name("vectorChild") && n.attribute("type") == Some("Structure") {
let pointcloud = Self::from_node(&n)?;
pointclouds.push(pointcloud);
}
}
}
Ok(pointclouds)
}
pub(crate) fn from_node(node: &Node) -> Result<Self> {
let guid = xml::opt_string(node, "guid")?;
let name = xml::opt_string(node, "name")?;
let description = xml::opt_string(node, "description")?;
let sensor_model = xml::opt_string(node, "sensorModel")?;
let sensor_vendor = xml::opt_string(node, "sensorVendor")?;
let sensor_serial = xml::opt_string(node, "sensorSerialNumber")?;
let sensor_hw_version = xml::opt_string(node, "sensorHardwareVersion")?;
let sensor_sw_version = xml::opt_string(node, "sensorSoftwareVersion")?;
let sensor_fw_version = xml::opt_string(node, "sensorFirmwareVersion")?;
let temperature = xml::opt_f64(node, "temperature")?;
let humidity = xml::opt_f64(node, "relativeHumidity")?;
let atmospheric_pressure = xml::opt_f64(node, "atmosphericPressure")?;
let acquisition_start = xml::opt_date_time(node, "acquisitionStart")?;
let acquisition_end = xml::opt_date_time(node, "acquisitionEnd")?;
let transform = xml::opt_transform(node, "pose")?;
let cartesian_bounds = node.children().find(|n| n.has_tag_name("cartesianBounds"));
let spherical_bounds = node.children().find(|n| n.has_tag_name("sphericalBounds"));
let index_bounds = node.children().find(|n| n.has_tag_name("indexBounds"));
let intensity_limits = node.children().find(|n| n.has_tag_name("intensityLimits"));
let color_limits = node.children().find(|n| n.has_tag_name("colorLimits"));
let original_guids = if let Some(original_guids_node) =
node.children().find(|n| n.has_tag_name("originalGuids"))
{
let mut guids = Vec::new();
for n in original_guids_node.children() {
if !n.is_element()
|| !n.has_tag_name("vectorChild")
|| n.attribute("type") != Some("String")
{
continue;
}
guids.push(n.text().unwrap_or("").to_owned())
}
Some(guids)
} else {
None
};
let points_tag = node
.children()
.find(|n| n.has_tag_name("points") && n.attribute("type") == Some("CompressedVector"))
.invalid_err("Cannot find 'points' tag inside 'data3D' child")?;
let file_offset = points_tag
.attribute("fileOffset")
.invalid_err("Cannot find 'fileOffset' attribute in 'points' tag")?
.parse::<u64>()
.invalid_err("Cannot parse 'fileOffset' attribute value as u64")?;
let records = points_tag
.attribute("recordCount")
.invalid_err("Cannot find 'recordCount' attribute in 'points' tag")?
.parse::<u64>()
.invalid_err("Cannot parse 'recordCount' attribute value as u64")?;
let prototype_tag = points_tag
.children()
.find(|n| n.has_tag_name("prototype") && n.attribute("type") == Some("Structure"))
.invalid_err("Cannot find 'prototype' child in 'points' tag")?;
let mut prototype = Vec::new();
for n in prototype_tag.children() {
if !n.is_element() {
continue;
}
let ns = n.lookup_prefix(n.tag_name().namespace().unwrap_or_default());
let tag = n.tag_name().name();
let name = RecordName::from_namespace_and_tag_name(ns, tag)?;
let data_type = RecordDataType::from_node(&n)?;
prototype.push(Record { name, data_type });
}
Ok(Self {
guid,
name,
file_offset,
original_guids,
records,
prototype,
cartesian_bounds: if let Some(node) = cartesian_bounds {
Some(CartesianBounds::from_node(&node)?)
} else {
None
},
spherical_bounds: if let Some(node) = spherical_bounds {
Some(SphericalBounds::from_node(&node)?)
} else {
None
},
index_bounds: if let Some(node) = index_bounds {
Some(IndexBounds::from_node(&node)?)
} else {
None
},
intensity_limits: if let Some(node) = intensity_limits {
Some(IntensityLimits::from_node(&node)?)
} else {
None
},
color_limits: if let Some(node) = color_limits {
Some(ColorLimits::from_node(&node)?)
} else {
None
},
transform,
description,
acquisition_start,
acquisition_end,
sensor_vendor,
sensor_model,
sensor_serial,
sensor_hw_version,
sensor_sw_version,
sensor_fw_version,
temperature,
humidity,
atmospheric_pressure,
})
}
#[allow(clippy::cognitive_complexity)]
pub(crate) fn xml_string(&self) -> Result<String> {
let mut xml = String::new();
xml += "<vectorChild type=\"Structure\">\n";
if let Some(guid) = &self.guid {
xml += &xml::gen_string("guid", &guid);
}
if let Some(original_guids) = &self.original_guids {
xml += "<originalGuids type=\"Vector\" allowHeterogeneousChildren=\"0\">\n";
for guid in original_guids {
xml += &xml::gen_string("vectorChild", &guid);
}
xml += "</originalGuids>\n";
}
if let Some(bounds) = &self.cartesian_bounds {
xml += &bounds.xml_string();
}
if let Some(bounds) = &self.spherical_bounds {
xml += &bounds.xml_string();
}
if let Some(bounds) = &self.index_bounds {
xml += &bounds.xml_string();
}
if let Some(limits) = &self.color_limits {
if limits.red_min.is_some()
&& limits.red_max.is_some()
&& limits.green_min.is_some()
&& limits.green_max.is_some()
&& limits.blue_min.is_some()
&& limits.blue_max.is_some()
{
xml += &limits.xml_string();
}
}
if let Some(limits) = &self.intensity_limits {
if limits.intensity_min.is_some() && limits.intensity_max.is_some() {
xml += &limits.xml_string();
}
}
if let Some(name) = &self.name {
xml += &xml::gen_string("name", name);
}
if let Some(desc) = &self.description {
xml += &xml::gen_string("description", desc);
}
if let Some(sensor_vendor) = &self.sensor_vendor {
xml += &xml::gen_string("sensorVendor", sensor_vendor);
}
if let Some(sensor_model) = &self.sensor_model {
xml += &xml::gen_string("sensorModel", sensor_model);
}
if let Some(sensor_serial) = &self.sensor_serial {
xml += &xml::gen_string("sensorSerialNumber", sensor_serial);
}
if let Some(sensor_sw_version) = &self.sensor_sw_version {
xml += &xml::gen_string("sensorSoftwareVersion", sensor_sw_version);
}
if let Some(sensor_fw_version) = &self.sensor_fw_version {
xml += &xml::gen_string("sensorFirmwareVersion", sensor_fw_version);
}
if let Some(sensor_hw_version) = &self.sensor_hw_version {
xml += &xml::gen_string("sensorHardwareVersion", sensor_hw_version);
}
if let Some(transform) = &self.transform {
xml += &transform.xml_string("pose");
}
if let Some(aq_start) = &self.acquisition_start {
xml += &aq_start.xml_string("acquisitionStart");
}
if let Some(aq_end) = &self.acquisition_end {
xml += &aq_end.xml_string("acquisitionEnd");
}
if let Some(temperature) = self.temperature {
xml += &xml::gen_float("temperature", temperature);
}
if let Some(humidity) = self.humidity {
xml += &xml::gen_float("relativeHumidity", humidity);
}
if let Some(pressure) = self.atmospheric_pressure {
xml += &xml::gen_float("atmosphericPressure", pressure);
}
xml += &format!(
"<points type=\"CompressedVector\" fileOffset=\"{}\" recordCount=\"{}\">\n",
self.file_offset, self.records
);
xml += "<prototype type=\"Structure\">\n";
for record in &self.prototype {
xml += &record.xml_string();
}
xml += "</prototype>\n";
xml += "</points>\n";
xml += "</vectorChild>\n";
Ok(xml)
}
fn contains(&self, names: &[RecordName]) -> bool {
names
.iter()
.all(|searched| self.prototype.iter().any(|actual| &actual.name == searched))
}
pub fn has_cartesian(&self) -> bool {
self.contains(&[
RecordName::CartesianX,
RecordName::CartesianY,
RecordName::CartesianZ,
])
}
pub fn has_spherical(&self) -> bool {
self.contains(&[
RecordName::SphericalRange,
RecordName::SphericalAzimuth,
RecordName::SphericalElevation,
])
}
pub fn has_color(&self) -> bool {
self.contains(&[
RecordName::ColorRed,
RecordName::ColorGreen,
RecordName::ColorBlue,
])
}
pub fn has_intensity(&self) -> bool {
self.contains(&[RecordName::Intensity])
}
pub fn has_row_column(&self) -> bool {
self.contains(&[RecordName::RowIndex, RecordName::ColumnIndex])
}
pub fn has_return(&self) -> bool {
self.contains(&[RecordName::ReturnCount, RecordName::ReturnIndex])
}
pub fn has_timestamp(&self) -> bool {
self.contains(&[RecordName::TimeStamp])
}
pub fn get_cartesian_bounds(&self) -> Option<CartesianBounds> {
if let Some(bounds) = &self.cartesian_bounds {
Some(bounds.clone())
} else if let Some(bounds) = &self.spherical_bounds {
bounds.to_cartesian()
} else {
None
}
}
}