sqlx_xugu/types/geometry/
polygon.rs

1use super::XgPoint;
2use crate::arguments::XuguArgumentValue;
3use crate::protocol::text::ColumnType;
4use crate::{Xugu, XuguTypeInfo, XuguValueRef};
5use sqlx_core::decode::Decode;
6use sqlx_core::encode::{Encode, IsNull};
7use sqlx_core::error::BoxDynError;
8use sqlx_core::types::Type;
9use sqlx_core::Error;
10use std::borrow::Cow;
11use std::fmt::{Display, Formatter, Write};
12use std::str::FromStr;
13
14/// ## Xugu Geometric Polygon type
15///
16/// Description: Polygon (similar to closed polygon)
17/// Representation: `((x1,y1),...)`
18///
19/// Polygons are represented by lists of points (the vertexes of the polygon). Polygons are very similar to closed paths; the essential semantic difference is that a polygon is considered to include the area within it, while a path is not.
20/// An important implementation difference between polygons and paths is that the stored representation of a polygon includes its smallest bounding box. This speeds up certain search operations, although computing the bounding box adds overhead while constructing new polygons.
21/// Values of type polygon are specified using any of the following syntaxes:
22///
23/// ```text
24/// ( ( x1 , y1 ) , ... , ( xn , yn ) )
25///   ( x1 , y1 ) , ... , ( xn , yn )
26///   ( x1 , y1   , ... ,   xn , yn )
27///     x1 , y1   , ... ,   xn , yn
28/// ```
29///
30/// where the points are the end points of the line segments comprising the boundary of the polygon.
31///
32#[derive(Debug, Clone, PartialEq)]
33pub struct XgPolygon {
34    pub points: Vec<XgPoint>,
35}
36
37impl Type<Xugu> for XgPolygon {
38    fn type_info() -> XuguTypeInfo {
39        XuguTypeInfo::binary(ColumnType::POLYGON)
40    }
41
42    fn compatible(ty: &XuguTypeInfo) -> bool {
43        matches!(
44            ty.r#type,
45            ColumnType::CHAR | ColumnType::POLYGON | ColumnType::POLYGON_OLD
46        )
47    }
48}
49
50impl<'r> Decode<'r, Xugu> for XgPolygon {
51    fn decode(value: XuguValueRef<'r>) -> Result<Self, BoxDynError> {
52        let s = value.as_str()?;
53        Ok(Self::from_str(s)?)
54    }
55}
56
57impl Encode<'_, Xugu> for XgPolygon {
58    fn encode_by_ref(&self, args: &mut Vec<XuguArgumentValue>) -> Result<IsNull, BoxDynError> {
59        let s = self.to_string();
60
61        args.push(XuguArgumentValue::Str(Cow::Owned(s)));
62
63        Ok(IsNull::No)
64    }
65
66    fn produces(&self) -> Option<XuguTypeInfo> {
67        Some(XuguTypeInfo::binary(ColumnType::CHAR))
68    }
69}
70
71impl Display for XgPolygon {
72    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
73        f.write_char('(')?;
74        for (i, p) in self.points.iter().enumerate() {
75            if i > 0 {
76                f.write_char(',')?;
77            }
78            Display::fmt(&p, f)?;
79        }
80        f.write_char(')')
81    }
82}
83
84fn parse_float_from_str(s: &str, error_msg: &str) -> Result<f64, Error> {
85    s.parse().map_err(|_| Error::Decode(error_msg.into()))
86}
87
88impl FromStr for XgPolygon {
89    type Err = Error;
90
91    fn from_str(s: &str) -> Result<Self, Self::Err> {
92        let sanitised = s.replace(['(', ')', '[', ']', ' '], "");
93        let parts = sanitised.split(',').collect::<Vec<_>>();
94
95        let mut points = vec![];
96
97        if parts.len() % 2 != 0 {
98            return Err(Error::Decode(
99                format!("Unmatched pair in POLYGON: {}", s).into(),
100            ));
101        }
102
103        for chunk in parts.chunks_exact(2) {
104            if let [x_str, y_str] = chunk {
105                let x = parse_float_from_str(x_str, "could not get x")?;
106                let y = parse_float_from_str(y_str, "could not get y")?;
107
108                let point = XgPoint { x, y };
109                points.push(point);
110            }
111        }
112
113        if !points.is_empty() {
114            return Ok(Self { points });
115        }
116
117        Err(Error::Decode(
118            format!("could not get polygon from {}", s).into(),
119        ))
120    }
121}