use core::f64;
use geo_traits::{CoordTrait, PointTrait};
use geoarrow_schema::Dimension;
use geoarrow_schema::error::{GeoArrowError, GeoArrowResult};
use crate::array::InterleavedCoordBuffer;
#[derive(Debug, Clone)]
pub struct InterleavedCoordBufferBuilder {
pub(crate) coords: Vec<f64>,
dim: Dimension,
}
impl InterleavedCoordBufferBuilder {
pub fn new(dim: Dimension) -> Self {
Self::with_capacity(0, dim)
}
pub fn with_capacity(capacity: usize, dim: Dimension) -> Self {
Self {
coords: Vec::with_capacity(capacity * dim.size()),
dim,
}
}
pub fn initialize(len: usize, dim: Dimension, value: f64) -> Self {
Self {
coords: vec![value; len * dim.size()],
dim,
}
}
pub fn reserve(&mut self, additional: usize) {
self.coords.reserve(additional * self.dim.size());
}
pub fn reserve_exact(&mut self, additional: usize) {
self.coords.reserve_exact(additional * self.dim.size());
}
pub fn shrink_to_fit(&mut self) {
self.coords.shrink_to_fit();
}
pub fn capacity(&self) -> usize {
self.coords.capacity() / self.dim.size()
}
pub fn len(&self) -> usize {
self.coords.len() / self.dim.size()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn push_coord(&mut self, coord: &impl CoordTrait<T = f64>) {
self.try_push_coord(coord).unwrap()
}
pub fn try_push_coord(&mut self, coord: &impl CoordTrait<T = f64>) -> GeoArrowResult<()> {
match self.dim {
Dimension::XY => match coord.dim() {
geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => {}
d => {
return Err(GeoArrowError::IncorrectGeometryType(format!(
"coord dimension must be XY for this buffer; got {d:?}."
)));
}
},
Dimension::XYZ => match coord.dim() {
geo_traits::Dimensions::Xyz | geo_traits::Dimensions::Unknown(3) => {}
d => {
return Err(GeoArrowError::IncorrectGeometryType(format!(
"coord dimension must be XYZ for this buffer; got {d:?}."
)));
}
},
Dimension::XYM => match coord.dim() {
geo_traits::Dimensions::Xym | geo_traits::Dimensions::Unknown(3) => {}
d => {
return Err(GeoArrowError::IncorrectGeometryType(format!(
"coord dimension must be XYM for this buffer; got {d:?}."
)));
}
},
Dimension::XYZM => match coord.dim() {
geo_traits::Dimensions::Xyzm | geo_traits::Dimensions::Unknown(4) => {}
d => {
return Err(GeoArrowError::IncorrectGeometryType(format!(
"coord dimension must be XYZM for this buffer; got {d:?}."
)));
}
},
}
self.coords.push(coord.x());
self.coords.push(coord.y());
if let Some(z) = coord.nth(2) {
self.coords.push(z);
};
if let Some(m) = coord.nth(3) {
self.coords.push(m);
};
Ok(())
}
pub(crate) fn push_constant(&mut self, value: f64) {
for _ in 0..self.dim.size() {
self.coords.push(value);
}
}
pub(crate) fn push_point(&mut self, point: &impl PointTrait<T = f64>) {
self.try_push_point(point).unwrap()
}
pub(crate) fn try_push_point(
&mut self,
point: &impl PointTrait<T = f64>,
) -> GeoArrowResult<()> {
if let Some(coord) = point.coord() {
self.try_push_coord(&coord)?;
} else {
self.push_constant(f64::NAN);
};
Ok(())
}
pub fn from_coords<'a>(
coords: impl ExactSizeIterator<Item = &'a (impl CoordTrait<T = f64> + 'a)>,
dim: Dimension,
) -> GeoArrowResult<Self> {
let mut buffer = InterleavedCoordBufferBuilder::with_capacity(coords.len(), dim);
for coord in coords {
buffer.push_coord(coord);
}
Ok(buffer)
}
pub fn finish(self) -> InterleavedCoordBuffer {
InterleavedCoordBuffer::new(self.coords.into(), self.dim)
}
}
#[cfg(test)]
mod test {
use wkt::types::Coord;
use super::*;
#[test]
fn errors_when_pushing_incompatible_coord() {
let mut builder = InterleavedCoordBufferBuilder::new(Dimension::XY);
builder
.try_push_coord(&Coord {
x: 0.0,
y: 0.0,
z: Some(0.0),
m: None,
})
.expect_err("Should err pushing XYZ to XY buffer");
let mut builder = InterleavedCoordBufferBuilder::new(Dimension::XYZ);
builder
.try_push_coord(&Coord {
x: 0.0,
y: 0.0,
z: None,
m: None,
})
.expect_err("Should err pushing XY to XYZ buffer");
builder
.try_push_coord(&Coord {
x: 0.0,
y: 0.0,
z: Some(0.0),
m: None,
})
.unwrap();
}
}