1use anyhow::{Result, bail};
10use tracing::debug;
11
12pub struct PolygonHandler;
17
18impl PolygonHandler {
19 pub fn validate_and_canonicalize(value: &str, field_name: &str) -> Result<String> {
20 debug!(
21 "Validating polygon field '{}' with value: {}",
22 field_name, value
23 );
24
25 let coordinates = Self::parse_polygon_coordinates(value)?;
27 debug!(
28 "Parsed {} coordinate pairs for field '{}'",
29 coordinates.len(),
30 field_name
31 );
32
33 Self::validate_polygon_geometry(&coordinates)?;
35 debug!(
36 "Polygon geometry validation passed for field '{}'",
37 field_name
38 );
39
40 Ok(value.to_string())
43 }
44
45 pub fn parse_polygon_coordinates(coord_string: &str) -> Result<Vec<(f64, f64)>> {
50 let trimmed = coord_string
51 .trim()
52 .trim_start_matches('(')
53 .trim_end_matches(')')
54 .trim();
55
56 if trimmed.is_empty() {
57 bail!("Empty polygon coordinate string");
58 }
59
60 let coord_parts: Vec<&str> = trimmed.split(',').collect();
61
62 if !coord_parts.len().is_multiple_of(2) {
63 bail!("Polygon coordinates must be in pairs (lat,lon)");
64 }
65
66 let mut coordinates = Vec::new();
67 let mut iter = coord_parts.iter();
68
69 while let Some(lat_str) = iter.next() {
70 let lon_str = iter.next().unwrap(); let lat: f64 = lat_str
73 .trim()
74 .parse()
75 .map_err(|_| anyhow::anyhow!("Invalid latitude value: {}", lat_str))?;
76
77 let lon: f64 = lon_str
78 .trim()
79 .parse()
80 .map_err(|_| anyhow::anyhow!("Invalid longitude value: {}", lon_str))?;
81
82 coordinates.push((lat, lon));
83 }
84
85 Ok(coordinates)
86 }
87
88 fn validate_polygon_geometry(coordinates: &[(f64, f64)]) -> Result<()> {
90 if coordinates.len() < 3 {
91 bail!("Polygon must have at least 3 coordinate pairs");
92 }
93
94 let first = coordinates.first().unwrap();
96 let last = coordinates.last().unwrap();
97
98 if first != last {
99 bail!("Polygon must be closed (first and last coordinates must be identical)");
100 }
101
102 Ok(())
103 }
104
105 pub fn calculate_bounding_box(coordinates: &[(f64, f64)]) -> String {
108 let mut min_lat = f64::INFINITY;
109 let mut min_lon = f64::INFINITY;
110 let mut max_lat = f64::NEG_INFINITY;
111 let mut max_lon = f64::NEG_INFINITY;
112
113 for &(lat, lon) in coordinates {
114 min_lat = min_lat.min(lat);
115 min_lon = min_lon.min(lon);
116 max_lat = max_lat.max(lat);
117 max_lon = max_lon.max(lon);
118 }
119
120 format!("{},{},{},{}", min_lat, min_lon, max_lat, max_lon)
121 }
122
123 pub fn parse_bbox_coordinates(s: &str) -> Result<(f64, f64, f64, f64)> {
124 let s = s.trim_matches(|c| c == '(' || c == ')');
126 let coords: Vec<f64> = s
127 .split(',')
128 .map(|part| part.trim().parse())
129 .collect::<Result<_, _>>()?;
130 if coords.len() != 4 {
131 anyhow::bail!("BBox must have 4 numbers");
132 }
133 Ok((coords[0], coords[1], coords[2], coords[3]))
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_validate_and_canonicalize_valid_polygon() {
143 let polygon_str = "(52.5,13.4,52.6,13.5,52.5,13.6,52.4,13.5,52.5,13.4)";
144 let result = PolygonHandler::validate_and_canonicalize(polygon_str, "polygon");
145
146 assert!(result.is_ok());
147 assert_eq!(result.unwrap(), polygon_str);
148 }
149
150 #[test]
151 fn test_validate_and_canonicalize_with_spaces() {
152 let polygon_str = "( 52.5 , 13.4 , 52.6 , 13.5 , 52.5 , 13.6 , 52.4 , 13.5 , 52.5 , 13.4 )";
153 let result = PolygonHandler::validate_and_canonicalize(polygon_str, "polygon");
154
155 assert!(result.is_ok());
156 assert_eq!(result.unwrap(), polygon_str);
157 }
158
159 #[test]
160 fn test_validate_and_canonicalize_not_closed() {
161 let polygon_str = "(52.5,13.4,52.6,13.5,52.5,13.6,52.4,13.5)"; let result = PolygonHandler::validate_and_canonicalize(polygon_str, "polygon");
163
164 assert!(result.is_err());
165 }
166
167 #[test]
168 fn test_validate_and_canonicalize_too_few_points() {
169 let polygon_str = "(52.5,13.4,52.6,13.5)"; let result = PolygonHandler::validate_and_canonicalize(polygon_str, "polygon");
171
172 assert!(result.is_err());
173 }
174
175 #[test]
176 fn test_validate_and_canonicalize_empty_string() {
177 let polygon_str = "";
178 let result = PolygonHandler::validate_and_canonicalize(polygon_str, "polygon");
179
180 assert!(result.is_err());
181 }
182
183 #[test]
184 fn test_validate_and_canonicalize_empty_parentheses() {
185 let polygon_str = "()";
186 let result = PolygonHandler::validate_and_canonicalize(polygon_str, "polygon");
187
188 assert!(result.is_err());
189 }
190
191 #[test]
192 fn test_parse_polygon_coordinates_valid() {
193 let coord_string = "(52.5,13.4,52.6,13.5,52.5,13.6,52.4,13.5,52.5,13.4)";
194 let result = PolygonHandler::parse_polygon_coordinates(coord_string);
195
196 assert!(result.is_ok());
197 let coordinates = result.unwrap();
198 assert_eq!(coordinates.len(), 5);
199 assert_eq!(coordinates[0], (52.5, 13.4));
200 assert_eq!(coordinates[1], (52.6, 13.5));
201 assert_eq!(coordinates[4], (52.5, 13.4)); }
203
204 #[test]
205 fn test_parse_polygon_coordinates_without_parentheses() {
206 let coord_string = "52.5,13.4,52.6,13.5,52.5,13.6,52.4,13.5,52.5,13.4";
207 let result = PolygonHandler::parse_polygon_coordinates(coord_string);
208
209 assert!(result.is_ok());
210 let coordinates = result.unwrap();
211 assert_eq!(coordinates.len(), 5);
212 assert_eq!(coordinates[0], (52.5, 13.4));
213 }
214
215 #[test]
216 fn test_parse_polygon_coordinates_with_spaces() {
217 let coord_string = "( 52.5 , 13.4 , 52.6 , 13.5 , 52.5 , 13.4 )";
218 let result = PolygonHandler::parse_polygon_coordinates(coord_string);
219
220 assert!(result.is_ok());
221 let coordinates = result.unwrap();
222 assert_eq!(coordinates.len(), 3);
223 assert_eq!(coordinates[0], (52.5, 13.4));
224 assert_eq!(coordinates[1], (52.6, 13.5));
225 assert_eq!(coordinates[2], (52.5, 13.4));
226 }
227
228 #[test]
229 fn test_parse_polygon_coordinates_odd_number() {
230 let coord_string = "(52.5,13.4,52.6)"; let result = PolygonHandler::parse_polygon_coordinates(coord_string);
232
233 assert!(result.is_err());
234 }
235
236 #[test]
237 fn test_parse_polygon_coordinates_invalid_latitude() {
238 let coord_string = "(invalid,13.4,52.6,13.5,52.5,13.4)";
239 let result = PolygonHandler::parse_polygon_coordinates(coord_string);
240
241 assert!(result.is_err());
242 }
243
244 #[test]
245 fn test_parse_polygon_coordinates_invalid_longitude() {
246 let coord_string = "(52.5,invalid,52.6,13.5,52.5,13.4)";
247 let result = PolygonHandler::parse_polygon_coordinates(coord_string);
248
249 assert!(result.is_err());
250 }
251
252 #[test]
253 fn test_parse_polygon_coordinates_empty() {
254 let coord_string = "()";
255 let result = PolygonHandler::parse_polygon_coordinates(coord_string);
256
257 assert!(result.is_err());
258 }
259
260 #[test]
261 fn test_validate_polygon_geometry_valid_triangle() {
262 let coordinates = vec![(0.0, 0.0), (1.0, 0.0), (0.5, 1.0), (0.0, 0.0)];
263 let result = PolygonHandler::validate_polygon_geometry(&coordinates);
264
265 assert!(result.is_ok());
266 }
267
268 #[test]
269 fn test_validate_polygon_geometry_valid_rectangle() {
270 let coordinates = vec![
271 (52.5, 13.4),
272 (52.6, 13.4),
273 (52.6, 13.5),
274 (52.5, 13.5),
275 (52.5, 13.4),
276 ];
277 let result = PolygonHandler::validate_polygon_geometry(&coordinates);
278
279 assert!(result.is_ok());
280 }
281
282 #[test]
283 fn test_validate_polygon_geometry_too_few_points() {
284 let coordinates = vec![(0.0, 0.0), (1.0, 0.0)]; let result = PolygonHandler::validate_polygon_geometry(&coordinates);
286
287 assert!(result.is_err());
288 }
289
290 #[test]
291 fn test_validate_polygon_geometry_not_closed() {
292 let coordinates = vec![(0.0, 0.0), (1.0, 0.0), (0.5, 1.0), (0.1, 0.1)]; let result = PolygonHandler::validate_polygon_geometry(&coordinates);
294
295 assert!(result.is_err());
296 }
297
298 #[test]
299 fn test_validate_polygon_geometry_minimum_valid() {
300 let coordinates = vec![(0.0, 0.0), (1.0, 0.0), (0.5, 1.0), (0.0, 0.0)]; let result = PolygonHandler::validate_polygon_geometry(&coordinates);
302
303 assert!(result.is_ok());
304 }
305
306 #[test]
307 fn test_calculate_bounding_box_rectangle() {
308 let coordinates = vec![
309 (52.5, 13.4),
310 (52.6, 13.4),
311 (52.6, 13.5),
312 (52.5, 13.5),
313 (52.5, 13.4),
314 ];
315 let bbox = PolygonHandler::calculate_bounding_box(&coordinates);
316
317 assert_eq!(bbox, "52.5,13.4,52.6,13.5");
318 }
319
320 #[test]
321 fn test_calculate_bounding_box_triangle() {
322 let coordinates = vec![(0.0, 0.0), (1.0, 0.0), (0.5, 1.0), (0.0, 0.0)];
323 let bbox = PolygonHandler::calculate_bounding_box(&coordinates);
324
325 assert_eq!(bbox, "0,0,1,1");
326 }
327
328 #[test]
329 fn test_calculate_bounding_box_single_point() {
330 let coordinates = vec![(52.5, 13.4), (52.5, 13.4), (52.5, 13.4), (52.5, 13.4)];
331 let bbox = PolygonHandler::calculate_bounding_box(&coordinates);
332
333 assert_eq!(bbox, "52.5,13.4,52.5,13.4");
334 }
335
336 #[test]
337 fn test_calculate_bounding_box_negative_coordinates() {
338 let coordinates = vec![
339 (-1.0, -1.0),
340 (1.0, -1.0),
341 (1.0, 1.0),
342 (-1.0, 1.0),
343 (-1.0, -1.0),
344 ];
345 let bbox = PolygonHandler::calculate_bounding_box(&coordinates);
346
347 assert_eq!(bbox, "-1,-1,1,1");
348 }
349
350 #[test]
351 fn test_integration_parse_and_validate() {
352 let polygon_str = "(52.5,13.4,52.6,13.5,52.5,13.6,52.4,13.5,52.5,13.4)";
353
354 let coordinates = PolygonHandler::parse_polygon_coordinates(polygon_str).unwrap();
356 let validation_result = PolygonHandler::validate_polygon_geometry(&coordinates);
357 assert!(validation_result.is_ok());
358
359 let bbox = PolygonHandler::calculate_bounding_box(&coordinates);
360 assert_eq!(bbox, "52.4,13.4,52.6,13.6");
361 }
362
363 #[test]
364 fn test_real_world_berlin_polygon() {
365 let polygon_str =
367 "(52.5200,13.4050,52.5200,13.4500,52.4800,13.4500,52.4800,13.4050,52.5200,13.4050)";
368 let result = PolygonHandler::validate_and_canonicalize(polygon_str, "berlin_area");
369
370 assert!(result.is_ok());
371
372 let coordinates = PolygonHandler::parse_polygon_coordinates(polygon_str).unwrap();
373 let bbox = PolygonHandler::calculate_bounding_box(&coordinates);
374 assert_eq!(bbox, "52.48,13.405,52.52,13.45");
375 }
376
377 #[test]
378 fn test_precision_handling() {
379 let polygon_str = "(52.123456789,13.987654321,52.234567890,13.876543210,52.345678901,13.765432109,52.123456789,13.987654321)";
381 let result = PolygonHandler::validate_and_canonicalize(polygon_str, "precision_test");
382
383 assert!(result.is_ok());
384
385 let coordinates = PolygonHandler::parse_polygon_coordinates(polygon_str).unwrap();
386 assert_eq!(coordinates[0].0, 52.123456789);
387 assert_eq!(coordinates[0].1, 13.987654321);
388 }
389}