1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
 * Rust type for
 * [polygon](https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-POLYGON).
 */
#[cfg_attr(docsrs, doc(cfg(feature = "geo")))]
#[derive(Clone, Debug, PartialEq)]
pub struct Polygon(geo_types::Polygon<f64>);

impl Polygon {
    #[must_use]
    pub fn new(path: &crate::Path) -> Self {
        use std::ops::Deref;

        Self(geo_types::Polygon::new(path.deref().clone(), Vec::new()))
    }
}

impl std::ops::Deref for Polygon {
    type Target = geo_types::Polygon<f64>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl std::fmt::Display for Polygon {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use std::fmt::Write as _;

        let mut s = String::new();

        for coordinate in self.0.exterior().points() {
            write!(s, "({}, {}),", coordinate.x(), coordinate.y())?;
        }

        write!(f, "{}", s.trim_end_matches(','))
    }
}

#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Polygon {
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
        let polygon = Self::new(&crate::Path::arbitrary(u)?);

        Ok(polygon)
    }
}

#[cfg_attr(docsrs, doc(cfg(feature = "geo")))]
impl crate::ToSql for Polygon {
    fn ty(&self) -> crate::pq::Type {
        crate::pq::types::POLYGON
    }

    /*
     * https://github.com/postgres/postgres/blob/REL_12_0/src/backend/utils/adt/geo_ops.c#L3389
     */
    fn to_text(&self) -> crate::Result<Option<String>> {
        self.to_string().to_text()
    }

    /*
     * https://github.com/postgres/postgres/blob/REL_12_0/src/backend/utils/adt/geo_ops.c#L3440
     */
    fn to_binary(&self) -> crate::Result<Option<Vec<u8>>> {
        let points = &self.0.exterior().0;

        let mut buf = Vec::new();

        crate::to_sql::write_i32(&mut buf, points.len() as i32)?;

        for point in points {
            crate::to_sql::write_f64(&mut buf, point.x)?;
            crate::to_sql::write_f64(&mut buf, point.y)?;
        }

        Ok(Some(buf))
    }
}

#[cfg_attr(docsrs, doc(cfg(feature = "geo")))]
impl crate::FromSql for Polygon {
    /*
     * https://github.com/postgres/postgres/blob/REL_12_0/src/backend/utils/adt/geo_ops.c#L3348
     */
    fn from_text(ty: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
        let path = crate::Path::from_text(ty, raw)?;

        Ok(Self::new(&path))
    }

    /*
     * https://github.com/postgres/postgres/blob/REL_12_0/src/backend/utils/adt/geo_ops.c#L3405
     */
    fn from_binary(_: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
        let mut buf = crate::from_sql::not_null(raw)?;
        let npts = crate::from_sql::read_i32(&mut buf)?;
        let mut coordinates = Vec::new();

        for _ in 0..npts {
            let x = crate::from_sql::read_f64(&mut buf)?;
            let y = crate::from_sql::read_f64(&mut buf)?;

            let coordinate = crate::Coordinate::new(x, y);
            coordinates.push(coordinate);
        }

        Ok(Self::new(&crate::Path::new(&coordinates.into())))
    }
}

#[cfg_attr(docsrs, doc(cfg(feature = "geo")))]
impl crate::entity::Simple for Polygon {}

#[cfg(test)]
mod test {
    crate::sql_test!(
        polygon,
        crate::Polygon,
        [(
            "'((0, 0), (10, 10), (10, 0), (0, 0))'",
            crate::Polygon::new(&crate::Path::new(
                &vec![
                    crate::Coordinate::new(0., 0.),
                    crate::Coordinate::new(10., 10.),
                    crate::Coordinate::new(10., 0.),
                    crate::Coordinate::new(0., 0.),
                ]
                .into()
            ))
        )]
    );
}