use std::io::{BufWriter, Seek, SeekFrom, Write};
use super::{header, ShapeType};
use super::{Error, PointZ};
use crate::record::{BBoxZ, EsriShape, RecordHeader};
use std::fs::File;
use std::path::Path;
use crate::reader::ShapeIndex;
use dbase::TableWriterBuilder;
pub(crate) fn f64_min(a: f64, b: f64) -> f64 {
if a < b {
a
} else {
b
}
}
pub(crate) fn f64_max(a: f64, b: f64) -> f64 {
if a > b {
a
} else {
b
}
}
pub struct ShapeWriter<T: Write + Seek> {
shp_dest: T,
shx_dest: Option<T>,
header: header::Header,
rec_num: u32,
}
impl<T: Write + Seek> ShapeWriter<T> {
pub fn new(shp_dest: T) -> Self {
Self {
shp_dest,
shx_dest: None,
header: header::Header::default(),
rec_num: 1,
}
}
pub fn with_shx(shp_dest: T, shx_dest: T) -> Self {
Self {
shp_dest,
shx_dest: Some(shx_dest),
header: Default::default(),
rec_num: 1,
}
}
pub fn write_shape<S: EsriShape>(&mut self, shape: &S) -> Result<(), Error> {
match (self.header.shape_type, S::shapetype()) {
(ShapeType::NullShape, t) => {
use std::f64::{MAX, MIN};
self.header.shape_type = t;
self.header.bbox = BBoxZ {
max: PointZ::new(MIN, MIN, MIN, MIN),
min: PointZ::new(MAX, MAX, MAX, MAX),
};
self.header.write_to(&mut self.shp_dest)?;
if let Some(shx_dest) = &mut self.shx_dest {
self.header.write_to(shx_dest)?;
}
}
(t1, t2) if t1 != t2 => {
return Err(Error::MismatchShapeType {
requested: t1,
actual: t2,
});
}
_ => {}
}
let record_size = (shape.size_in_bytes() + std::mem::size_of::<i32>()) / 2;
RecordHeader {
record_number: self.rec_num as i32,
record_size: record_size as i32,
}
.write_to(&mut self.shp_dest)?;
self.header.shape_type.write_to(&mut self.shp_dest)?;
shape.write_to(&mut self.shp_dest)?;
if let Some(shx_dest) = &mut self.shx_dest {
ShapeIndex {
offset: self.header.file_length,
record_size: record_size as i32,
}
.write_to(shx_dest)?;
}
self.header.file_length += record_size as i32 + RecordHeader::SIZE as i32 / 2;
self.header.bbox.grow_from_shape(shape);
self.rec_num += 1;
Ok(())
}
pub fn write_shapes<'a, S: EsriShape + 'a, C: IntoIterator<Item = &'a S>>(
mut self,
container: C,
) -> Result<(), Error> {
for shape in container {
self.write_shape(shape)?;
}
Ok(())
}
fn close(&mut self) -> Result<(), Error> {
if self.header.bbox.max.m == std::f64::MIN && self.header.bbox.min.m == std::f64::MAX {
self.header.bbox.max.m = 0.0;
self.header.bbox.min.m = 0.0;
}
if self.header.bbox.max.z == std::f64::MIN && self.header.bbox.min.z == std::f64::MAX {
self.header.bbox.max.z = 0.0;
self.header.bbox.min.z = 0.0;
}
self.shp_dest.seek(SeekFrom::Start(0))?;
self.header.write_to(&mut self.shp_dest)?;
self.shp_dest.seek(SeekFrom::End(0))?;
if let Some(shx_dest) = &mut self.shx_dest {
let mut shx_header = self.header;
shx_header.file_length = header::HEADER_SIZE / 2
+ ((self.rec_num - 1) as i32 * 2 * std::mem::size_of::<i32>() as i32 / 2);
shx_dest.seek(SeekFrom::Start(0))?;
shx_header.write_to(shx_dest)?;
shx_dest.seek(SeekFrom::End(0))?;
}
Ok(())
}
}
impl<T: Write + Seek> Drop for ShapeWriter<T> {
fn drop(&mut self) {
let _ = self.close();
}
}
impl ShapeWriter<BufWriter<File>> {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let shp_path = path.as_ref().to_path_buf();
let shx_path = shp_path.with_extension("shx");
let shp_file = BufWriter::new(File::create(shp_path)?);
let shx_file = BufWriter::new(File::create(shx_path)?);
Ok(Self::with_shx(shp_file, shx_file))
}
}
pub struct Writer<T: Write + Seek> {
shape_writer: ShapeWriter<T>,
dbase_writer: dbase::TableWriter<T>,
}
impl<T: Write + Seek> Writer<T> {
pub fn new(shape_writer: ShapeWriter<T>, dbase_writer: dbase::TableWriter<T>) -> Self {
Self {
shape_writer,
dbase_writer,
}
}
pub fn write_shape_and_record<S: EsriShape, R: dbase::WritableRecord>(
&mut self,
shape: &S,
record: &R,
) -> Result<(), Error> {
self.shape_writer.write_shape(shape)?;
self.dbase_writer.write_record(record)?;
Ok(())
}
pub fn write_shapes_and_records<
'a,
S: EsriShape + 'a,
R: dbase::WritableRecord + 'a,
C: IntoIterator<Item = (&'a S, &'a R)>,
>(
mut self,
container: C,
) -> Result<(), Error> {
for (shape, record) in container.into_iter() {
self.write_shape_and_record(shape, record)?;
}
Ok(())
}
}
impl Writer<BufWriter<File>> {
pub fn from_path<P: AsRef<Path>>(
path: P,
table_builder: TableWriterBuilder,
) -> Result<Self, Error> {
Ok(Self {
shape_writer: ShapeWriter::from_path(path.as_ref())?,
dbase_writer: table_builder
.build_with_file_dest(path.as_ref().with_extension("dbf"))?,
})
}
pub fn from_path_with_info<P: AsRef<Path>>(
path: P,
table_info: dbase::TableInfo,
) -> Result<Self, Error> {
Ok(Self {
shape_writer: ShapeWriter::from_path(path.as_ref())?,
dbase_writer: dbase::TableWriterBuilder::from_table_info(table_info)
.build_with_file_dest(path.as_ref().with_extension("dbf"))?,
})
}
}