1use crate::limits::ParserLimits;
19use crate::types::Entry;
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(loc);
182 }
183 true
184 }
185 b"line" => {
186 if let Some(loc) = parse_line(text) {
187 entry.geo = Some(loc);
188 }
189 true
190 }
191 b"polygon" => {
192 if let Some(loc) = parse_polygon(text) {
193 entry.geo = Some(loc);
194 }
195 true
196 }
197 b"box" => {
198 if let Some(loc) = parse_box(text) {
199 entry.geo = Some(loc);
200 }
201 true
202 }
203 _ => false,
204 }
205}
206
207fn parse_point(text: &str) -> Option<GeoLocation> {
212 let coords = parse_coordinates(text)?;
213 if coords.len() == 1 {
214 Some(GeoLocation {
215 geo_type: GeoType::Point,
216 coordinates: coords,
217 srs_name: None,
218 })
219 } else {
220 None
221 }
222}
223
224fn parse_line(text: &str) -> Option<GeoLocation> {
229 let coords = parse_coordinates(text)?;
230 if coords.len() >= 2 {
231 Some(GeoLocation {
232 geo_type: GeoType::Line,
233 coordinates: coords,
234 srs_name: None,
235 })
236 } else {
237 None
238 }
239}
240
241fn parse_polygon(text: &str) -> Option<GeoLocation> {
246 let coords = parse_coordinates(text)?;
247 if coords.len() >= 3 {
248 Some(GeoLocation {
249 geo_type: GeoType::Polygon,
250 coordinates: coords,
251 srs_name: None,
252 })
253 } else {
254 None
255 }
256}
257
258fn parse_box(text: &str) -> Option<GeoLocation> {
263 let coords = parse_coordinates(text)?;
264 if coords.len() == 2 {
265 Some(GeoLocation {
266 geo_type: GeoType::Box,
267 coordinates: coords,
268 srs_name: None,
269 })
270 } else {
271 None
272 }
273}
274
275fn parse_coordinates(text: &str) -> Option<Vec<(f64, f64)>> {
279 let parts: Vec<&str> = text.split_whitespace().collect();
280
281 if parts.is_empty() || !parts.len().is_multiple_of(2) {
283 return None;
284 }
285
286 let mut coords = Vec::with_capacity(parts.len() / 2);
287
288 for chunk in parts.chunks(2) {
289 let lat = chunk[0].parse::<f64>().ok()?;
290 let lon = chunk[1].parse::<f64>().ok()?;
291
292 if !(-90.0..=90.0).contains(&lat) || !(-180.0..=180.0).contains(&lon) {
294 return None;
295 }
296
297 coords.push((lat, lon));
298 }
299
300 Some(coords)
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn test_parse_point() {
309 let loc = parse_point("45.256 -71.92").unwrap();
310 assert_eq!(loc.geo_type, GeoType::Point);
311 assert_eq!(loc.coordinates.len(), 1);
312 assert_eq!(loc.coordinates[0], (45.256, -71.92));
313 }
314
315 #[test]
316 fn test_parse_point_invalid() {
317 assert!(parse_point("45.256").is_none());
318 assert!(parse_point("45.256 -71.92 extra").is_none());
319 assert!(parse_point("not numbers").is_none());
320 assert!(parse_point("").is_none());
321 }
322
323 #[test]
324 fn test_parse_line() {
325 let loc = parse_line("45.256 -71.92 46.0 -72.0").unwrap();
326 assert_eq!(loc.geo_type, GeoType::Line);
327 assert_eq!(loc.coordinates.len(), 2);
328 assert_eq!(loc.coordinates[0], (45.256, -71.92));
329 assert_eq!(loc.coordinates[1], (46.0, -72.0));
330 }
331
332 #[test]
333 fn test_parse_line_single_point() {
334 assert!(parse_line("45.256 -71.92").is_none());
336 }
337
338 #[test]
339 fn test_parse_polygon() {
340 let loc = parse_polygon("45.0 -71.0 46.0 -71.0 46.0 -72.0 45.0 -71.0").unwrap();
341 assert_eq!(loc.geo_type, GeoType::Polygon);
342 assert_eq!(loc.coordinates.len(), 4);
343 assert_eq!(loc.coordinates[0], (45.0, -71.0));
344 assert_eq!(loc.coordinates[3], (45.0, -71.0)); }
346
347 #[test]
348 fn test_parse_box() {
349 let loc = parse_box("45.0 -72.0 46.0 -71.0").unwrap();
350 assert_eq!(loc.geo_type, GeoType::Box);
351 assert_eq!(loc.coordinates.len(), 2);
352 assert_eq!(loc.coordinates[0], (45.0, -72.0)); assert_eq!(loc.coordinates[1], (46.0, -71.0)); }
355
356 #[test]
357 fn test_parse_box_invalid() {
358 assert!(parse_box("45.0 -72.0").is_none());
360 assert!(parse_box("45.0 -72.0 46.0 -71.0 extra values").is_none());
361 }
362
363 #[test]
364 fn test_coordinate_validation() {
365 assert!(parse_point("91.0 0.0").is_none());
367 assert!(parse_point("-91.0 0.0").is_none());
369 assert!(parse_point("0.0 181.0").is_none());
371 assert!(parse_point("0.0 -181.0").is_none());
373 }
374
375 #[test]
376 fn test_handle_entry_element_point() {
377 let mut entry = Entry::default();
378 let limits = ParserLimits::default();
379
380 let handled = handle_entry_element(b"point", "45.256 -71.92", &mut entry, &limits);
381 assert!(handled);
382 assert!(entry.geo.is_some());
383
384 let geo = entry.geo.as_ref().unwrap();
385 assert_eq!(geo.geo_type, GeoType::Point);
386 assert_eq!(geo.coordinates[0], (45.256, -71.92));
387 }
388
389 #[test]
390 fn test_handle_entry_element_line() {
391 let mut entry = Entry::default();
392 let limits = ParserLimits::default();
393
394 let handled =
395 handle_entry_element(b"line", "45.256 -71.92 46.0 -72.0", &mut entry, &limits);
396 assert!(handled);
397 assert!(entry.geo.is_some());
398 assert_eq!(entry.geo.as_ref().unwrap().geo_type, GeoType::Line);
399 }
400
401 #[test]
402 fn test_handle_entry_element_unknown() {
403 let mut entry = Entry::default();
404 let limits = ParserLimits::default();
405
406 let handled = handle_entry_element(b"unknown", "data", &mut entry, &limits);
407 assert!(!handled);
408 assert!(entry.geo.is_none());
409 }
410
411 #[test]
412 fn test_geo_location_constructors() {
413 let point = GeoLocation::point(45.0, -71.0);
414 assert_eq!(point.geo_type, GeoType::Point);
415 assert_eq!(point.coordinates.len(), 1);
416
417 let line = GeoLocation::line(vec![(45.0, -71.0), (46.0, -72.0)]);
418 assert_eq!(line.geo_type, GeoType::Line);
419 assert_eq!(line.coordinates.len(), 2);
420
421 let polygon = GeoLocation::polygon(vec![(45.0, -71.0), (46.0, -71.0), (45.0, -71.0)]);
422 assert_eq!(polygon.geo_type, GeoType::Polygon);
423 assert_eq!(polygon.coordinates.len(), 3);
424
425 let bbox = GeoLocation::bbox(45.0, -72.0, 46.0, -71.0);
426 assert_eq!(bbox.geo_type, GeoType::Box);
427 assert_eq!(bbox.coordinates.len(), 2);
428 }
429
430 #[test]
431 fn test_whitespace_handling() {
432 let loc = parse_point(" 45.256 -71.92 ").unwrap();
433 assert_eq!(loc.coordinates[0], (45.256, -71.92));
434 }
435}