spatialrust_io/e57/
writer.rs1use std::path::Path;
2use std::time::{SystemTime, UNIX_EPOCH};
3
4use e57::{E57Writer as ExternalE57Writer, RawValues, RecordValue};
5use spatialrust_core::{
6 DType, FieldSemantic, HasPositions3, PointBuffer, PointCloud, PointField, PointSchema,
7};
8
9use crate::e57::schema::{schema_from_point_cloud, validate_export_schema};
10use crate::error::{e57_format, e57_parse, IoError};
11use crate::{PointWriter, WriteOptions};
12
13pub struct E57Writer;
15
16#[must_use]
18pub fn new_e57_guid(prefix: &str) -> String {
19 let nanos = SystemTime::now()
20 .duration_since(UNIX_EPOCH)
21 .map(|duration| duration.as_nanos())
22 .unwrap_or(0);
23 format!("{prefix}-{nanos}")
24}
25
26impl PointWriter for E57Writer {
27 fn write(
28 &mut self,
29 cloud: &PointCloud,
30 _options: &WriteOptions,
31 ) -> spatialrust_core::SpatialResult<()> {
32 let path = std::env::temp_dir().join(format!("spatialrust_e57_{}.e57", std::process::id()));
33 write_e57_file(&path, cloud)
34 .map_err(|error| spatialrust_core::SpatialError::Io(error.to_string()))?;
35 std::fs::remove_file(path)
36 .map_err(|error| spatialrust_core::SpatialError::Io(error.to_string()))?;
37 Ok(())
38 }
39}
40
41pub fn write_e57(path: impl AsRef<Path>, cloud: &PointCloud) -> Result<(), IoError> {
43 write_e57_file(path, cloud)
44}
45
46pub fn write_e57_file(path: impl AsRef<Path>, cloud: &PointCloud) -> Result<(), IoError> {
48 cloud.validate()?;
49 validate_export_schema(cloud.schema())?;
50
51 let (export_schema, prototype) = schema_from_point_cloud(cloud.schema());
52 let file_guid = new_e57_guid("spatialrust");
53 let mut writer = ExternalE57Writer::from_file(path, &file_guid)
54 .map_err(|error| e57_format(error.to_string()))?;
55
56 let scan_guid = new_e57_guid("scan");
57 let mut pc_writer = writer
58 .add_pointcloud(&scan_guid, prototype)
59 .map_err(|error| e57_format(error.to_string()))?;
60
61 for index in 0..cloud.len() {
62 let values = point_values(cloud, &export_schema, index)?;
63 pc_writer.add_point(values).map_err(|error| e57_format(error.to_string()))?;
64 }
65
66 pc_writer.finalize().map_err(|error| e57_format(error.to_string()))?;
67 writer.finalize().map_err(|error| e57_format(error.to_string()))
68}
69
70fn point_values(
71 cloud: &PointCloud,
72 schema: &PointSchema,
73 index: usize,
74) -> Result<RawValues, IoError> {
75 let (x, y, z) = cloud.positions3()?;
76 let mut values = Vec::with_capacity(schema.len());
77
78 for field in schema.fields() {
79 let value = match field.semantic {
80 FieldSemantic::PositionX => RecordValue::Single(x[index]),
81 FieldSemantic::PositionY => RecordValue::Single(y[index]),
82 FieldSemantic::PositionZ => RecordValue::Single(z[index]),
83 FieldSemantic::Intensity => RecordValue::Single(read_cloud_field(cloud, field, index)?),
84 FieldSemantic::ColorR | FieldSemantic::ColorG | FieldSemantic::ColorB => {
85 RecordValue::Integer(read_cloud_field(cloud, field, index)?.round() as i64)
86 }
87 _ => {
88 return Err(e57_parse(format!(
89 "unsupported E57 field `{}` during export",
90 field.name
91 )));
92 }
93 };
94 values.push(value);
95 }
96
97 Ok(values)
98}
99
100fn read_cloud_field(cloud: &PointCloud, field: &PointField, index: usize) -> Result<f32, IoError> {
101 let buffer = cloud.field(&field.name)?;
102 match field.dtype {
103 DType::F32 | DType::F16 => Ok(buffer.as_f32()?[index]),
104 DType::U8 => {
105 let PointBuffer::U8(values) = buffer else {
106 return Err(spatialrust_core::SpatialError::UnsupportedDType(field.dtype).into());
107 };
108 Ok(f32::from(values[index]))
109 }
110 DType::U16 => {
111 let PointBuffer::U16(values) = buffer else {
112 return Err(spatialrust_core::SpatialError::UnsupportedDType(field.dtype).into());
113 };
114 Ok(f32::from(values[index]))
115 }
116 _ => Err(spatialrust_core::SpatialError::UnsupportedDType(field.dtype).into()),
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::write_e57_file;
123 use crate::e57::reader::read_e57_file;
124 use spatialrust_core::PointCloudBuilder;
125
126 #[test]
127 fn writes_xyz_cloud() {
128 let mut builder = PointCloudBuilder::xyz();
129 builder.push_point([1.0, 2.0, 3.0]).unwrap();
130 let cloud = builder.build().unwrap();
131 let path =
132 std::env::temp_dir().join(format!("spatialrust_e57_write_{}.e57", std::process::id()));
133 write_e57_file(&path, &cloud).unwrap();
134 let loaded = read_e57_file(&path).unwrap();
135 let _ = std::fs::remove_file(path);
136 assert_eq!(loaded.len(), 1);
137 }
138}