//! Parcel data model and subdivision/edit entry points (spec §3, §4). //! //! A [`Parcel`] is a simple polygon with exactly one frontage edge //! coincident with a road segment (invariant **I2**). Parcels are //! grouped by their containing block (face), and the whole crate's //! public output is a [`ParcelSet`]. //! //! Subdivision and edit operations are exposed via the free //! functions [`subdivide_all`] and [`apply_road_edit`]. pub mod classify; pub mod deform; pub mod regularize; pub mod subdivide; use std::collections::HashMap; use std::fmt; use glam::DVec2; use slotmap::{new_key_type, SlotMap}; use crate::geometry::Polygon; use crate::network::graph::FaceId; use crate::network::RoadId; pub use deform::{apply_road_edit, EditOutcome, RoadEdit}; pub use subdivide::{subdivide_all, subdivide_all_with_stats, SubdivisionStats}; new_key_type! { /// Stable identifier for a [`Parcel`]. pub struct ParcelId; } /// Kind of a parcel boundary edge (spec §3.2). #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum EdgeKind { /// Edge lies coincident with a road segment. Frontage, /// Edge is adjacent to the frontage edge in the parcel ring. Side, /// All other edges. Back, } /// Trait that downstream code implements for buildings to participate /// in deform-time fit checks (spec §4.6). pub trait BuildingFitCheck: 'static { /// True iff the building still fits inside `parcel`. fn fits_in(&self, parcel: &Parcel) -> bool; } /// Owning wrapper around a `BuildingFitCheck`. Stored on parcels via /// `Option`. /// /// Note: the spec describes `BuildingHandle` as "opaque". This crate /// implements it as a heap-allocated trait object so that the /// deformation pipeline can call `fits_in` without taking a callback /// from the user. Recorded as revision §11 (R1). pub struct BuildingHandle { inner: Box, } impl BuildingHandle { /// Wrap a building implementation. #[must_use] pub fn new(b: B) -> Self { Self { inner: Box::new(b) } } /// Forward to the inner [`BuildingFitCheck::fits_in`]. #[must_use] pub fn fits_in(&self, parcel: &Parcel) -> bool { self.inner.fits_in(parcel) } } impl fmt::Debug for BuildingHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BuildingHandle").finish_non_exhaustive() } } /// A single parcel. #[derive(Debug)] pub struct Parcel { pub(crate) polygon: Polygon, pub(crate) edge_kinds: Vec, pub(crate) frontage_road: RoadId, pub(crate) frontage_edge_index: usize, pub(crate) block: FaceId, pub(crate) building: Option, } impl Parcel { /// Vertex ring of the parcel polygon (CCW, not closed). #[must_use] pub fn vertices(&self) -> &[DVec2] { self.polygon.vertices() } /// Polygon view. #[must_use] pub fn polygon(&self) -> &Polygon { &self.polygon } /// Edge classification, parallel to `vertices()`. #[must_use] pub fn edge_kinds(&self) -> &[EdgeKind] { &self.edge_kinds } /// The single road this parcel faces (invariant **I2**). #[must_use] pub fn frontage_road(&self) -> RoadId { self.frontage_road } /// Endpoints of the frontage edge. #[must_use] pub fn frontage_edge(&self) -> (DVec2, DVec2) { let v = self.polygon.vertices(); let i = self.frontage_edge_index; (v[i], v[(i + 1) % v.len()]) } /// Frontage length, meters. #[must_use] pub fn frontage_length(&self) -> f64 { let (a, b) = self.frontage_edge(); (b - a).length() } /// Parcel area, m². #[must_use] pub fn area(&self) -> f64 { self.polygon.area() } /// Attach a building. Returns the previously-attached building, if /// any. pub fn attach_building(&mut self, b: BuildingHandle) -> Option { self.building.replace(b) } /// Detach (and return) the currently attached building, if any. pub fn detach_building(&mut self) -> Option { self.building.take() } /// Whether a building is currently attached. #[must_use] pub fn has_building(&self) -> bool { self.building.is_some() } } /// The output of `subdivide_all`: a stable-ID indexed collection of /// parcels, organized by their containing block. #[derive(Debug, Default)] pub struct ParcelSet { pub(crate) parcels: SlotMap, pub(crate) by_block: HashMap>, pub(crate) by_road: HashMap>, } impl ParcelSet { /// Number of parcels. #[must_use] pub fn len(&self) -> usize { self.parcels.len() } /// True if there are no parcels. #[must_use] pub fn is_empty(&self) -> bool { self.parcels.is_empty() } /// Iterate over `(ParcelId, &Parcel)` pairs. pub fn iter(&self) -> impl Iterator { self.parcels.iter() } /// Look up a parcel by id. #[must_use] pub fn get(&self, id: ParcelId) -> Option<&Parcel> { self.parcels.get(id) } /// Look up a parcel mutably by id. pub fn get_mut(&mut self, id: ParcelId) -> Option<&mut Parcel> { self.parcels.get_mut(id) } /// Iterate over the parcel ids within a single road's frontage. pub fn parcels_on_road(&self, road: RoadId) -> impl Iterator + '_ { self.by_road .get(&road) .into_iter() .flat_map(|v| v.iter().copied()) } pub(crate) fn insert(&mut self, parcel: Parcel) -> ParcelId { let block = parcel.block; let road = parcel.frontage_road; let id = self.parcels.insert(parcel); self.by_block.entry(block).or_default().push(id); self.by_road.entry(road).or_default().push(id); id } }