use crate::error::{Result, ShapefileError};
use crate::shp::Shape;
use crate::shp::shapes::{MultiPartShape, MultiPartShapeM, MultiPartShapeZ, Point};
use oxigdal_core::vector::{MultiPolygon, Polygon};
pub fn geometry_polygon_to_shape(polygon: &Polygon, has_z: bool, has_m: bool) -> Result<Shape> {
let mut all_points: Vec<Point> = Vec::new();
let mut parts: Vec<i32> = Vec::new();
parts.push(all_points.len() as i32);
for coord in &polygon.exterior.coords {
all_points.push(Point::new(coord.x, coord.y));
}
for interior in &polygon.interiors {
parts.push(all_points.len() as i32);
for coord in &interior.coords {
all_points.push(Point::new(coord.x, coord.y));
}
}
if all_points.is_empty() {
return Err(ShapefileError::invalid_geometry(
"Polygon must have at least one point",
));
}
if has_z {
let z_values: Vec<f64> = polygon
.exterior
.coords
.iter()
.chain(polygon.interiors.iter().flat_map(|r| r.coords.iter()))
.map(|c| c.z.unwrap_or(0.0))
.collect();
let m_values_opt: Option<Vec<f64>> = if has_m {
Some(
polygon
.exterior
.coords
.iter()
.chain(polygon.interiors.iter().flat_map(|r| r.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::PolygonZ(shape_z))
} else if has_m {
let m_values: Vec<f64> = polygon
.exterior
.coords
.iter()
.chain(polygon.interiors.iter().flat_map(|r| r.coords.iter()))
.map(|c| c.m.unwrap_or(0.0))
.collect();
let shape_m = MultiPartShapeM::new(parts, all_points, m_values)?;
Ok(Shape::PolygonM(shape_m))
} else {
let shape = MultiPartShape::new(parts, all_points)?;
Ok(Shape::Polygon(shape))
}
}
pub fn geometry_multipolygon_to_shape(
multipolygon: &MultiPolygon,
has_z: bool,
has_m: bool,
) -> Result<Shape> {
let mut all_points: Vec<Point> = Vec::new();
let mut parts: Vec<i32> = Vec::new();
for polygon in &multipolygon.polygons {
parts.push(all_points.len() as i32);
for coord in &polygon.exterior.coords {
all_points.push(Point::new(coord.x, coord.y));
}
for interior in &polygon.interiors {
parts.push(all_points.len() as i32);
for coord in &interior.coords {
all_points.push(Point::new(coord.x, coord.y));
}
}
}
if all_points.is_empty() {
return Err(ShapefileError::invalid_geometry(
"MultiPolygon must have at least one point",
));
}
if has_z {
let z_values: Vec<f64> = multipolygon
.polygons
.iter()
.flat_map(|poly| {
poly.exterior
.coords
.iter()
.chain(poly.interiors.iter().flat_map(|r| r.coords.iter()))
})
.map(|c| c.z.unwrap_or(0.0))
.collect();
let m_values_opt: Option<Vec<f64>> = if has_m {
Some(
multipolygon
.polygons
.iter()
.flat_map(|poly| {
poly.exterior
.coords
.iter()
.chain(poly.interiors.iter().flat_map(|r| r.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::PolygonZ(shape_z))
} else if has_m {
let m_values: Vec<f64> = multipolygon
.polygons
.iter()
.flat_map(|poly| {
poly.exterior
.coords
.iter()
.chain(poly.interiors.iter().flat_map(|r| r.coords.iter()))
})
.map(|c| c.m.unwrap_or(0.0))
.collect();
let shape_m = MultiPartShapeM::new(parts, all_points, m_values)?;
Ok(Shape::PolygonM(shape_m))
} else {
let shape = MultiPartShape::new(parts, all_points)?;
Ok(Shape::Polygon(shape))
}
}
#[cfg(test)]
#[allow(clippy::panic)]
mod tests {
use super::*;
use oxigdal_core::vector::{Coordinate, LineString};
fn make_square_exterior_2d() -> LineString {
LineString::new(vec![
Coordinate::new_2d(0.0, 0.0),
Coordinate::new_2d(1.0, 0.0),
Coordinate::new_2d(1.0, 1.0),
Coordinate::new_2d(0.0, 1.0),
Coordinate::new_2d(0.0, 0.0),
])
.expect("valid exterior")
}
fn make_square_exterior_3d() -> LineString {
LineString::new(vec![
Coordinate::new_3d(0.0, 0.0, 1.0),
Coordinate::new_3d(1.0, 0.0, 2.0),
Coordinate::new_3d(1.0, 1.0, 3.0),
Coordinate::new_3d(0.0, 1.0, 4.0),
Coordinate::new_3d(0.0, 0.0, 1.0),
])
.expect("valid 3D exterior")
}
#[test]
fn test_polygon_2d() {
let exterior = make_square_exterior_2d();
let poly = Polygon::new(exterior, vec![]).expect("valid polygon");
let shape = geometry_polygon_to_shape(&poly, false, false).expect("2D polygon");
assert!(matches!(shape, Shape::Polygon(_)));
}
#[test]
fn test_polygon_z_shape_type() {
let exterior = make_square_exterior_3d();
let poly = Polygon::new(exterior, vec![]).expect("valid 3D polygon");
let shape = geometry_polygon_to_shape(&poly, true, false).expect("PolygonZ");
if let Shape::PolygonZ(sz) = shape {
assert_eq!(sz.base.num_points, 5);
assert_eq!(sz.z_values.len(), 5);
use crate::shp::ShapeRecord;
let mut buf = Vec::new();
let record = ShapeRecord::new(1, Shape::PolygonZ(sz));
record.write(&mut buf).expect("write PolygonZ");
let shape_type = i32::from_le_bytes(buf[8..12].try_into().expect("4 bytes"));
assert_eq!(shape_type, 15, "PolygonZ shape type must be 15");
} else {
panic!("Expected PolygonZ, got {:?}", shape);
}
}
#[test]
fn test_polygon_m_shape_type() {
let exterior = LineString::new(vec![
Coordinate::new_2dm(0.0, 0.0, 0.5),
Coordinate::new_2dm(1.0, 0.0, 0.5),
Coordinate::new_2dm(1.0, 1.0, 0.5),
Coordinate::new_2dm(0.0, 1.0, 0.5),
Coordinate::new_2dm(0.0, 0.0, 0.5),
])
.expect("valid M exterior");
let poly = Polygon::new(exterior, vec![]).expect("valid polygon M");
let shape = geometry_polygon_to_shape(&poly, false, true).expect("PolygonM");
if let Shape::PolygonM(sm) = shape {
assert_eq!(sm.base.num_points, 5);
use crate::shp::ShapeRecord;
let mut buf = Vec::new();
let record = ShapeRecord::new(1, Shape::PolygonM(sm));
record.write(&mut buf).expect("write PolygonM");
let shape_type = i32::from_le_bytes(buf[8..12].try_into().expect("4 bytes"));
assert_eq!(shape_type, 25, "PolygonM shape type must be 25");
} else {
panic!("Expected PolygonM, got {:?}", shape);
}
}
}