use super::*;
use crate::cityjson::core::boundary::nested::{
BoundaryNestedMultiLineString32, BoundaryNestedMultiOrCompositeSolid32,
BoundaryNestedMultiOrCompositeSurface32, BoundaryNestedMultiPoint32, BoundaryNestedSolid32,
};
use crate::cityjson::core::vertex::VertexIndex;
use crate::v2_0::{GeometryVertices32, RealWorldCoordinate};
fn vi<T: VertexRef>(value: T) -> VertexIndex<T> {
VertexIndex::new(value)
}
#[test]
fn empty() {
let boundary: Boundary<u32> = Boundary::new();
assert_eq!(boundary.check_type(), BoundaryType::None);
assert!(boundary.is_consistent());
}
#[test]
fn with_capacity() {
let boundary: Boundary<u32> = Boundary::with_capacity(
10, 5, 3, 2, 1, );
assert_eq!(boundary.check_type(), BoundaryType::None);
assert!(boundary.is_consistent());
}
#[test]
fn type_detection() {
let mut multi_point_boundary: Boundary<u32> = Boundary::new();
multi_point_boundary.vertices = vec![vi(0), vi(1), vi(2)];
assert_eq!(multi_point_boundary.check_type(), BoundaryType::MultiPoint);
let mut multi_line_boundary: Boundary<u32> = Boundary::new();
multi_line_boundary.vertices = vec![vi(0), vi(1), vi(2)];
multi_line_boundary.rings = vec![vi(0)];
assert_eq!(
multi_line_boundary.check_type(),
BoundaryType::MultiLineString
);
let mut multi_surface_boundary: Boundary<u32> = Boundary::new();
multi_surface_boundary.vertices = vec![vi(0), vi(1), vi(2)];
multi_surface_boundary.rings = vec![vi(0)];
multi_surface_boundary.surfaces = vec![vi(0)];
assert_eq!(
multi_surface_boundary.check_type(),
BoundaryType::MultiOrCompositeSurface
);
let mut solid_boundary: Boundary<u32> = Boundary::new();
solid_boundary.vertices = vec![vi(0), vi(1), vi(2)];
solid_boundary.rings = vec![vi(0)];
solid_boundary.surfaces = vec![vi(0)];
solid_boundary.shells = vec![vi(0)];
assert_eq!(solid_boundary.check_type(), BoundaryType::Solid);
let mut multi_solid_boundary: Boundary<u32> = Boundary::new();
multi_solid_boundary.vertices = vec![vi(0), vi(1), vi(2)];
multi_solid_boundary.rings = vec![vi(0)];
multi_solid_boundary.surfaces = vec![vi(0)];
multi_solid_boundary.shells = vec![vi(0)];
multi_solid_boundary.solids = vec![vi(0)];
assert_eq!(
multi_solid_boundary.check_type(),
BoundaryType::MultiOrCompositeSolid
);
}
#[test]
fn boundary_consistency() {
let mut consistent: Boundary<u32> = Boundary::new();
consistent.vertices = vec![vi(0), vi(1), vi(2), vi(3)];
consistent.rings = vec![vi(0), vi(2)];
assert!(consistent.is_consistent());
let mut consistent2: Boundary<u32> = Boundary::new();
consistent2.vertices = vec![vi(0), vi(1), vi(2), vi(3), vi(4), vi(5)];
consistent2.rings = vec![vi(0), vi(3), vi(6)]; consistent2.surfaces = vec![vi(0), vi(2)];
assert!(consistent2.is_consistent());
let mut inconsistent: Boundary<u32> = Boundary::new();
inconsistent.vertices = vec![vi(0), vi(1)];
inconsistent.rings = vec![vi(0), vi(3)]; assert!(!inconsistent.is_consistent());
let mut inconsistent2: Boundary<u32> = Boundary::new();
inconsistent2.vertices = vec![vi(0), vi(1), vi(2), vi(3)];
inconsistent2.rings = vec![vi(0)];
inconsistent2.surfaces = vec![vi(0), vi(2)]; assert!(!inconsistent2.is_consistent());
}
#[test]
fn boundary_consistency_allows_empty_segments() {
let nested: BoundaryNestedMultiLineString32 = vec![vec![0, 1, 2], vec![], vec![3, 4]];
let flattened: Boundary<u32> = nested.clone().try_into().unwrap();
assert!(flattened.is_consistent());
assert_eq!(flattened.to_nested_multi_linestring().unwrap(), nested);
}
#[test]
fn boundary_consistency_rejects_singleton_out_of_bounds_offsets() {
let mut ring_boundary: Boundary<u32> = Boundary::new();
ring_boundary.vertices = vec![vi(0), vi(1)];
ring_boundary.rings = vec![vi(3)];
assert!(!ring_boundary.is_consistent());
assert!(ring_boundary.to_nested_multi_linestring().is_err());
let mut surface_boundary: Boundary<u32> = Boundary::new();
surface_boundary.vertices = vec![vi(0)];
surface_boundary.rings = vec![vi(0)];
surface_boundary.surfaces = vec![vi(2)];
assert!(!surface_boundary.is_consistent());
assert!(
surface_boundary
.to_nested_multi_or_composite_surface()
.is_err()
);
let mut shell_boundary: Boundary<u32> = Boundary::new();
shell_boundary.vertices = vec![vi(0)];
shell_boundary.rings = vec![vi(0)];
shell_boundary.surfaces = vec![vi(0)];
shell_boundary.shells = vec![vi(2)];
assert!(!shell_boundary.is_consistent());
assert!(shell_boundary.to_nested_solid().is_err());
let mut solid_boundary: Boundary<u32> = Boundary::new();
solid_boundary.vertices = vec![vi(0)];
solid_boundary.rings = vec![vi(0)];
solid_boundary.surfaces = vec![vi(0)];
solid_boundary.shells = vec![vi(0)];
solid_boundary.solids = vec![vi(2)];
assert!(!solid_boundary.is_consistent());
assert!(solid_boundary.to_nested_multi_or_composite_solid().is_err());
}
#[test]
fn multi_point_conversion() {
let nested: BoundaryNestedMultiPoint32 = vec![0, 1, 2, 3];
let flattened: Boundary<u32> = nested.clone().into();
assert_eq!(flattened.check_type(), BoundaryType::MultiPoint);
let round_trip = flattened.to_nested_multi_point().unwrap();
assert_eq!(round_trip, nested);
let mut multi_line_boundary: Boundary<u32> = Boundary::new();
multi_line_boundary.vertices = vec![vi(0), vi(1), vi(2)];
multi_line_boundary.rings = vec![vi(0)];
assert!(multi_line_boundary.to_nested_multi_point().is_err());
}
#[test]
fn multi_linestring_conversion() {
let nested: BoundaryNestedMultiLineString32 = vec![vec![0, 1, 2], vec![3, 4, 5, 6]];
let flattened: Boundary<u32> = nested.clone().try_into().unwrap();
assert_eq!(flattened.check_type(), BoundaryType::MultiLineString);
let round_trip = flattened.to_nested_multi_linestring().unwrap();
assert_eq!(round_trip, nested);
let mut multi_point_boundary: Boundary<u32> = Boundary::new();
multi_point_boundary.vertices = vec![vi(0), vi(1), vi(2)];
assert!(multi_point_boundary.to_nested_multi_linestring().is_err());
}
#[test]
fn coordinates_iterates_boundary_order() {
let boundary: Boundary<u32> = vec![vec![0, 1, 0, 2]].try_into().unwrap();
let vertices = GeometryVertices32::from(vec![
RealWorldCoordinate::new(10.0, 0.0, 0.0),
RealWorldCoordinate::new(20.0, 0.0, 0.0),
RealWorldCoordinate::new(30.0, 0.0, 0.0),
]);
let xs: Vec<f64> = boundary
.coordinates(&vertices)
.map(RealWorldCoordinate::x)
.collect();
assert_eq!(xs, vec![10.0, 20.0, 10.0, 30.0]);
}
#[test]
fn unique_coordinates_deduplicates_vertex_references() {
let boundary: Boundary<u32> = vec![vec![0, 1, 0, 2], vec![2, 1, 0]].try_into().unwrap();
let vertices = GeometryVertices32::from(vec![
RealWorldCoordinate::new(10.0, 0.0, 0.0),
RealWorldCoordinate::new(20.0, 0.0, 0.0),
RealWorldCoordinate::new(30.0, 0.0, 0.0),
]);
let mut scratch = Vec::new();
let xs: Vec<f64> = boundary
.unique_coordinates(&vertices, &mut scratch)
.map(RealWorldCoordinate::x)
.collect();
assert_eq!(xs, vec![10.0, 20.0, 30.0]);
assert_eq!(
boundary
.unique_vertex_indices(&mut scratch)
.iter()
.map(VertexIndex::to_usize)
.collect::<Vec<_>>(),
vec![0, 1, 2]
);
}
#[test]
fn multi_surface_conversion() {
let nested: BoundaryNestedMultiOrCompositeSurface32 = vec![
vec![vec![0, 1, 2, 0]],
vec![vec![3, 4, 5, 3], vec![6, 7, 8, 6]],
];
let flattened: Boundary<u32> = nested.clone().try_into().unwrap();
assert_eq!(
flattened.check_type(),
BoundaryType::MultiOrCompositeSurface
);
let round_trip = flattened.to_nested_multi_or_composite_surface().unwrap();
assert_eq!(round_trip, nested);
let mut multi_point_boundary: Boundary<u32> = Boundary::new();
multi_point_boundary.vertices = vec![vi(0), vi(1), vi(2)];
assert!(
multi_point_boundary
.to_nested_multi_or_composite_surface()
.is_err()
);
}
#[test]
fn solid_conversion() {
let nested: BoundaryNestedSolid32 = vec![
vec![
vec![vec![0, 1, 2, 3, 0]], vec![vec![4, 5, 6, 7, 4]], vec![vec![0, 3, 7, 4, 0]], vec![vec![1, 2, 6, 5, 1]], vec![vec![0, 1, 5, 4, 0]], vec![vec![3, 2, 6, 7, 3]], ],
];
let flattened: Boundary<u32> = nested.clone().try_into().unwrap();
assert_eq!(flattened.check_type(), BoundaryType::Solid);
let round_trip = flattened.to_nested_solid().unwrap();
assert_eq!(round_trip, nested);
let mut multi_point_boundary: Boundary<u32> = Boundary::new();
multi_point_boundary.vertices = vec![vi(0), vi(1), vi(2)];
assert!(multi_point_boundary.to_nested_solid().is_err());
}
#[test]
fn multi_solid_conversion() {
let nested: BoundaryNestedMultiOrCompositeSolid32 = vec![
vec![vec![vec![vec![0, 1, 2, 0]]]],
vec![vec![vec![vec![3, 4, 5, 3]]]],
];
let flattened: Boundary<u32> = nested.clone().try_into().unwrap();
assert_eq!(flattened.check_type(), BoundaryType::MultiOrCompositeSolid);
let round_trip = flattened.to_nested_multi_or_composite_solid().unwrap();
assert_eq!(round_trip, nested);
let mut multi_point_boundary: Boundary<u32> = Boundary::new();
multi_point_boundary.vertices = vec![vi(0), vi(1), vi(2)];
assert!(
multi_point_boundary
.to_nested_multi_or_composite_solid()
.is_err()
);
}
#[test]
fn display_boundary_type() {
assert_eq!(BoundaryType::None.to_string(), "None");
assert_eq!(BoundaryType::MultiPoint.to_string(), "MultiPoint");
assert_eq!(BoundaryType::MultiLineString.to_string(), "MultiLineString");
assert_eq!(
BoundaryType::MultiOrCompositeSurface.to_string(),
"MultiOrCompositeSurface"
);
assert_eq!(BoundaryType::Solid.to_string(), "Solid");
assert_eq!(
BoundaryType::MultiOrCompositeSolid.to_string(),
"MultiOrCompositeSolid"
);
}
#[test]
fn boundary_counter() {
let mut counter = BoundaryCounter::<u32>::default();
assert_eq!(counter.vertex_offset().value(), 0);
assert_eq!(counter.ring_offset().value(), 0);
assert_eq!(counter.surface_offset().value(), 0);
assert_eq!(counter.shell_offset().value(), 0);
assert_eq!(counter.solid_offset().value(), 0);
assert_eq!(counter.increment_vertex_idx().value(), 1);
assert_eq!(counter.increment_vertex_idx().value(), 2);
assert_eq!(counter.increment_ring_idx().value(), 1);
assert_eq!(counter.increment_surface_idx().value(), 1);
assert_eq!(counter.increment_shell_idx().value(), 1);
assert_eq!(counter.increment_solid_idx().value(), 1);
assert_eq!(counter.vertex_offset().value(), 2);
assert_eq!(counter.ring_offset().value(), 1);
assert_eq!(counter.surface_offset().value(), 1);
assert_eq!(counter.shell_offset().value(), 1);
assert_eq!(counter.solid_offset().value(), 1);
}
#[test]
fn from_parts_unchecked_matches_checked_layout_for_valid_input() {
let nested: BoundaryNestedMultiOrCompositeSolid32 = vec![
vec![
vec![
vec![vec![0, 1, 2, 0]],
vec![vec![3, 4, 5, 3], vec![6, 7, 8, 6]],
],
vec![vec![vec![9, 10, 11, 9]]],
],
vec![vec![vec![vec![12, 13, 14, 12]]]],
];
let checked: Boundary<u32> = nested.clone().try_into().unwrap();
let unchecked = unsafe {
Boundary::from_parts_unchecked(
checked.vertices().to_vec(),
checked.rings().to_vec(),
checked.surfaces().to_vec(),
checked.shells().to_vec(),
checked.solids().to_vec(),
)
};
assert_eq!(unchecked, checked);
assert!(unchecked.is_consistent());
assert_eq!(
unchecked.to_nested_multi_or_composite_solid().unwrap(),
nested
);
}
#[test]
fn from_parts_checked_rejects_invalid_offsets_but_unchecked_preserves_them() {
let vertices = vec![vi(0_u32), vi(1_u32), vi(2_u32), vi(3_u32)];
let rings = vec![vi(1_u32), vi(4_u32)];
let error = Boundary::from_parts(
vertices.clone(),
rings.clone(),
Vec::new(),
Vec::new(),
Vec::new(),
)
.unwrap_err();
assert!(matches!(error, error::Error::InvalidGeometry(_)));
let unchecked = unsafe {
Boundary::from_parts_unchecked(vertices, rings, Vec::new(), Vec::new(), Vec::new())
};
assert_eq!(unchecked.check_type(), BoundaryType::MultiLineString);
assert!(!unchecked.is_consistent());
assert_eq!(&*unchecked.vertices_raw(), &[0, 1, 2, 3]);
assert_eq!(&*unchecked.rings_raw(), &[1, 4]);
assert!(unchecked.to_nested_multi_linestring().is_err());
}
#[cfg(test)]
mod nested_tests {
use super::*;
use crate::cityjson::core::boundary::nested::{
BoundaryNestedMultiLineString16, BoundaryNestedMultiLineString32,
BoundaryNestedMultiOrCompositeSolid16, BoundaryNestedMultiOrCompositeSolid32,
BoundaryNestedMultiOrCompositeSurface16, BoundaryNestedMultiOrCompositeSurface32,
BoundaryNestedMultiPoint16, BoundaryNestedMultiPoint32, BoundaryNestedSolid16,
BoundaryNestedSolid32,
};
use crate::cityjson::core::vertex::VertexIndex;
const U16_MAX: usize = u16::MAX as usize;
const U16_MAX_PLUS_ONE: usize = (u16::MAX as usize) + 1;
#[test]
fn empty_nested_conversions() {
let empty_multi_point: BoundaryNestedMultiPoint32 = vec![];
let boundary: Boundary<u32> = empty_multi_point.into();
assert_eq!(boundary.check_type(), BoundaryType::None);
let empty_multi_linestring: BoundaryNestedMultiLineString32 = vec![];
let boundary: Boundary<u32> = empty_multi_linestring.try_into().unwrap();
assert_eq!(boundary.check_type(), BoundaryType::None);
let empty_multi_surface: BoundaryNestedMultiOrCompositeSurface32 = vec![];
let boundary: Boundary<u32> = empty_multi_surface.try_into().unwrap();
assert_eq!(boundary.check_type(), BoundaryType::None);
let empty_solid: BoundaryNestedSolid32 = vec![];
let boundary: Boundary<u32> = empty_solid.try_into().unwrap();
assert_eq!(boundary.check_type(), BoundaryType::None);
let empty_multisolid: BoundaryNestedMultiOrCompositeSolid32 = vec![];
let boundary: Boundary<u32> = empty_multisolid.try_into().unwrap();
assert_eq!(boundary.check_type(), BoundaryType::None);
}
#[test]
fn nested_multilinestring_with_empty_linestrings() {
let nested: BoundaryNestedMultiLineString32 = vec![
vec![0, 1, 2],
vec![], vec![3, 4, 5],
];
let flattened: Boundary<u32> = nested.clone().try_into().unwrap();
assert_eq!(flattened.check_type(), BoundaryType::MultiLineString);
assert!(flattened.is_consistent());
let round_trip = flattened.to_nested_multi_linestring().unwrap();
assert_eq!(round_trip.len(), 3);
assert_eq!(round_trip[0], vec![0, 1, 2]);
assert_eq!(round_trip[1], Vec::<u32>::new()); assert_eq!(round_trip[2], vec![3, 4, 5]);
}
#[test]
fn nested_multisurface_with_empty_components() {
let nested: BoundaryNestedMultiOrCompositeSurface<u32> = vec![
vec![vec![0, 1, 2, 0]],
vec![], vec![vec![3, 4, 5, 3]],
];
let flattened: Boundary<u32> = nested.clone().try_into().unwrap();
assert_eq!(
flattened.check_type(),
BoundaryType::MultiOrCompositeSurface
);
assert!(flattened.is_consistent());
let round_trip = flattened.to_nested_multi_or_composite_surface().unwrap();
let empty_surface = BoundaryNestedMultiLineString32::default();
assert_eq!(round_trip.len(), 3);
assert_eq!(round_trip[1], empty_surface); }
#[test]
fn type_alias_consistency() {
let mp16: BoundaryNestedMultiPoint16 = vec![0, 1, 2];
let boundary16: Boundary16 = mp16.clone().into();
let mp32: BoundaryNestedMultiPoint32 = vec![0, 1, 2];
let boundary32: Boundary32 = mp32.into();
assert_eq!(boundary16.check_type(), boundary32.check_type());
assert_eq!(boundary16.vertices.len(), boundary32.vertices.len());
let mp16_again = boundary16.to_nested_multi_point().unwrap();
assert_eq!(mp16_again, mp16);
}
#[test]
fn multisolid_with_complex_structure() {
let nested: BoundaryNestedMultiOrCompositeSolid32 = vec![
vec![
vec![
vec![vec![0, 1, 2, 0]], vec![vec![3, 4, 5, 3]], ],
vec![vec![vec![6, 7, 8, 6]]],
],
vec![
vec![vec![vec![9, 10, 11, 9], vec![12, 13, 14, 12]]],
],
];
let flattened: Boundary<u32> = nested.clone().try_into().unwrap();
assert_eq!(flattened.check_type(), BoundaryType::MultiOrCompositeSolid);
assert!(flattened.is_consistent());
let round_trip = flattened.to_nested_multi_or_composite_solid().unwrap();
assert_eq!(round_trip, nested);
}
#[test]
fn nested_solid_with_empty_shells_is_consistent() {
let nested: BoundaryNestedSolid32 = vec![vec![vec![vec![0, 1, 2, 0]]], vec![]];
let flattened: Boundary<u32> = nested.clone().try_into().unwrap();
assert_eq!(flattened.check_type(), BoundaryType::Solid);
assert!(flattened.is_consistent());
assert_eq!(flattened.to_nested_solid().unwrap(), nested);
}
#[test]
fn nested_multisolid_with_empty_solids_is_consistent() {
let nested: BoundaryNestedMultiOrCompositeSolid32 =
vec![vec![vec![vec![vec![0, 1, 2, 0]]]], vec![]];
let flattened: Boundary<u32> = nested.clone().try_into().unwrap();
assert_eq!(flattened.check_type(), BoundaryType::MultiOrCompositeSolid);
assert!(flattened.is_consistent());
assert_eq!(
flattened.to_nested_multi_or_composite_solid().unwrap(),
nested
);
}
#[test]
#[cfg_attr(miri, ignore)] fn nested_multilinestring_u16_max_vertices_roundtrips() {
let nested: BoundaryNestedMultiLineString16 = vec![vec![0; U16_MAX]];
let flattened = Boundary::<u16>::try_from(nested.clone()).unwrap();
assert_eq!(flattened.check_type(), BoundaryType::MultiLineString);
assert!(flattened.is_consistent());
assert_eq!(flattened.vertices.len(), U16_MAX);
assert_eq!(flattened.to_nested_multi_linestring().unwrap(), nested);
}
#[test]
#[cfg_attr(miri, ignore)] fn nested_multisurface_u16_max_rings_roundtrips() {
let nested: BoundaryNestedMultiOrCompositeSurface16 = vec![vec![vec![]; U16_MAX], vec![]];
let flattened = Boundary::<u16>::try_from(nested.clone()).unwrap();
assert_eq!(
flattened.check_type(),
BoundaryType::MultiOrCompositeSurface
);
assert!(flattened.is_consistent());
assert_eq!(flattened.rings.len(), U16_MAX);
assert_eq!(
flattened.to_nested_multi_or_composite_surface().unwrap(),
nested
);
}
#[test]
#[cfg_attr(miri, ignore)] fn nested_solid_u16_max_surfaces_roundtrips() {
let nested: BoundaryNestedSolid16 = vec![vec![vec![]; U16_MAX], vec![]];
let flattened = Boundary::<u16>::try_from(nested.clone()).unwrap();
assert_eq!(flattened.check_type(), BoundaryType::Solid);
assert!(flattened.is_consistent());
assert_eq!(flattened.surfaces.len(), U16_MAX);
assert_eq!(flattened.to_nested_solid().unwrap(), nested);
}
#[test]
#[cfg_attr(miri, ignore)] fn nested_multisolid_u16_max_shells_roundtrips() {
let nested: BoundaryNestedMultiOrCompositeSolid16 = vec![vec![vec![]; U16_MAX], vec![]];
let flattened = Boundary::<u16>::try_from(nested.clone()).unwrap();
assert_eq!(flattened.check_type(), BoundaryType::MultiOrCompositeSolid);
assert!(flattened.is_consistent());
assert_eq!(flattened.shells.len(), U16_MAX);
assert_eq!(
flattened.to_nested_multi_or_composite_solid().unwrap(),
nested
);
}
#[test]
#[cfg_attr(miri, ignore)] fn nested_multilinestring_overflow_returns_index_overflow() {
let nested: BoundaryNestedMultiLineString16 = vec![vec![0; U16_MAX_PLUS_ONE]];
let result = Boundary::<u16>::try_from(nested);
assert_eq!(
result.unwrap_err(),
error::Error::IndexOverflow {
index_type: std::any::type_name::<u16>().to_string(),
value: u16::MAX.to_string(),
}
);
}
#[test]
#[cfg_attr(miri, ignore)] fn nested_multisurface_overflow_returns_index_conversion() {
let nested: BoundaryNestedMultiOrCompositeSurface16 =
vec![vec![vec![]; U16_MAX_PLUS_ONE], vec![]];
let result = Boundary::<u16>::try_from(nested);
assert_eq!(
result.unwrap_err(),
error::Error::IndexConversion {
source_type: "usize".to_string(),
target_type: std::any::type_name::<u16>().to_string(),
value: U16_MAX_PLUS_ONE.to_string(),
}
);
}
#[test]
#[cfg_attr(miri, ignore)] fn nested_solid_overflow_returns_index_conversion() {
let nested: BoundaryNestedSolid16 = vec![vec![vec![]; U16_MAX_PLUS_ONE], vec![]];
let result = Boundary::<u16>::try_from(nested);
assert_eq!(
result.unwrap_err(),
error::Error::IndexConversion {
source_type: "usize".to_string(),
target_type: std::any::type_name::<u16>().to_string(),
value: U16_MAX_PLUS_ONE.to_string(),
}
);
}
#[test]
#[cfg_attr(miri, ignore)] fn nested_multisolid_overflow_returns_index_conversion() {
let nested: BoundaryNestedMultiOrCompositeSolid16 =
vec![vec![vec![]; U16_MAX_PLUS_ONE], vec![]];
let result = Boundary::<u16>::try_from(nested);
assert_eq!(
result.unwrap_err(),
error::Error::IndexConversion {
source_type: "usize".to_string(),
target_type: std::any::type_name::<u16>().to_string(),
value: U16_MAX_PLUS_ONE.to_string(),
}
);
}
#[test]
#[cfg_attr(miri, ignore)] fn to_nested_multilinestring_overflow_returns_err_without_panic() {
let mut boundary = Boundary::<u16>::new();
boundary.vertices = vec![VertexIndex::new(0); U16_MAX_PLUS_ONE];
boundary.rings = vec![VertexIndex::new(0)];
let result = std::panic::catch_unwind(|| boundary.to_nested_multi_linestring());
assert!(result.is_ok());
assert!(result.unwrap().is_err());
}
#[test]
#[cfg_attr(miri, ignore)] fn to_nested_multisurface_overflow_returns_err_without_panic() {
let mut boundary = Boundary::<u16>::new();
boundary.rings = vec![VertexIndex::new(0); U16_MAX_PLUS_ONE];
boundary.surfaces = vec![VertexIndex::new(0)];
let result = std::panic::catch_unwind(|| boundary.to_nested_multi_or_composite_surface());
assert!(result.is_ok());
assert!(result.unwrap().is_err());
}
#[test]
#[cfg_attr(miri, ignore)] fn to_nested_solid_overflow_returns_err_without_panic() {
let mut boundary = Boundary::<u16>::new();
boundary.surfaces = vec![VertexIndex::new(0); U16_MAX_PLUS_ONE];
boundary.shells = vec![VertexIndex::new(0)];
let result = std::panic::catch_unwind(|| boundary.to_nested_solid());
assert!(result.is_ok());
assert!(result.unwrap().is_err());
}
#[test]
#[cfg_attr(miri, ignore)] fn to_nested_multisolid_overflow_returns_err_without_panic() {
let mut boundary = Boundary::<u16>::new();
boundary.shells = vec![VertexIndex::new(0); U16_MAX_PLUS_ONE];
boundary.solids = vec![VertexIndex::new(0)];
let result = std::panic::catch_unwind(|| boundary.to_nested_multi_or_composite_solid());
assert!(result.is_ok());
assert!(result.unwrap().is_err());
}
}
#[cfg(test)]
mod boundary_offsets {
use super::*;
fn valid_multisurface() -> Boundary<u32> {
let mut b = Boundary::<u32>::new();
b.vertices = (0u32..10).map(VertexIndex::new).collect();
b.rings = vec![vi(0), vi(3), vi(6)];
b.surfaces = vec![vi(0), vi(2)];
b
}
#[test]
fn valid_baseline_is_consistent() {
let b = valid_multisurface();
assert!(b.is_consistent(), "baseline must be consistent");
}
#[test]
fn first_ring_offset_not_zero_is_inconsistent() {
let mut b = valid_multisurface();
b.rings[0] = vi(1); assert!(
!b.is_consistent(),
"non-zero first offset must be inconsistent"
);
assert!(b.to_nested_multi_or_composite_surface().is_err());
}
#[test]
fn decreasing_ring_offset_is_inconsistent() {
let mut b = valid_multisurface();
b.rings = vec![vi(0), vi(5), vi(3)];
assert!(!b.is_consistent(), "decreasing offset must be inconsistent");
assert!(b.to_nested_multi_or_composite_surface().is_err());
}
#[test]
fn ring_offset_exceeding_vertex_count_is_inconsistent() {
let mut b = valid_multisurface();
let vlen = u32::try_from(b.vertices.len()).expect("test boundary length must fit u32");
*b.rings.last_mut().unwrap() = vi(vlen + 5);
assert!(
!b.is_consistent(),
"offset exceeding child length must be inconsistent"
);
assert!(b.to_nested_multi_or_composite_surface().is_err());
}
#[test]
fn first_surface_offset_not_zero_is_inconsistent() {
let mut b = valid_multisurface();
b.surfaces[0] = vi(1); assert!(!b.is_consistent());
}
#[test]
fn decreasing_surface_offset_is_inconsistent() {
let mut b = valid_multisurface();
b.surfaces = vec![vi(0), vi(0)]; b.surfaces = vec![vi(2), vi(0)]; assert!(!b.is_consistent());
}
#[test]
fn surface_offset_exceeding_ring_count_is_inconsistent() {
let mut b = valid_multisurface();
let rlen = u32::try_from(b.rings.len()).expect("test ring length must fit u32");
*b.surfaces.last_mut().unwrap() = vi(rlen + 1);
assert!(!b.is_consistent());
}
}
#[cfg(test)]
mod boundary_kind_shapes {
use super::*;
#[test]
fn multisurface_with_shells_is_reported_as_solid() {
let mut b = Boundary::<u32>::new();
b.vertices = vec![vi(0), vi(1), vi(2)];
b.rings = vec![vi(0)];
b.surfaces = vec![vi(0)];
b.shells = vec![vi(0)]; assert_eq!(b.check_type(), BoundaryType::Solid);
assert!(b.to_nested_multi_or_composite_surface().is_err());
}
#[test]
fn multisurface_with_shells_and_solids_is_reported_as_multisolid() {
let mut b = Boundary::<u32>::new();
b.vertices = vec![vi(0), vi(1), vi(2)];
b.rings = vec![vi(0)];
b.surfaces = vec![vi(0)];
b.shells = vec![vi(0)];
b.solids = vec![vi(0)]; assert_eq!(b.check_type(), BoundaryType::MultiOrCompositeSolid);
assert!(b.to_nested_multi_or_composite_surface().is_err());
}
#[test]
fn solid_with_solids_array_is_reported_as_multisolid() {
let mut b = Boundary::<u32>::new();
b.vertices = vec![vi(0), vi(1), vi(2)];
b.rings = vec![vi(0)];
b.surfaces = vec![vi(0)];
b.shells = vec![vi(0)];
b.solids = vec![vi(0)];
assert_eq!(b.check_type(), BoundaryType::MultiOrCompositeSolid);
assert!(b.to_nested_solid().is_err());
}
#[test]
fn multisurface_with_empty_vertices_fails_conversion() {
let mut b = Boundary::<u32>::new();
b.rings = vec![vi(0)];
b.surfaces = vec![vi(0)];
b.rings = vec![vi(0), vi(1)]; assert!(!b.is_consistent());
}
#[test]
fn solid_shape_requires_non_empty_shells_and_empty_solids() {
let mut b = Boundary::<u32>::new();
b.vertices = vec![vi(0), vi(1), vi(2)];
b.rings = vec![vi(0)];
b.surfaces = vec![vi(0)];
b.shells = vec![vi(0)];
assert_eq!(b.check_type(), BoundaryType::Solid);
assert!(b.to_nested_solid().is_ok());
}
#[test]
fn multisolid_with_missing_solids_is_reported_as_solid() {
let mut b = Boundary::<u32>::new();
b.vertices = vec![vi(0), vi(1), vi(2)];
b.rings = vec![vi(0)];
b.surfaces = vec![vi(0)];
b.shells = vec![vi(0)];
assert_eq!(b.check_type(), BoundaryType::Solid);
assert!(b.to_nested_multi_or_composite_solid().is_err());
}
}
#[cfg(test)]
#[cfg(not(miri))]
mod property_tests {
use super::*;
use crate::cityjson::core::boundary::nested::{
BoundaryNestedMultiLineString16, BoundaryNestedMultiOrCompositeSolid16,
BoundaryNestedMultiOrCompositeSurface16, BoundaryNestedMultiPoint16, BoundaryNestedSolid16,
};
use proptest::prelude::*;
fn cast_index<T: VertexRef>(value: u16) -> T {
T::try_from(u32::from(value)).ok().unwrap()
}
fn cast_multi_point<T: VertexRef>(
value: BoundaryNestedMultiPoint16,
) -> BoundaryNestedMultiPoint<T> {
value.into_iter().map(cast_index::<T>).collect()
}
fn cast_multi_linestring<T: VertexRef>(
value: BoundaryNestedMultiLineString16,
) -> BoundaryNestedMultiLineString<T> {
value.into_iter().map(cast_multi_point::<T>).collect()
}
fn cast_multi_surface<T: VertexRef>(
value: BoundaryNestedMultiOrCompositeSurface16,
) -> BoundaryNestedMultiOrCompositeSurface<T> {
value.into_iter().map(cast_multi_linestring::<T>).collect()
}
fn cast_solid<T: VertexRef>(value: BoundaryNestedSolid16) -> BoundaryNestedSolid<T> {
value.into_iter().map(cast_multi_surface::<T>).collect()
}
fn cast_multisolid<T: VertexRef>(
value: BoundaryNestedMultiOrCompositeSolid16,
) -> BoundaryNestedMultiOrCompositeSolid<T> {
value.into_iter().map(cast_solid::<T>).collect()
}
fn assert_multi_point_roundtrip<T: VertexRef + std::fmt::Debug>(
nested: &BoundaryNestedMultiPoint<T>,
) {
let flattened: Boundary<T> = nested.clone().into();
assert_eq!(flattened.check_type(), BoundaryType::MultiPoint);
assert!(flattened.is_consistent());
assert_eq!(flattened.vertices.len(), nested.len());
assert_eq!(flattened.to_nested_multi_point().unwrap(), *nested);
}
fn assert_multi_linestring_roundtrip<T: VertexRef + std::fmt::Debug>(
nested: &BoundaryNestedMultiLineString<T>,
) {
let expected_vertices = nested.iter().map(std::vec::Vec::len).sum::<usize>();
let expected_rings = nested.len();
let flattened = Boundary::<T>::try_from(nested.clone()).unwrap();
assert_eq!(flattened.check_type(), BoundaryType::MultiLineString);
assert!(flattened.is_consistent());
assert_eq!(flattened.vertices.len(), expected_vertices);
assert_eq!(flattened.rings.len(), expected_rings);
assert_eq!(flattened.to_nested_multi_linestring().unwrap(), *nested);
}
fn assert_multi_surface_roundtrip<T: VertexRef + std::fmt::Debug>(
nested: &BoundaryNestedMultiOrCompositeSurface<T>,
) {
let expected_vertices = nested
.iter()
.flatten()
.map(std::vec::Vec::len)
.sum::<usize>();
let expected_rings = nested.iter().map(std::vec::Vec::len).sum::<usize>();
let expected_surfaces = nested.len();
let flattened = Boundary::<T>::try_from(nested.clone()).unwrap();
assert_eq!(
flattened.check_type(),
BoundaryType::MultiOrCompositeSurface
);
assert!(flattened.is_consistent());
assert_eq!(flattened.vertices.len(), expected_vertices);
assert_eq!(flattened.rings.len(), expected_rings);
assert_eq!(flattened.surfaces.len(), expected_surfaces);
assert_eq!(
flattened.to_nested_multi_or_composite_surface().unwrap(),
*nested
);
}
fn assert_solid_roundtrip<T: VertexRef + std::fmt::Debug>(nested: &BoundaryNestedSolid<T>) {
let expected_vertices = nested
.iter()
.flatten()
.flatten()
.map(std::vec::Vec::len)
.sum::<usize>();
let expected_rings = nested
.iter()
.flatten()
.map(std::vec::Vec::len)
.sum::<usize>();
let expected_surfaces = nested.iter().map(std::vec::Vec::len).sum::<usize>();
let expected_shells = nested.len();
let flattened = Boundary::<T>::try_from(nested.clone()).unwrap();
assert_eq!(flattened.check_type(), BoundaryType::Solid);
assert!(flattened.is_consistent());
assert_eq!(flattened.vertices.len(), expected_vertices);
assert_eq!(flattened.rings.len(), expected_rings);
assert_eq!(flattened.surfaces.len(), expected_surfaces);
assert_eq!(flattened.shells.len(), expected_shells);
assert_eq!(flattened.to_nested_solid().unwrap(), *nested);
}
fn assert_multisolid_roundtrip<T: VertexRef + std::fmt::Debug>(
nested: &BoundaryNestedMultiOrCompositeSolid<T>,
) {
let expected_vertices = nested
.iter()
.flatten()
.flatten()
.flatten()
.map(std::vec::Vec::len)
.sum::<usize>();
let expected_rings = nested
.iter()
.flatten()
.flatten()
.map(std::vec::Vec::len)
.sum::<usize>();
let expected_surfaces = nested
.iter()
.flatten()
.map(std::vec::Vec::len)
.sum::<usize>();
let expected_shells = nested.iter().map(std::vec::Vec::len).sum::<usize>();
let expected_solids = nested.len();
let flattened = Boundary::<T>::try_from(nested.clone()).unwrap();
assert_eq!(flattened.check_type(), BoundaryType::MultiOrCompositeSolid);
assert!(flattened.is_consistent());
assert_eq!(flattened.vertices.len(), expected_vertices);
assert_eq!(flattened.rings.len(), expected_rings);
assert_eq!(flattened.surfaces.len(), expected_surfaces);
assert_eq!(flattened.shells.len(), expected_shells);
assert_eq!(flattened.solids.len(), expected_solids);
assert_eq!(
flattened.to_nested_multi_or_composite_solid().unwrap(),
*nested
);
}
fn assert_across_widths<I16, F16, F32, F64>(
nested: &I16,
assert_u16: F16,
assert_u32: F32,
assert_u64: F64,
) where
I16: Clone,
F16: FnOnce(&I16),
F32: FnOnce(I16),
F64: FnOnce(I16),
{
assert_u16(nested);
assert_u32(nested.clone());
assert_u64(nested.clone());
}
fn offset_u32(value: usize) -> VertexIndex<u32> {
VertexIndex::try_from(value).unwrap()
}
fn malformed_boundary_strategy() -> impl Strategy<Value = Boundary<u32>> {
prop_oneof![
(1usize..6, 0usize..4).prop_map(|(start, extra)| {
let mut boundary = Boundary::new();
boundary.vertices = vec![VertexIndex::new(0u32); start + extra];
boundary.rings = vec![offset_u32(start)];
boundary
}),
(2usize..8, 1usize..4).prop_map(|(mid, tail)| {
let mut boundary = Boundary::new();
boundary.vertices = vec![VertexIndex::new(0u32); mid + tail];
boundary.rings = vec![offset_u32(0), offset_u32(mid), offset_u32(mid - 1)];
boundary
}),
(0usize..6, 1usize..4).prop_map(|(len, delta)| {
let mut boundary = Boundary::new();
boundary.vertices = vec![VertexIndex::new(0u32); len];
boundary.rings = vec![offset_u32(0), offset_u32(len + delta)];
boundary
}),
(1usize..4).prop_map(|delta| {
let mut boundary = Boundary::new();
boundary.vertices = vec![VertexIndex::new(0u32)];
boundary.rings = vec![offset_u32(0)];
boundary.surfaces = vec![offset_u32(1 + delta)];
boundary
}),
(1usize..4).prop_map(|delta| {
let mut boundary = Boundary::new();
boundary.vertices = vec![VertexIndex::new(0u32)];
boundary.rings = vec![offset_u32(0)];
boundary.surfaces = vec![offset_u32(0)];
boundary.shells = vec![offset_u32(1 + delta)];
boundary
}),
(1usize..4).prop_map(|delta| {
let mut boundary = Boundary::new();
boundary.vertices = vec![VertexIndex::new(0u32)];
boundary.rings = vec![offset_u32(0)];
boundary.surfaces = vec![offset_u32(0)];
boundary.shells = vec![offset_u32(0)];
boundary.solids = vec![offset_u32(1 + delta)];
boundary
}),
]
}
fn assert_inconsistent_boundary_rejected(boundary: &Boundary<u32>) {
assert!(!boundary.is_consistent());
let result = std::panic::catch_unwind(|| match boundary.check_type() {
BoundaryType::MultiLineString => boundary.to_nested_multi_linestring().map(|_| ()),
BoundaryType::MultiOrCompositeSurface => {
boundary.to_nested_multi_or_composite_surface().map(|_| ())
}
BoundaryType::Solid => boundary.to_nested_solid().map(|_| ()),
BoundaryType::MultiOrCompositeSolid => {
boundary.to_nested_multi_or_composite_solid().map(|_| ())
}
other => panic!("unexpected malformed boundary type: {other:?}"),
});
assert!(result.is_ok());
assert!(result.unwrap().is_err());
}
fn valid_multi_point_strategy() -> impl Strategy<Value = BoundaryNestedMultiPoint16> {
prop::collection::vec(0u16..128, 1..8)
}
fn valid_multi_linestring_strategy() -> impl Strategy<Value = BoundaryNestedMultiLineString16> {
prop::collection::vec(prop::collection::vec(0u16..128, 0..6), 1..5)
}
fn valid_multi_surface_strategy()
-> impl Strategy<Value = BoundaryNestedMultiOrCompositeSurface16> {
prop::collection::vec(valid_multi_linestring_strategy(), 1..4)
}
fn valid_solid_strategy() -> impl Strategy<Value = BoundaryNestedSolid16> {
prop::collection::vec(valid_multi_surface_strategy(), 1..3)
}
fn valid_multisolid_strategy() -> impl Strategy<Value = BoundaryNestedMultiOrCompositeSolid16> {
prop::collection::vec(valid_solid_strategy(), 1..3)
}
proptest! {
#[test]
fn valid_multi_point_roundtrips_across_widths(nested in valid_multi_point_strategy()) {
assert_across_widths(
&nested,
assert_multi_point_roundtrip::<u16>,
|value| assert_multi_point_roundtrip::<u32>(&cast_multi_point::<u32>(value.clone())),
|value| assert_multi_point_roundtrip::<u64>(&cast_multi_point::<u64>(value.clone())),
);
}
#[test]
fn valid_multi_linestring_roundtrips_across_widths(
nested in valid_multi_linestring_strategy()
) {
assert_across_widths(
&nested,
assert_multi_linestring_roundtrip::<u16>,
|value| assert_multi_linestring_roundtrip::<u32>(&cast_multi_linestring::<u32>(value.clone())),
|value| assert_multi_linestring_roundtrip::<u64>(&cast_multi_linestring::<u64>(value.clone())),
);
}
#[test]
fn valid_multi_surface_roundtrips_across_widths(
nested in valid_multi_surface_strategy()
) {
assert_across_widths(
&nested,
assert_multi_surface_roundtrip::<u16>,
|value| assert_multi_surface_roundtrip::<u32>(&cast_multi_surface::<u32>(value.clone())),
|value| assert_multi_surface_roundtrip::<u64>(&cast_multi_surface::<u64>(value.clone())),
);
}
#[test]
fn valid_solid_roundtrips_across_widths(nested in valid_solid_strategy()) {
assert_across_widths(
&nested,
assert_solid_roundtrip::<u16>,
|value| assert_solid_roundtrip::<u32>(&cast_solid::<u32>(value.clone())),
|value| assert_solid_roundtrip::<u64>(&cast_solid::<u64>(value.clone())),
);
}
#[test]
fn valid_multisolid_roundtrips_across_widths(nested in valid_multisolid_strategy()) {
assert_across_widths(
&nested,
assert_multisolid_roundtrip::<u16>,
|value| assert_multisolid_roundtrip::<u32>(&cast_multisolid::<u32>(value.clone())),
|value| assert_multisolid_roundtrip::<u64>(&cast_multisolid::<u64>(value.clone())),
);
}
#[test]
fn malformed_flattened_boundaries_are_rejected(boundary in malformed_boundary_strategy()) {
assert_inconsistent_boundary_rejected(&boundary);
}
}
}