use crate::error::{Result, ShapefileError};
use crate::shp::Shape;
use crate::shp::shapes::{MultiPartShape, MultiPartShapeM, MultiPartShapeZ, Point};
use oxigdal_core::vector::{LineString, MultiLineString};
pub fn geometry_linestring_to_shape(ls: &LineString, has_z: bool, has_m: bool) -> Result<Shape> {
if ls.coords.is_empty() {
return Err(ShapefileError::invalid_geometry(
"LineString must have at least one point",
));
}
let points: Vec<Point> = ls.coords.iter().map(|c| Point::new(c.x, c.y)).collect();
let parts = vec![0i32];
if has_z {
let z_values: Vec<f64> = ls.coords.iter().map(|c| c.z.unwrap_or(0.0)).collect();
let m_values_opt: Option<Vec<f64>> = if has_m {
Some(ls.coords.iter().map(|c| c.m.unwrap_or(0.0)).collect())
} else {
None
};
let shape_z = MultiPartShapeZ::new(parts, points, z_values, m_values_opt)?;
Ok(Shape::PolyLineZ(shape_z))
} else if has_m {
let m_values: Vec<f64> = ls.coords.iter().map(|c| c.m.unwrap_or(0.0)).collect();
let shape_m = MultiPartShapeM::new(parts, points, m_values)?;
Ok(Shape::PolyLineM(shape_m))
} else {
let shape = MultiPartShape::new(parts, points)?;
Ok(Shape::PolyLine(shape))
}
}
pub fn geometry_multilinestring_to_shape(
mls: &MultiLineString,
has_z: bool,
has_m: bool,
) -> Result<Shape> {
let mut all_points: Vec<Point> = Vec::new();
let mut parts: Vec<i32> = Vec::new();
for linestring in &mls.line_strings {
parts.push(all_points.len() as i32);
for coord in &linestring.coords {
all_points.push(Point::new(coord.x, coord.y));
}
}
if all_points.is_empty() {
return Err(ShapefileError::invalid_geometry(
"MultiLineString must have at least one point",
));
}
if has_z {
let z_values: Vec<f64> = mls
.line_strings
.iter()
.flat_map(|ls| ls.coords.iter())
.map(|c| c.z.unwrap_or(0.0))
.collect();
let m_values_opt: Option<Vec<f64>> = if has_m {
Some(
mls.line_strings
.iter()
.flat_map(|ls| ls.coords.iter())
.map(|c| c.m.unwrap_or(0.0))
.collect(),
)
} else {
None
};
let shape_z = MultiPartShapeZ::new(parts, all_points, z_values, m_values_opt)?;
Ok(Shape::PolyLineZ(shape_z))
} else if has_m {
let m_values: Vec<f64> = mls
.line_strings
.iter()
.flat_map(|ls| ls.coords.iter())
.map(|c| c.m.unwrap_or(0.0))
.collect();
let shape_m = MultiPartShapeM::new(parts, all_points, m_values)?;
Ok(Shape::PolyLineM(shape_m))
} else {
let shape = MultiPartShape::new(parts, all_points)?;
Ok(Shape::PolyLine(shape))
}
}
#[cfg(test)]
#[allow(clippy::panic)]
mod tests {
use super::*;
use crate::shp::ShapeRecord;
use oxigdal_core::vector::Coordinate;
#[test]
fn test_linestring_2d() {
let coords = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(1.0, 1.0)];
let ls = LineString::new(coords).expect("valid linestring");
let shape = geometry_linestring_to_shape(&ls, false, false).expect("2D polyline");
assert!(matches!(shape, Shape::PolyLine(_)));
}
#[test]
fn test_polyline_z_shape_type() {
let coords = vec![
Coordinate::new_3d(0.0, 0.0, 10.0),
Coordinate::new_3d(1.0, 1.0, 20.0),
Coordinate::new_3d(2.0, 0.0, 15.0),
];
let ls = LineString::new(coords).expect("valid 3D linestring");
let shape = geometry_linestring_to_shape(&ls, true, false).expect("PolyLineZ");
if let Shape::PolyLineZ(sz) = shape {
assert_eq!(sz.z_values.len(), 3);
let mut buf = Vec::new();
let record = ShapeRecord::new(1, Shape::PolyLineZ(sz));
record.write(&mut buf).expect("write PolyLineZ");
let shape_type = i32::from_le_bytes(buf[8..12].try_into().expect("4 bytes"));
assert_eq!(shape_type, 13, "PolyLineZ shape type must be 13");
} else {
panic!("Expected PolyLineZ");
}
}
#[test]
fn test_polyline_m_shape_type() {
let coords = vec![
Coordinate::new_2dm(0.0, 0.0, 0.0),
Coordinate::new_2dm(1.0, 1.0, 1.0),
];
let ls = LineString::new(coords).expect("valid M linestring");
let shape = geometry_linestring_to_shape(&ls, false, true).expect("PolyLineM");
if let Shape::PolyLineM(sm) = shape {
assert_eq!(sm.m_values.len(), 2);
let mut buf = Vec::new();
let record = ShapeRecord::new(1, Shape::PolyLineM(sm));
record.write(&mut buf).expect("write PolyLineM");
let shape_type = i32::from_le_bytes(buf[8..12].try_into().expect("4 bytes"));
assert_eq!(shape_type, 23, "PolyLineM shape type must be 23");
} else {
panic!("Expected PolyLineM");
}
}
#[test]
fn test_multilinestring_z_multiple_parts() {
use oxigdal_core::vector::MultiLineString;
let ls1 = LineString::new(vec![
Coordinate::new_3d(0.0, 0.0, 1.0),
Coordinate::new_3d(1.0, 0.0, 2.0),
])
.expect("ls1");
let ls2 = LineString::new(vec![
Coordinate::new_3d(2.0, 0.0, 3.0),
Coordinate::new_3d(3.0, 0.0, 4.0),
])
.expect("ls2");
let mls = MultiLineString::new(vec![ls1, ls2]);
let shape = geometry_multilinestring_to_shape(&mls, true, false).expect("MultiPolyLineZ");
if let Shape::PolyLineZ(sz) = shape {
assert_eq!(sz.base.num_parts, 2, "two parts");
assert_eq!(sz.base.num_points, 4, "four points total");
assert_eq!(sz.z_values.len(), 4);
let mut buf = Vec::new();
let record = ShapeRecord::new(1, Shape::PolyLineZ(sz));
record.write(&mut buf).expect("write");
let shape_type = i32::from_le_bytes(buf[8..12].try_into().expect("4b"));
assert_eq!(shape_type, 13);
} else {
panic!("Expected PolyLineZ");
}
}
}