egml_core/error.rs
1use crate::model::base::Id;
2use crate::model::geometry::DirectPosition;
3use std::fmt;
4
5/// Errors returned by `egml-core` operations.
6#[derive(Debug, PartialEq, Clone)]
7#[non_exhaustive]
8pub enum Error {
9 /// Returned when a floating-point coordinate is not finite (NaN or ±infinity).
10 ///
11 /// `axis` names the offending component (`"x"`, `"y"`, or `"z"`); `value` is
12 /// the actual non-finite number that was supplied.
13 NonFiniteCoordinate { axis: &'static str, value: f64 },
14
15 /// Returned when a collection has fewer elements than the minimum required by
16 /// the GML geometry constraint.
17 ///
18 /// Optional fields carry progressively more context: `spec` cites the ISO
19 /// clause, `id` names the offending GML object, and `detail` gives a
20 /// human-readable description of the actual content that was supplied.
21 TooFewElements {
22 geometry: &'static str,
23 minimum: usize,
24 spec: Option<&'static str>,
25 id: Option<Id>,
26 detail: Option<String>,
27 },
28
29 /// Returned when two positions that must be distinct are identical.
30 ///
31 /// Applies to [`Triangle`](crate::model::geometry::primitives::Triangle)
32 /// vertices ([OGC 07-036 §10.5.12.5](https://docs.ogc.org/is/07-036/07-036.pdf)).
33 IdenticalPositions {
34 first: DirectPosition,
35 second: DirectPosition,
36 },
37
38 /// Returned when adjacent positions in a sequence are equal.
39 ///
40 /// `index` is the zero-based index of the first position in the duplicate
41 /// pair; `position` is its value. Applies to
42 /// [`LinearRing`](crate::model::geometry::primitives::LinearRing)
43 /// ([OGC 07-036 §10.5.8](https://docs.ogc.org/is/07-036/07-036.pdf)) and
44 /// [`LineString`](crate::model::geometry::primitives::LineString)
45 /// ([OGC 07-036 §10.4.4](https://docs.ogc.org/is/07-036/07-036.pdf)).
46 AdjacentDuplicatePositions {
47 index: usize,
48 position: DirectPosition,
49 },
50
51 /// Returned when the first and last position of a ring are equal.
52 ///
53 /// `position` is the repeated vertex. A `gml:LinearRing` is implicitly
54 /// closed and must not include an explicit closing vertex ([OGC 07-036 §10.5.8](https://docs.ogc.org/is/07-036/07-036.pdf)).
55 RepeatedClosingVertex { position: DirectPosition },
56
57 /// Returned when an [`Envelope`](crate::model::geometry::Envelope) is constructed
58 /// with a lower corner that strictly exceeds the upper corner along `axis`.
59 ///
60 /// `lower` and `upper` are the actual conflicting coordinate values.
61 /// [OGC 07-036 §10.1.4.6](https://docs.ogc.org/is/07-036/07-036.pdf) requires `lowerCorner ≤ upperCorner` in every component.
62 InvalidEnvelopeBounds {
63 axis: &'static str,
64 lower: f64,
65 upper: f64,
66 },
67
68 /// Returned when
69 /// [`Envelope::to_triangulated_surface`](crate::model::geometry::Envelope::to_triangulated_surface)
70 /// is called on an envelope that is neither a surface nor a volume.
71 ///
72 /// `non_zero_extents` is the number of axes with non-zero extent (0 or 1).
73 NotSurfaceOrVolume { non_zero_extents: u8 },
74
75 /// Returned when [`Envelope::to_polygon`](crate::model::geometry::Envelope::to_polygon)
76 /// is called on an envelope that does not have exactly two non-zero extents.
77 ///
78 /// `non_zero_extents` is the actual number of axes with non-zero extent.
79 NotASurface { non_zero_extents: u8 },
80
81 /// Returned when [`Envelope::to_solid`](crate::model::geometry::Envelope::to_solid)
82 /// is called on an envelope that does not have all three extents non-zero.
83 ///
84 /// `non_zero_extents` is the actual number of axes with non-zero extent.
85 NotAVolume { non_zero_extents: u8 },
86
87 /// Returned when the earcut polygon triangulation algorithm produces no triangles.
88 ///
89 /// `context` provides additional information about which polygon or patch
90 /// could not be decomposed.
91 TriangulationFailed { context: String },
92
93 /// Returned when an operation requires an exterior ring but the polygon has none.
94 MissingExteriorRing,
95
96 /// Returned when a ring property carries only an xlink:href reference and the
97 /// referenced geometry object has not been resolved into an inline object.
98 ///
99 /// `href` is the reference value if one was present, or `None` if the property
100 /// has neither an inline object nor a reference.
101 UnresolvedRingReference { href: Option<String> },
102
103 /// Returned when a surface property carries only an xlink:href reference and the
104 /// referenced geometry object has not been resolved into an inline object.
105 ///
106 /// `href` is the reference value if one was present, or `None` if the property
107 /// has neither an inline object nor a reference.
108 UnresolvedSurfaceReference { href: Option<String> },
109
110 /// Returned when a curve property carries only an xlink:href reference and the
111 /// referenced geometry object has not been resolved into an inline object.
112 ///
113 /// `href` is the reference value if one was present, or `None` if the property
114 /// has neither an inline object nor a reference.
115 UnresolvedCurveReference { href: Option<String> },
116
117 /// Returned when an operation requires an exterior shell but the solid has none.
118 MissingExteriorShell,
119
120 /// Returned when a shell property carries only an xlink:href reference and the
121 /// referenced geometry object has not been resolved into an inline object.
122 ///
123 /// `href` is the reference value if one was present, or `None` if the property
124 /// has neither an inline object nor a reference.
125 UnresolvedShellReference { href: Option<String> },
126}
127
128impl fmt::Display for Error {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 match self {
131 Error::NonFiniteCoordinate { axis, value } => write!(
132 f,
133 "coordinate '{axis}' has non-finite value {value}; \
134 all GML coordinate values must be real numbers (OGC 07-036 §10.1.4.1)"
135 ),
136 Error::TooFewElements {
137 geometry,
138 minimum,
139 spec,
140 id,
141 detail,
142 } => {
143 write!(f, "{geometry} requires at least {minimum} element(s)")?;
144 if let Some(s) = spec {
145 write!(f, " ({s})")?;
146 }
147 if let Some(i) = id {
148 write!(f, " [id={i}]")?;
149 }
150 if let Some(d) = detail {
151 write!(f, ": {d}")?;
152 }
153 Ok(())
154 }
155 Error::IdenticalPositions { first, second } => write!(
156 f,
157 "geometry contains two identical positions {first} and {second}; \
158 all positions must be distinct (OGC 07-036 §10.5.12.5)"
159 ),
160 Error::AdjacentDuplicatePositions { index, position } => write!(
161 f,
162 "adjacent duplicate positions at index {index} ({position}); \
163 consecutive coordinates must be distinct"
164 ),
165 Error::RepeatedClosingVertex { position } => write!(
166 f,
167 "linear ring has repeated closing vertex at {position}; \
168 gml:LinearRing is implicitly closed and must not include \
169 an explicit closing vertex (OGC 07-036 §10.5.8)"
170 ),
171 Error::InvalidEnvelopeBounds { axis, lower, upper } => write!(
172 f,
173 "envelope lower.{axis}={lower} exceeds upper.{axis}={upper}; \
174 lowerCorner must be ≤ upperCorner in every component (OGC 07-036 §10.1.4.6)"
175 ),
176 Error::NotSurfaceOrVolume { non_zero_extents } => write!(
177 f,
178 "envelope has {non_zero_extents} non-zero extent(s) and cannot be triangulated; \
179 requires a surface (2 non-zero extents) or volume (3 non-zero extents)"
180 ),
181 Error::NotASurface { non_zero_extents } => write!(
182 f,
183 "envelope has {non_zero_extents} non-zero extent(s); \
184 to_polygon requires exactly 2"
185 ),
186 Error::NotAVolume { non_zero_extents } => write!(
187 f,
188 "envelope has {non_zero_extents} non-zero extent(s); \
189 to_solid requires all 3 to be non-zero"
190 ),
191 Error::TriangulationFailed { context } => {
192 write!(f, "polygon triangulation (earcut) failed: {context}")
193 }
194 Error::MissingExteriorRing => write!(
195 f,
196 "polygon has no exterior ring; \
197 operation requires a defined outer boundary (OGC 07-036 §10.5.6)"
198 ),
199 Error::UnresolvedRingReference { href: Some(href) } => write!(
200 f,
201 "ring property references '{href}' via xlink:href but the object has not been resolved"
202 ),
203 Error::UnresolvedRingReference { href: None } => write!(
204 f,
205 "ring property has neither an inline object nor an xlink:href reference"
206 ),
207 Error::UnresolvedSurfaceReference { href: Some(href) } => write!(
208 f,
209 "surface property references '{href}' via xlink:href but the object has not been resolved"
210 ),
211 Error::UnresolvedSurfaceReference { href: None } => write!(
212 f,
213 "surface property has neither an inline object nor an xlink:href reference"
214 ),
215 Error::UnresolvedCurveReference { href: Some(href) } => write!(
216 f,
217 "curve property references '{href}' via xlink:href but the object has not been resolved"
218 ),
219 Error::UnresolvedCurveReference { href: None } => write!(
220 f,
221 "curve property has neither an inline object nor an xlink:href reference"
222 ),
223 Error::MissingExteriorShell => write!(
224 f,
225 "solid has no exterior shell; \
226 operation requires a defined outer boundary (OGC 07-036 §10.6.4)"
227 ),
228 Error::UnresolvedShellReference { href: Some(href) } => write!(
229 f,
230 "shell property references '{href}' via xlink:href but the object has not been resolved"
231 ),
232 Error::UnresolvedShellReference { href: None } => write!(
233 f,
234 "shell property has neither an inline object nor an xlink:href reference"
235 ),
236 }
237 }
238}
239
240impl std::error::Error for Error {}