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}