1use crate::limits::ParserLimits;
19use crate::types::{Entry, FeedMeta};
20
21pub const GEORSS: &str = "http://www.georss.org/georss";
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26pub enum GeoType {
27 #[default]
29 Point,
30 Line,
32 Polygon,
34 Box,
36}
37
38#[derive(Debug, Clone, Default, PartialEq)]
40pub struct GeoLocation {
41 pub geo_type: GeoType,
43 pub coordinates: Vec<(f64, f64)>,
50 pub srs_name: Option<String>,
54}
55
56impl GeoLocation {
57 #[must_use]
73 pub fn point(lat: f64, lon: f64) -> Self {
74 Self {
75 geo_type: GeoType::Point,
76 coordinates: vec![(lat, lon)],
77 srs_name: None,
78 }
79 }
80
81 #[must_use]
97 pub const fn line(coords: Vec<(f64, f64)>) -> Self {
98 Self {
99 geo_type: GeoType::Line,
100 coordinates: coords,
101 srs_name: None,
102 }
103 }
104
105 #[must_use]
125 pub const fn polygon(coords: Vec<(f64, f64)>) -> Self {
126 Self {
127 geo_type: GeoType::Polygon,
128 coordinates: coords,
129 srs_name: None,
130 }
131 }
132
133 #[must_use]
151 pub fn bbox(lower_lat: f64, lower_lon: f64, upper_lat: f64, upper_lon: f64) -> Self {
152 Self {
153 geo_type: GeoType::Box,
154 coordinates: vec![(lower_lat, lower_lon), (upper_lat, upper_lon)],
155 srs_name: None,
156 }
157 }
158}
159
160pub fn handle_entry_element(
173 tag: &[u8],
174 text: &str,
175 entry: &mut Entry,
176 _limits: &ParserLimits,
177) -> bool {
178 match tag {
179 b"point" => {
180 if let Some(loc) = parse_point(text) {
181 entry.geo = Some(Box::new(loc));
182 }
183 true
184 }
185 b"line" => {
186 if let Some(loc) = parse_line(text) {
187 entry.geo = Some(Box::new(loc));
188 }
189 true
190 }
191 b"polygon" => {
192 if let Some(loc) = parse_polygon(text) {
193 entry.geo = Some(Box::new(loc));
194 }
195 true
196 }
197 b"box" => {
198 if let Some(loc) = parse_box(text) {
199 entry.geo = Some(Box::new(loc));
200 }
201 true
202 }
203 _ => false,
204 }
205}
206
207pub fn handle_feed_element(
220 tag: &[u8],
221 text: &str,
222 feed: &mut FeedMeta,
223 _limits: &ParserLimits,
224) -> bool {
225 match tag {
226 b"point" => {
227 if let Some(loc) = parse_point(text) {
228 feed.geo = Some(Box::new(loc));
229 }
230 true
231 }
232 b"line" => {
233 if let Some(loc) = parse_line(text) {
234 feed.geo = Some(Box::new(loc));
235 }
236 true
237 }
238 b"polygon" => {
239 if let Some(loc) = parse_polygon(text) {
240 feed.geo = Some(Box::new(loc));
241 }
242 true
243 }
244 b"box" => {
245 if let Some(loc) = parse_box(text) {
246 feed.geo = Some(Box::new(loc));
247 }
248 true
249 }
250 _ => false,
251 }
252}
253
254fn parse_point(text: &str) -> Option<GeoLocation> {
259 let coords = parse_coordinates(text)?;
260 if coords.len() == 1 {
261 Some(GeoLocation {
262 geo_type: GeoType::Point,
263 coordinates: coords,
264 srs_name: None,
265 })
266 } else {
267 None
268 }
269}
270
271fn parse_line(text: &str) -> Option<GeoLocation> {
276 let coords = parse_coordinates(text)?;
277 if coords.len() >= 2 {
278 Some(GeoLocation {
279 geo_type: GeoType::Line,
280 coordinates: coords,
281 srs_name: None,
282 })
283 } else {
284 None
285 }
286}
287
288fn parse_polygon(text: &str) -> Option<GeoLocation> {
293 let coords = parse_coordinates(text)?;
294 if coords.len() >= 3 {
295 Some(GeoLocation {
296 geo_type: GeoType::Polygon,
297 coordinates: coords,
298 srs_name: None,
299 })
300 } else {
301 None
302 }
303}
304
305fn parse_box(text: &str) -> Option<GeoLocation> {
310 let coords = parse_coordinates(text)?;
311 if coords.len() == 2 {
312 Some(GeoLocation {
313 geo_type: GeoType::Box,
314 coordinates: coords,
315 srs_name: None,
316 })
317 } else {
318 None
319 }
320}
321
322fn parse_coordinates(text: &str) -> Option<Vec<(f64, f64)>> {
326 let parts: Vec<&str> = text.split_whitespace().collect();
327
328 if parts.is_empty() || !parts.len().is_multiple_of(2) {
330 return None;
331 }
332
333 let mut coords = Vec::with_capacity(parts.len() / 2);
334
335 for chunk in parts.chunks(2) {
336 let lat = chunk[0].parse::<f64>().ok()?;
337 let lon = chunk[1].parse::<f64>().ok()?;
338
339 if !(-90.0..=90.0).contains(&lat) || !(-180.0..=180.0).contains(&lon) {
341 return None;
342 }
343
344 coords.push((lat, lon));
345 }
346
347 Some(coords)
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[test]
355 fn test_parse_point() {
356 let loc = parse_point("45.256 -71.92").unwrap();
357 assert_eq!(loc.geo_type, GeoType::Point);
358 assert_eq!(loc.coordinates.len(), 1);
359 assert_eq!(loc.coordinates[0], (45.256, -71.92));
360 }
361
362 #[test]
363 fn test_parse_point_invalid() {
364 assert!(parse_point("45.256").is_none());
365 assert!(parse_point("45.256 -71.92 extra").is_none());
366 assert!(parse_point("not numbers").is_none());
367 assert!(parse_point("").is_none());
368 }
369
370 #[test]
371 fn test_parse_line() {
372 let loc = parse_line("45.256 -71.92 46.0 -72.0").unwrap();
373 assert_eq!(loc.geo_type, GeoType::Line);
374 assert_eq!(loc.coordinates.len(), 2);
375 assert_eq!(loc.coordinates[0], (45.256, -71.92));
376 assert_eq!(loc.coordinates[1], (46.0, -72.0));
377 }
378
379 #[test]
380 fn test_parse_line_single_point() {
381 assert!(parse_line("45.256 -71.92").is_none());
383 }
384
385 #[test]
386 fn test_parse_polygon() {
387 let loc = parse_polygon("45.0 -71.0 46.0 -71.0 46.0 -72.0 45.0 -71.0").unwrap();
388 assert_eq!(loc.geo_type, GeoType::Polygon);
389 assert_eq!(loc.coordinates.len(), 4);
390 assert_eq!(loc.coordinates[0], (45.0, -71.0));
391 assert_eq!(loc.coordinates[3], (45.0, -71.0)); }
393
394 #[test]
395 fn test_parse_box() {
396 let loc = parse_box("45.0 -72.0 46.0 -71.0").unwrap();
397 assert_eq!(loc.geo_type, GeoType::Box);
398 assert_eq!(loc.coordinates.len(), 2);
399 assert_eq!(loc.coordinates[0], (45.0, -72.0)); assert_eq!(loc.coordinates[1], (46.0, -71.0)); }
402
403 #[test]
404 fn test_parse_box_invalid() {
405 assert!(parse_box("45.0 -72.0").is_none());
407 assert!(parse_box("45.0 -72.0 46.0 -71.0 extra values").is_none());
408 }
409
410 #[test]
411 fn test_coordinate_validation() {
412 assert!(parse_point("91.0 0.0").is_none());
414 assert!(parse_point("-91.0 0.0").is_none());
416 assert!(parse_point("0.0 181.0").is_none());
418 assert!(parse_point("0.0 -181.0").is_none());
420 }
421
422 #[test]
423 fn test_handle_entry_element_point() {
424 let mut entry = Entry::default();
425 let limits = ParserLimits::default();
426
427 let handled = handle_entry_element(b"point", "45.256 -71.92", &mut entry, &limits);
428 assert!(handled);
429 assert!(entry.geo.is_some());
430
431 let geo = entry.geo.as_ref().unwrap();
432 assert_eq!(geo.geo_type, GeoType::Point);
433 assert_eq!(geo.coordinates[0], (45.256, -71.92));
434 }
435
436 #[test]
437 fn test_handle_entry_element_line() {
438 let mut entry = Entry::default();
439 let limits = ParserLimits::default();
440
441 let handled =
442 handle_entry_element(b"line", "45.256 -71.92 46.0 -72.0", &mut entry, &limits);
443 assert!(handled);
444 assert!(entry.geo.is_some());
445 assert_eq!(entry.geo.as_ref().unwrap().geo_type, GeoType::Line);
446 }
447
448 #[test]
449 fn test_handle_entry_element_unknown() {
450 let mut entry = Entry::default();
451 let limits = ParserLimits::default();
452
453 let handled = handle_entry_element(b"unknown", "data", &mut entry, &limits);
454 assert!(!handled);
455 assert!(entry.geo.is_none());
456 }
457
458 #[test]
459 fn test_geo_location_constructors() {
460 let point = GeoLocation::point(45.0, -71.0);
461 assert_eq!(point.geo_type, GeoType::Point);
462 assert_eq!(point.coordinates.len(), 1);
463
464 let line = GeoLocation::line(vec![(45.0, -71.0), (46.0, -72.0)]);
465 assert_eq!(line.geo_type, GeoType::Line);
466 assert_eq!(line.coordinates.len(), 2);
467
468 let polygon = GeoLocation::polygon(vec![(45.0, -71.0), (46.0, -71.0), (45.0, -71.0)]);
469 assert_eq!(polygon.geo_type, GeoType::Polygon);
470 assert_eq!(polygon.coordinates.len(), 3);
471
472 let bbox = GeoLocation::bbox(45.0, -72.0, 46.0, -71.0);
473 assert_eq!(bbox.geo_type, GeoType::Box);
474 assert_eq!(bbox.coordinates.len(), 2);
475 }
476
477 #[test]
478 fn test_whitespace_handling() {
479 let loc = parse_point(" 45.256 -71.92 ").unwrap();
480 assert_eq!(loc.coordinates[0], (45.256, -71.92));
481 }
482
483 #[test]
484 fn test_handle_feed_element_point() {
485 let mut feed = FeedMeta::default();
486 let limits = ParserLimits::default();
487
488 let handled = handle_feed_element(b"point", "45.256 -71.92", &mut feed, &limits);
489 assert!(handled);
490 assert!(feed.geo.is_some());
491
492 let geo = feed.geo.as_ref().unwrap();
493 assert_eq!(geo.geo_type, GeoType::Point);
494 assert_eq!(geo.coordinates[0], (45.256, -71.92));
495 }
496
497 #[test]
498 fn test_handle_feed_element_line() {
499 let mut feed = FeedMeta::default();
500 let limits = ParserLimits::default();
501
502 let handled = handle_feed_element(b"line", "45.256 -71.92 46.0 -72.0", &mut feed, &limits);
503 assert!(handled);
504 assert!(feed.geo.is_some());
505 assert_eq!(feed.geo.as_ref().unwrap().geo_type, GeoType::Line);
506 }
507
508 #[test]
509 fn test_handle_feed_element_polygon() {
510 let mut feed = FeedMeta::default();
511 let limits = ParserLimits::default();
512
513 let handled = handle_feed_element(
514 b"polygon",
515 "45.0 -71.0 46.0 -71.0 46.0 -72.0 45.0 -71.0",
516 &mut feed,
517 &limits,
518 );
519 assert!(handled);
520 assert!(feed.geo.is_some());
521 assert_eq!(feed.geo.as_ref().unwrap().geo_type, GeoType::Polygon);
522 }
523
524 #[test]
525 fn test_handle_feed_element_box() {
526 let mut feed = FeedMeta::default();
527 let limits = ParserLimits::default();
528
529 let handled = handle_feed_element(b"box", "45.0 -72.0 46.0 -71.0", &mut feed, &limits);
530 assert!(handled);
531 assert!(feed.geo.is_some());
532 assert_eq!(feed.geo.as_ref().unwrap().geo_type, GeoType::Box);
533 }
534
535 #[test]
536 fn test_handle_feed_element_unknown() {
537 let mut feed = FeedMeta::default();
538 let limits = ParserLimits::default();
539
540 let handled = handle_feed_element(b"unknown", "data", &mut feed, &limits);
541 assert!(!handled);
542 assert!(feed.geo.is_none());
543 }
544
545 #[test]
546 fn test_handle_feed_element_invalid_data() {
547 let mut feed = FeedMeta::default();
548 let limits = ParserLimits::default();
549
550 let handled = handle_feed_element(b"point", "invalid data", &mut feed, &limits);
551 assert!(handled);
552 assert!(feed.geo.is_none());
553 }
554}