use std::io::{Seek, SeekFrom, Write};
use crate::crs::{ogc_wkt_from_epsg, Crs};
use crate::e57::page::PageWriter;
use crate::e57::xml::build_xml;
use crate::e57::{E57_SIGNATURE, PAGE_SIZE};
use crate::io::{le, PointWriter};
use crate::point::PointRecord;
use crate::Result;
#[derive(Debug, Clone)]
pub struct E57WriterConfig {
pub name: String,
pub guid: String,
pub has_intensity: bool,
pub has_color: bool,
pub coordinate_metadata: Option<String>,
pub crs: Option<Crs>,
}
impl Default for E57WriterConfig {
fn default() -> Self {
E57WriterConfig {
name: "Point Cloud".to_owned(),
guid: "{00000000-0000-0000-0000-000000000000}".to_owned(),
has_intensity: false,
has_color: false,
coordinate_metadata: None,
crs: None,
}
}
}
pub struct E57Writer<W: Write + Seek> {
inner: W,
config: E57WriterConfig,
points: Vec<PointRecord>,
}
impl<W: Write + Seek> E57Writer<W> {
pub fn new(inner: W, config: E57WriterConfig) -> Self {
E57Writer { inner, config, points: Vec::new() }
}
}
impl<W: Write + Seek> PointWriter for E57Writer<W> {
fn write_point(&mut self, p: &PointRecord) -> Result<()> {
self.points.push(*p);
Ok(())
}
fn finish(&mut self) -> Result<()> {
let point_count = self.points.len() as u64;
let has_i = self.config.has_intensity;
let has_c = self.config.has_color;
self.inner.seek(SeekFrom::Start(0))?;
self.inner.write_all(E57_SIGNATURE)?;
le::write_u32(&mut self.inner, 1)?; le::write_u32(&mut self.inner, 0)?; le::write_u64(&mut self.inner, 0)?; le::write_u64(&mut self.inner, 0)?; le::write_u64(&mut self.inner, 0)?; le::write_u64(&mut self.inner, PAGE_SIZE as u64)?; le::write_u64(&mut self.inner, PAGE_SIZE as u64)?;
let binary_offset = self.inner.stream_position()?;
let record_size = point_record_size(has_i, has_c);
let _ = record_size;
let mut page_writer = PageWriter::new(&mut self.inner);
for p in &self.points {
let bytes = encode_point(p, has_i, has_c);
page_writer.write_bytes(&bytes)?;
}
page_writer.flush_final()?;
let xml_offset = self.inner.stream_position()?;
let crs_string: Option<String> = self.config.coordinate_metadata.clone()
.or_else(|| self.config.crs.as_ref().and_then(|c| c.wkt.clone()))
.or_else(|| self.config.crs.as_ref().and_then(|c| c.epsg)
.and_then(ogc_wkt_from_epsg)
)
.or_else(|| self.config.crs.as_ref().and_then(|c| c.epsg)
.map(|e| format!("EPSG:{e}"))
);
let xml = build_xml(
point_count, binary_offset,
has_i, has_c,
&self.config.guid, &self.config.name,
crs_string.as_deref(),
);
let xml_bytes = xml.as_bytes();
let xml_length = xml_bytes.len() as u64;
let mut xml_page_writer = PageWriter::new(&mut self.inner);
xml_page_writer.write_bytes(xml_bytes)?;
xml_page_writer.flush_final()?;
let file_length = self.inner.stream_position()?;
self.inner.seek(SeekFrom::Start(0))?;
self.inner.write_all(E57_SIGNATURE)?;
le::write_u32(&mut self.inner, 1)?;
le::write_u32(&mut self.inner, 0)?;
le::write_u64(&mut self.inner, file_length)?;
le::write_u64(&mut self.inner, xml_offset)?;
le::write_u64(&mut self.inner, xml_length)?;
le::write_u64(&mut self.inner, PAGE_SIZE as u64)?;
le::write_u64(&mut self.inner, PAGE_SIZE as u64)?;
self.inner.seek(SeekFrom::Start(file_length))?;
Ok(())
}
}
fn encode_point(p: &PointRecord, has_i: bool, has_c: bool) -> Vec<u8> {
let mut v = Vec::with_capacity(32);
v.extend_from_slice(&p.x.to_le_bytes());
v.extend_from_slice(&p.y.to_le_bytes());
v.extend_from_slice(&p.z.to_le_bytes());
if has_i {
let intensity = f32::from(p.intensity) / 65535.0;
v.extend_from_slice(&intensity.to_le_bytes());
}
if has_c {
let r = p.color.map_or(0, |c| (c.red >> 8) as u8);
let g = p.color.map_or(0, |c| (c.green >> 8) as u8);
let b = p.color.map_or(0, |c| (c.blue >> 8) as u8);
v.push(r); v.push(g); v.push(b);
}
v
}
fn point_record_size(has_i: bool, has_c: bool) -> usize {
let mut size = 24; if has_i { size += 4; } if has_c { size += 3; } size
}