Skip to main content

topology_checker_core/
violation.rs

1use crate::{FeatureRef, Rule};
2use geo::Geometry;
3use serde::{Deserialize, Serialize};
4
5/// Categories of reported topology violations, ordered deterministically.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
7#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
8#[serde(rename_all = "kebab-case")]
9#[non_exhaustive]
10pub enum ViolationKind {
11    /// Coincident points, shared linework or polygon intersection area.
12    Overlap,
13    /// Geometry outside the required polygon coverage.
14    Outside,
15    /// A node with one incident endpoint use.
16    Dangle,
17    /// A node joining endpoints of exactly two distinct features.
18    PseudoNode,
19    /// An interior intersection between distinct features.
20    Crossing,
21    /// A meeting with at least one feature interior.
22    TouchInterior,
23    /// Repeated linework within one feature.
24    SelfOverlap,
25    /// A true self-crossing within one feature.
26    SelfCrossing,
27    /// An interior ring of the polygon union.
28    Gap,
29    /// An exterior ring of the polygon union, reported by the gaps rule.
30    OuterBoundary,
31    /// A feature containing multiple parts.
32    Multipart,
33    /// A point, endpoint or line piece without required coverage.
34    NotCovered,
35    /// A polygon feature without a qualifying point feature.
36    MissingPoint,
37    /// A polygon or point feature failing a one-to-one assignment.
38    PointCount,
39    /// A strictly contained point or whole feature.
40    Contained,
41    /// Any prohibited contact, including isolated points.
42    Intersection,
43}
44
45impl ViolationKind {
46    /// Return the stable kebab-case output name.
47    #[must_use]
48    pub fn name(self) -> &'static str {
49        match self {
50            Self::Overlap => "overlap",
51            Self::Outside => "outside",
52            Self::Dangle => "dangle",
53            Self::PseudoNode => "pseudo-node",
54            Self::Crossing => "crossing",
55            Self::TouchInterior => "touch-interior",
56            Self::SelfOverlap => "self-overlap",
57            Self::SelfCrossing => "self-crossing",
58            Self::Gap => "gap",
59            Self::OuterBoundary => "outer-boundary",
60            Self::Multipart => "multipart",
61            Self::NotCovered => "not-covered",
62            Self::MissingPoint => "missing-point",
63            Self::PointCount => "point-count",
64            Self::Contained => "contained",
65            Self::Intersection => "intersection",
66        }
67    }
68}
69
70/// A topology violation, including provenance. `part: None` means multiple parts contributed.
71#[derive(Debug, Clone, PartialEq, Serialize)]
72#[non_exhaustive]
73pub struct Violation {
74    /// Rule that produced the violation.
75    pub rule: Rule,
76    /// Violation category, serialized as `error_type`.
77    #[serde(rename = "error_type")]
78    pub kind: ViolationKind,
79    /// Violation geometry in planar input coordinate units.
80    /// Aggregation may produce multi geometries and deduplicates coincident points.
81    pub geometry: Geometry<f64>,
82    /// Primary feature provenance, absent for layer-wide results such as gaps.
83    pub origin: Option<FeatureRef>,
84    /// Secondary feature provenance for pair violations, when applicable.
85    pub destination: Option<FeatureRef>,
86}