pub mod nested;
use super::vertices::Vertices;
use crate::cityjson::core::boundary::nested::{
BoundaryNestedMultiLineString, BoundaryNestedMultiOrCompositeSolid,
BoundaryNestedMultiOrCompositeSurface, BoundaryNestedMultiPoint, BoundaryNestedSolid,
};
use crate::cityjson::core::coordinate::Coordinate;
use crate::cityjson::core::vertex::VertexRef;
use crate::cityjson::core::vertex::{RawVertexView, VertexIndex};
use crate::error;
pub type Boundary16 = Boundary<u16>;
pub type Boundary32 = Boundary<u32>;
pub type Boundary64 = Boundary<u64>;
#[repr(C)]
#[derive(Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct Boundary<VR: VertexRef> {
pub(crate) vertices: Vec<VertexIndex<VR>>,
pub(crate) rings: Vec<VertexIndex<VR>>,
pub(crate) surfaces: Vec<VertexIndex<VR>>,
pub(crate) shells: Vec<VertexIndex<VR>>,
pub(crate) solids: Vec<VertexIndex<VR>>,
}
#[derive(Debug, Clone, Copy)]
pub struct BoundaryColumnar<'a, VR: VertexRef> {
pub vertices: &'a [VertexIndex<VR>],
pub ring_offsets: &'a [VertexIndex<VR>],
pub surface_offsets: &'a [VertexIndex<VR>],
pub shell_offsets: &'a [VertexIndex<VR>],
pub solid_offsets: &'a [VertexIndex<VR>],
}
pub struct BoundaryCoordinates<'a, VR: VertexRef, V: Coordinate> {
indices: std::slice::Iter<'a, VertexIndex<VR>>,
vertices: &'a Vertices<VR, V>,
}
impl<'a, VR: VertexRef, V: Coordinate> Iterator for BoundaryCoordinates<'a, VR, V> {
type Item = &'a V;
fn next(&mut self) -> Option<Self::Item> {
loop {
let index = *self.indices.next()?;
if let Some(coordinate) = self.vertices.get(index) {
return Some(coordinate);
}
}
}
}
pub struct BoundaryUniqueCoordinates<'a, VR: VertexRef, V: Coordinate> {
indices: std::slice::Iter<'a, VertexIndex<VR>>,
vertices: &'a Vertices<VR, V>,
}
impl<'a, VR: VertexRef, V: Coordinate> Iterator for BoundaryUniqueCoordinates<'a, VR, V> {
type Item = &'a V;
fn next(&mut self) -> Option<Self::Item> {
loop {
let index = *self.indices.next()?;
if let Some(coordinate) = self.vertices.get(index) {
return Some(coordinate);
}
}
}
}
impl<VR: VertexRef> Boundary<VR> {
#[inline]
fn offsets_are_consistent(offsets: &[VertexIndex<VR>], child_len: usize) -> bool {
let Some(first) = offsets.first() else {
return true;
};
if !first.is_zero() {
return false;
}
if offsets
.last()
.is_some_and(|last| last.to_usize() > child_len)
{
return false;
}
offsets.windows(2).all(|window| {
let start = window[0].to_usize();
let end = window[1].to_usize();
start <= end && end <= child_len
})
}
fn ensure_convertible_as(&self, expected: BoundaryType) -> error::Result<()> {
let boundary_type = self.check_type();
if boundary_type != expected {
return Err(error::Error::IncompatibleBoundary(
boundary_type.to_string(),
expected.to_string(),
));
}
if !self.is_consistent() {
return Err(error::Error::InvalidGeometry(format!(
"inconsistent {expected} boundary offsets"
)));
}
Ok(())
}
#[inline]
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[inline]
#[must_use]
pub fn with_capacity(
vertices: usize,
rings: usize,
surfaces: usize,
shells: usize,
solids: usize,
) -> Self {
Self {
vertices: Vec::with_capacity(vertices),
rings: Vec::with_capacity(rings),
surfaces: Vec::with_capacity(surfaces),
shells: Vec::with_capacity(shells),
solids: Vec::with_capacity(solids),
}
}
pub fn from_parts(
vertices: Vec<VertexIndex<VR>>,
rings: Vec<VertexIndex<VR>>,
surfaces: Vec<VertexIndex<VR>>,
shells: Vec<VertexIndex<VR>>,
solids: Vec<VertexIndex<VR>>,
) -> error::Result<Self> {
let boundary = Self {
vertices,
rings,
surfaces,
shells,
solids,
};
if !boundary.is_consistent() {
return Err(error::Error::InvalidGeometry(
"inconsistent boundary offsets".to_owned(),
));
}
Ok(boundary)
}
#[must_use]
pub unsafe fn from_parts_unchecked(
vertices: Vec<VertexIndex<VR>>,
rings: Vec<VertexIndex<VR>>,
surfaces: Vec<VertexIndex<VR>>,
shells: Vec<VertexIndex<VR>>,
solids: Vec<VertexIndex<VR>>,
) -> Self {
Self {
vertices,
rings,
surfaces,
shells,
solids,
}
}
#[must_use]
pub fn vertices_raw(&self) -> RawVertexView<'_, VR> {
RawVertexView(&self.vertices)
}
#[must_use]
pub fn rings_raw(&self) -> RawVertexView<'_, VR> {
RawVertexView(&self.rings)
}
#[must_use]
pub fn surfaces_raw(&self) -> RawVertexView<'_, VR> {
RawVertexView(&self.surfaces)
}
#[must_use]
pub fn shells_raw(&self) -> RawVertexView<'_, VR> {
RawVertexView(&self.shells)
}
#[must_use]
pub fn solids_raw(&self) -> RawVertexView<'_, VR> {
RawVertexView(&self.solids)
}
#[inline]
#[must_use]
pub fn to_columnar(&self) -> BoundaryColumnar<'_, VR> {
BoundaryColumnar {
vertices: &self.vertices,
ring_offsets: &self.rings,
surface_offsets: &self.surfaces,
shell_offsets: &self.shells,
solid_offsets: &self.solids,
}
}
#[inline]
#[must_use]
pub fn vertices(&self) -> &[VertexIndex<VR>] {
&self.vertices
}
#[inline]
#[must_use]
pub fn coordinates<'a, V: Coordinate>(
&'a self,
vertices: &'a Vertices<VR, V>,
) -> BoundaryCoordinates<'a, VR, V> {
BoundaryCoordinates {
indices: self.vertices.iter(),
vertices,
}
}
#[inline]
pub fn unique_vertex_indices<'a>(
&self,
scratch: &'a mut Vec<VertexIndex<VR>>,
) -> &'a [VertexIndex<VR>] {
scratch.clear();
scratch.extend_from_slice(&self.vertices);
scratch.sort_unstable();
scratch.dedup();
scratch.as_slice()
}
#[inline]
#[must_use]
pub fn unique_coordinates<'a, V: Coordinate>(
&'a self,
vertices: &'a Vertices<VR, V>,
scratch: &'a mut Vec<VertexIndex<VR>>,
) -> BoundaryUniqueCoordinates<'a, VR, V> {
let indices = self.unique_vertex_indices(scratch);
BoundaryUniqueCoordinates {
indices: indices.iter(),
vertices,
}
}
pub fn set_vertices_from_iter<I>(&mut self, iter: I)
where
I: IntoIterator<Item = VertexIndex<VR>>,
{
self.vertices = iter.into_iter().collect();
}
#[inline]
#[must_use]
pub fn rings(&self) -> &[VertexIndex<VR>] {
&self.rings
}
pub fn set_rings_from_iter<I>(&mut self, iter: I)
where
I: IntoIterator<Item = VertexIndex<VR>>,
{
self.rings = iter.into_iter().collect();
}
#[inline]
#[must_use]
pub fn surfaces(&self) -> &[VertexIndex<VR>] {
&self.surfaces
}
pub fn set_surfaces_from_iter<I>(&mut self, iter: I)
where
I: IntoIterator<Item = VertexIndex<VR>>,
{
self.surfaces = iter.into_iter().collect();
}
#[inline]
#[must_use]
pub fn shells(&self) -> &[VertexIndex<VR>] {
&self.shells
}
pub fn set_shells_from_iter<I>(&mut self, iter: I)
where
I: IntoIterator<Item = VertexIndex<VR>>,
{
self.shells = iter.into_iter().collect();
}
#[inline]
#[must_use]
pub fn solids(&self) -> &[VertexIndex<VR>] {
&self.solids
}
pub fn set_solids_from_iter<I>(&mut self, iter: I)
where
I: IntoIterator<Item = VertexIndex<VR>>,
{
self.solids = iter.into_iter().collect();
}
pub fn to_nested_multi_point(&self) -> error::Result<BoundaryNestedMultiPoint<VR>> {
self.ensure_convertible_as(BoundaryType::MultiPoint)?;
Ok(self
.vertices
.iter()
.map(super::vertex::VertexIndex::value)
.collect())
}
pub fn to_nested_multi_linestring(&self) -> error::Result<BoundaryNestedMultiLineString<VR>> {
self.ensure_convertible_as(BoundaryType::MultiLineString)?;
let mut counter = BoundaryCounter::<VR>::default();
let mut ml = BoundaryNestedMultiLineString::with_capacity(self.rings.len());
self.push_rings_to_surface(self.rings.as_slice(), &mut ml, &mut counter)?;
Ok(ml)
}
pub fn to_nested_multi_or_composite_surface(
&self,
) -> error::Result<BoundaryNestedMultiOrCompositeSurface<VR>> {
self.ensure_convertible_as(BoundaryType::MultiOrCompositeSurface)?;
let mut counter = BoundaryCounter::<VR>::default();
let mut mc_surface =
BoundaryNestedMultiOrCompositeSurface::with_capacity(self.surfaces.len());
self.push_surfaces_to_multi_surface(
self.surfaces.as_slice(),
&mut mc_surface,
&mut counter,
)?;
Ok(mc_surface)
}
pub fn to_nested_solid(&self) -> error::Result<BoundaryNestedSolid<VR>> {
self.ensure_convertible_as(BoundaryType::Solid)?;
let mut counter = BoundaryCounter::<VR>::default();
let mut solid = BoundaryNestedSolid::with_capacity(self.shells.len());
self.push_shells_to_solid(self.shells.as_slice(), &mut solid, &mut counter)?;
Ok(solid)
}
pub fn to_nested_multi_or_composite_solid(
&self,
) -> error::Result<BoundaryNestedMultiOrCompositeSolid<VR>> {
self.ensure_convertible_as(BoundaryType::MultiOrCompositeSolid)?;
let mut counter = BoundaryCounter::<VR>::default();
let mut mc_solid = BoundaryNestedMultiOrCompositeSolid::with_capacity(self.solids.len());
for &shells_start_i in &self.solids {
let shells_len = VertexIndex::<VR>::try_from(self.shells.len())?;
let shells_end_i = self
.solids
.get(counter.try_increment_solid_idx()?.to_usize())
.copied()
.unwrap_or(shells_len);
if let Some(shells) = self
.shells
.get(shells_start_i.to_usize()..shells_end_i.to_usize())
{
let mut solid = BoundaryNestedSolid::with_capacity(shells.len());
self.push_shells_to_solid(shells, &mut solid, &mut counter)?;
mc_solid.push(solid);
}
}
Ok(mc_solid)
}
fn push_shells_to_solid(
&self,
shells: &[VertexIndex<VR>],
solid: &mut Vec<BoundaryNestedMultiOrCompositeSurface<VR>>,
counter: &mut BoundaryCounter<VR>,
) -> error::Result<()> {
for &surfaces_start_i in shells {
let surfaces_len = VertexIndex::<VR>::try_from(self.surfaces.len())?;
let surfaces_end_i = self
.shells
.get(counter.try_increment_shell_idx()?.to_usize())
.copied()
.unwrap_or(surfaces_len);
if let Some(surfaces) = self
.surfaces
.get(surfaces_start_i.to_usize()..surfaces_end_i.to_usize())
{
let mut mc_surface =
BoundaryNestedMultiOrCompositeSurface::with_capacity(surfaces.len());
self.push_surfaces_to_multi_surface(surfaces, &mut mc_surface, counter)?;
solid.push(mc_surface);
}
}
Ok(())
}
fn push_surfaces_to_multi_surface(
&self,
surfaces: &[VertexIndex<VR>],
mc_surface: &mut BoundaryNestedMultiOrCompositeSurface<VR>,
counter: &mut BoundaryCounter<VR>,
) -> error::Result<()> {
for &ring_start_i in surfaces {
let rings_len = VertexIndex::<VR>::try_from(self.rings.len())?;
let ring_end_i = self
.surfaces
.get(counter.try_increment_surface_idx()?.to_usize())
.copied()
.unwrap_or(rings_len);
if let Some(rings) = self
.rings
.get(ring_start_i.to_usize()..ring_end_i.to_usize())
{
let mut surface = BoundaryNestedMultiLineString::with_capacity(rings.len());
self.push_rings_to_surface(rings, &mut surface, counter)?;
mc_surface.push(surface);
}
}
Ok(())
}
fn push_rings_to_surface(
&self,
rings: &[VertexIndex<VR>],
surface: &mut BoundaryNestedMultiLineString<VR>,
counter: &mut BoundaryCounter<VR>,
) -> error::Result<()> {
for &vertices_start_i in rings {
let vertices_len = VertexIndex::<VR>::try_from(self.vertices.len())?;
let vertices_end_i = self
.rings
.get(counter.try_increment_ring_idx()?.to_usize())
.copied()
.unwrap_or(vertices_len);
if let Some(vertices) = self
.vertices
.get(vertices_start_i.to_usize()..vertices_end_i.to_usize())
{
surface.push(
vertices
.iter()
.map(super::vertex::VertexIndex::value)
.collect(),
);
}
}
Ok(())
}
#[must_use]
pub fn check_type(&self) -> BoundaryType {
if !self.solids.is_empty() {
BoundaryType::MultiOrCompositeSolid
} else if !self.shells.is_empty() {
BoundaryType::Solid
} else if !self.surfaces.is_empty() {
BoundaryType::MultiOrCompositeSurface
} else if !self.rings.is_empty() {
BoundaryType::MultiLineString
} else if !self.vertices.is_empty() {
BoundaryType::MultiPoint
} else {
BoundaryType::None
}
}
#[must_use]
pub fn is_consistent(&self) -> bool {
Self::offsets_are_consistent(&self.rings, self.vertices.len())
&& Self::offsets_are_consistent(&self.surfaces, self.rings.len())
&& Self::offsets_are_consistent(&self.shells, self.surfaces.len())
&& Self::offsets_are_consistent(&self.solids, self.shells.len())
}
}
#[derive(Copy, Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)]
#[non_exhaustive]
pub enum BoundaryType {
MultiOrCompositeSolid,
Solid,
MultiOrCompositeSurface,
MultiLineString,
MultiPoint,
#[default]
None,
}
impl std::fmt::Display for BoundaryType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
BoundaryType::MultiOrCompositeSolid => "MultiOrCompositeSolid",
BoundaryType::Solid => "Solid",
BoundaryType::MultiOrCompositeSurface => "MultiOrCompositeSurface",
BoundaryType::MultiLineString => "MultiLineString",
BoundaryType::MultiPoint => "MultiPoint",
BoundaryType::None => "None",
};
write!(f, "{s}")
}
}
#[derive(Default)]
pub(crate) struct BoundaryCounter<VR: VertexRef> {
#[cfg(test)]
pub(crate) vertex: VertexIndex<VR>, pub(crate) ring: VertexIndex<VR>, pub(crate) surface: VertexIndex<VR>, pub(crate) shell: VertexIndex<VR>, pub(crate) solid: VertexIndex<VR>, }
impl<VR: VertexRef> BoundaryCounter<VR> {
#[inline]
fn increment_checked(offset: &mut VertexIndex<VR>) -> error::Result<VertexIndex<VR>> {
*offset = offset.next().ok_or_else(|| error::Error::IndexOverflow {
index_type: std::any::type_name::<VR>().to_string(),
value: offset.value().to_string(),
})?;
Ok(*offset)
}
#[cfg(test)]
pub(crate) fn increment_vertex_idx(&mut self) -> VertexIndex<VR> {
self.vertex += VertexIndex::new(VR::one());
self.vertex
}
#[cfg(test)]
pub(crate) fn increment_ring_idx(&mut self) -> VertexIndex<VR> {
self.ring += VertexIndex::new(VR::one());
self.ring
}
#[cfg(test)]
pub(crate) fn increment_surface_idx(&mut self) -> VertexIndex<VR> {
self.surface += VertexIndex::new(VR::one());
self.surface
}
#[cfg(test)]
pub(crate) fn increment_shell_idx(&mut self) -> VertexIndex<VR> {
self.shell += VertexIndex::new(VR::one());
self.shell
}
#[cfg(test)]
pub(crate) fn increment_solid_idx(&mut self) -> VertexIndex<VR> {
self.solid += VertexIndex::new(VR::one());
self.solid
}
pub(crate) fn try_increment_ring_idx(&mut self) -> error::Result<VertexIndex<VR>> {
Self::increment_checked(&mut self.ring)
}
pub(crate) fn try_increment_surface_idx(&mut self) -> error::Result<VertexIndex<VR>> {
Self::increment_checked(&mut self.surface)
}
pub(crate) fn try_increment_shell_idx(&mut self) -> error::Result<VertexIndex<VR>> {
Self::increment_checked(&mut self.shell)
}
pub(crate) fn try_increment_solid_idx(&mut self) -> error::Result<VertexIndex<VR>> {
Self::increment_checked(&mut self.solid)
}
#[cfg(test)]
pub(crate) fn vertex_offset(&self) -> VertexIndex<VR> {
self.vertex
}
#[cfg(test)]
pub(crate) fn ring_offset(&self) -> VertexIndex<VR> {
self.ring
}
#[cfg(test)]
pub(crate) fn surface_offset(&self) -> VertexIndex<VR> {
self.surface
}
#[cfg(test)]
pub(crate) fn shell_offset(&self) -> VertexIndex<VR> {
self.shell
}
#[cfg(test)]
pub(crate) fn solid_offset(&self) -> VertexIndex<VR> {
self.solid
}
}
#[cfg(test)]
mod tests;