Skip to main content

geo_wkt_writer/
lib.rs

1use geo_types::{Coordinate, Geometry, GeometryCollection, Line, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, Rect, Triangle};
2use std::fmt;
3use geo::algorithm::orient::{Direction, Orient};
4
5pub trait ToWkt {
6    /// Returns a WKT representation of a geo-types Geometry
7    ///
8    /// There is no Rect or Triangle WKT text type, so both of those will be serialized
9    /// as POLYGON WKT representations. Any polygons returned will be normalized.
10    ///
11    /// # Examples
12    ///
13    /// ```
14    /// use geo_types::{Geometry, GeometryCollection, polygon, point};
15    /// use geo_wkt_writer::ToWkt;
16    ///
17    /// let poly = Geometry::Polygon(polygon![
18    ///             (x: 1.0, y: 1.0),
19    ///             (x: 4.0, y: 1.0),
20    ///             (x: 4.0, y: 4.0),
21    ///             (x: 1.0, y: 4.0),
22    ///             (x: 1.0, y: 1.0),
23    ///         ]);
24    /// let pe = Geometry::Point(point!(x: 1.0, y: 1.0));
25    /// let gc = GeometryCollection(vec![pe, poly]);
26    /// let wkt_out = gc.to_wkt();
27    /// let expected = String::from("GEOMETRYCOLLECTION(POINT(1 1),POLYGON((1 1,4 1,4 4,1 4,1 1)))");
28    /// assert_eq!(wkt_out, expected);
29    /// ```
30    fn to_wkt(&self) -> String;
31}
32
33/** Geometries */
34
35impl<T: num_traits::Float + fmt::Display> ToWkt for GeometryCollection<T> {
36    fn to_wkt(&self) -> String {
37        if self.is_empty() {
38            "GEOMETRYCOLLECTION EMPTY".into()
39        } else {
40            format!(
41                "GEOMETRYCOLLECTION({})",
42                self.0
43                    .iter()
44                    .map(|p| p.to_wkt())
45                    .collect::<Vec<String>>()
46                    .join(",")
47            )
48        }
49    }
50}
51
52impl<T: num_traits::Float + fmt::Display> ToWkt for Geometry<T> {
53    fn to_wkt(&self) -> String {
54        match self {
55            Geometry::MultiPolygon { .. } => self.clone().into_multi_polygon().unwrap().to_wkt(),
56            Geometry::Polygon { .. } => self.clone().into_polygon().unwrap().to_wkt(),
57            Geometry::MultiLineString { .. } => {
58                self.clone().into_multi_line_string().unwrap().to_wkt()
59            }
60            Geometry::LineString { .. } => self.clone().into_line_string().unwrap().to_wkt(),
61            Geometry::Point { .. } => self.clone().into_point().unwrap().to_wkt(),
62            _ => "GEOMETRYCOLLECTION EMPTY".into(),
63        }
64    }
65}
66
67/** Polygons */
68
69impl<T: num_traits::Float + fmt::Display> ToWkt for MultiPolygon<T> {
70    fn to_wkt(&self) -> String {
71        multi_polygon_to_wkt(self)
72    }
73}
74
75fn multi_polygon_to_wkt<T: num_traits::Float + fmt::Display>(poly: &MultiPolygon<T>) -> String {
76    if poly.0.is_empty() {
77        "MULTIPOLYGON EMPTY".into()
78    } else {
79        format!(
80            "MULTIPOLYGON((({})))",
81            poly.0
82                .iter()
83                .map(|p| polygon_linestrings_to_wkt(&p))
84                .collect::<Vec<String>>()
85                .join(")),((")
86        )
87    }
88}
89
90impl<T: num_traits::Float + fmt::Display> ToWkt for Polygon<T> {
91    fn to_wkt(&self) -> String {
92        polygon_to_wkt(self)
93    }
94}
95
96fn polygon_to_wkt<T: num_traits::Float + fmt::Display>(poly: &Polygon<T>) -> String {
97    if poly.exterior().0.is_empty() {
98        "POLYGON EMPTY".into()
99    } else {
100        format!("POLYGON(({}))", polygon_linestrings_to_wkt(poly))
101    }
102}
103
104fn polygon_linestrings_to_wkt<T: num_traits::Float + fmt::Display>(poly: &Polygon<T>) -> String {
105    let norm_poly = poly.orient(Direction::Default);
106    let mut lines: Vec<LineString<T>> = norm_poly.interiors().into();
107    let exterior: &LineString<T> = norm_poly.exterior();
108    lines.insert(0, exterior.clone());
109
110    lines
111        .iter()
112        .map(|l| line_to_wkt(&l))
113        .collect::<Vec<String>>()
114        .join("),(")
115}
116
117/** Rect */
118
119impl<T: num_traits::Float + fmt::Display> ToWkt for Rect<T> {
120    fn to_wkt(&self) -> String {
121        format!("POLYGON(({} {},{} {},{} {},{} {},{} {}))",
122        self.min.x, self.min.y,
123        self.min.x, self.max.y,
124        self.max.x, self.max.y,
125        self.max.x, self.min.y,
126        self.min.x, self.min.y)
127    }
128}
129
130/** Triangle */
131
132impl<T: num_traits::Float + fmt::Display> ToWkt for Triangle<T> {
133    fn to_wkt(&self) -> String {
134        format!("POLYGON(({} {},{} {},{} {},{} {}))",
135                self.0.x, self.0.y,
136                self.1.x, self.1.y,
137                self.2.x, self.2.y,
138                self.0.x, self.0.y)
139    }
140}
141
142/** Lines */
143
144impl<T: num_traits::Float + fmt::Display> ToWkt for MultiLineString<T> {
145    fn to_wkt(&self) -> String {
146        multi_linestring_to_wkt(self)
147    }
148}
149
150fn multi_linestring_to_wkt<T: num_traits::Float + fmt::Display>(
151    multi_line: &MultiLineString<T>,
152) -> String {
153    if multi_line.0.is_empty() {
154        "MULTILINESTRING EMPTY".into()
155    } else {
156        format!(
157            "MULTILINESTRING(({}))",
158            multi_line
159                .0
160                .iter()
161                .map(|l| line_to_wkt(&l))
162                .collect::<Vec<String>>()
163                .join("),(")
164        )
165    }
166}
167
168impl<T: num_traits::Float + fmt::Display> ToWkt for LineString<T> {
169    fn to_wkt(&self) -> String {
170        linestring_to_wkt(self)
171    }
172}
173
174impl<T: num_traits::Float + fmt::Display> ToWkt for Line<T> {
175    fn to_wkt(&self) -> String {
176        linestring_to_wkt(&LineString(vec![self.start, self.end]))
177    }
178}
179
180fn linestring_to_wkt<T: num_traits::Float + fmt::Display>(line: &LineString<T>) -> String {
181    if line.0.is_empty() {
182        "LINESTRING EMPTY".into()
183    } else {
184        format!("LINESTRING({})", line_to_wkt(line))
185    }
186}
187
188fn line_to_wkt<T: num_traits::Float + fmt::Display>(line: &LineString<T>) -> String {
189    line.0
190        .iter()
191        .map(|c| coord_to_wkt(&c))
192        .collect::<Vec<String>>()
193        .join(",")
194}
195
196/** Points */
197
198impl<T: num_traits::Float + fmt::Display> ToWkt for MultiPoint<T> {
199    fn to_wkt(&self) -> String {
200        multi_point_to_wkt(self)
201    }
202}
203
204fn multi_point_to_wkt<T: num_traits::Float + fmt::Display>(multi_point: &MultiPoint<T>) -> String {
205    if multi_point.0.is_empty() {
206        "MULTIPOINT EMPTY".into()
207    } else {
208        format!(
209            "MULTIPOINT({})",
210            multi_point
211                .0
212                .iter()
213                .map(|p| point_to_string(&p))
214                .collect::<Vec<String>>()
215                .join(",")
216        )
217    }
218}
219
220impl<T: num_traits::Float + fmt::Display> ToWkt for Point<T> {
221    fn to_wkt(&self) -> String {
222        point_to_wkt(self)
223    }
224}
225
226fn point_to_wkt<T: num_traits::Float + fmt::Display>(point: &Point<T>) -> String {
227    format!("POINT({})", point_to_string(point))
228}
229
230fn point_to_string<T: num_traits::Float + fmt::Display>(point: &Point<T>) -> String {
231    coord_to_wkt(&point.0)
232}
233
234fn coord_to_wkt<T: num_traits::Float + fmt::Display>(coord: &Coordinate<T>) -> String {
235    format!("{} {}", coord.x, coord.y)
236}
237
238/** Tests */
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use geo_types::{line_string, point, polygon};
244
245    #[test]
246    fn can_format_geom_collection() {
247        let poly = Geometry::Polygon(polygon![
248            (x: 1.0, y: 1.0),
249            (x: 4.0, y: 1.0),
250            (x: 4.0, y: 4.0),
251            (x: 1.0, y: 4.0),
252            (x: 1.0, y: 1.0),
253        ]);
254        let pe = Geometry::Point(point!(x: 1.0, y: 1.0));
255        let gc = GeometryCollection(vec![pe, poly]);
256        let wkt_out = gc.to_wkt();
257        let expected =
258            String::from("GEOMETRYCOLLECTION(POINT(1 1),POLYGON((1 1,4 1,4 4,1 4,1 1)))");
259        assert_eq!(wkt_out, expected);
260    }
261
262    #[test]
263    fn can_format_empty_geom_collection() {
264        let gc = GeometryCollection(vec![] as Vec<Geometry<f64>>);
265        let wkt_out = gc.to_wkt();
266        let expected = String::from("GEOMETRYCOLLECTION EMPTY");
267        assert_eq!(wkt_out, expected);
268    }
269
270    #[test]
271    fn can_format_multi_polygon() {
272        let poly1 = polygon![
273            (x: 1.0, y: 1.0),
274            (x: 4.0, y: 1.0),
275            (x: 4.0, y: 4.0),
276            (x: 1.0, y: 4.0),
277            (x: 1.0, y: 1.0),
278        ];
279        let poly2 = polygon!(
280        exterior: [
281            (x: 0.0, y: 0.0),
282            (x: 6.0, y: 0.0),
283            (x: 6.0, y: 6.0),
284            (x: 0.0, y: 6.0),
285            (x: 0.0, y: 0.0),],
286        interiors:[[
287            (x: 1.0, y: 1.0),
288            (x: 4.0, y: 1.0),
289            (x: 4.0, y: 4.0),
290            (x: 1.50, y: 4.0),
291            (x: 1.0, y: 1.0),]
292            ]
293        );
294        let mp = MultiPolygon(vec![poly1, poly2]);
295        let wkt_out = mp.to_wkt();
296        let expected = String::from(
297            "MULTIPOLYGON(((1 1,4 1,4 4,1 4,1 1)),((0 0,6 0,6 6,0 6,0 0),(1 1,1.5 4,4 4,4 1,1 1)))",
298        );
299        assert_eq!(wkt_out, expected);
300    }
301
302    #[test]
303    fn can_format_empty_multi_polygon() {
304        let mp = MultiPolygon(vec![] as Vec<Polygon<f64>>);
305        let wkt_out = mp.to_wkt();
306        let expected = String::from("MULTIPOLYGON EMPTY");
307        assert_eq!(wkt_out, expected);
308    }
309
310    #[test]
311    fn can_format_polygon() {
312        let poly = polygon![
313            (x: 1.0, y: 1.0),
314            (x: 4.0, y: 1.0),
315            (x: 4.0, y: 4.0),
316            (x: 1.0, y: 4.0),
317            (x: 1.0, y: 1.0),
318        ];
319        let wkt_out = poly.to_wkt();
320        let expected = String::from("POLYGON((1 1,4 1,4 4,1 4,1 1))");
321        assert_eq!(wkt_out, expected);
322    }
323
324    #[test]
325    fn can_format_empty_polygon() {
326        let poly: Polygon<f64> =
327            Polygon::new(LineString::from(vec![] as Vec<Coordinate<f64>>), vec![]);
328        let wkt_out = poly.to_wkt();
329        let expected = String::from("POLYGON EMPTY");
330        assert_eq!(wkt_out, expected);
331    }
332
333    #[test]
334    fn can_format_polygon_with_hole() {
335        let poly = polygon!(
336        exterior: [
337            (x: 0.0, y: 0.0),
338            (x: 6.0, y: 0.0),
339            (x: 6.0, y: 6.0),
340            (x: 0.0, y: 6.0),
341            (x: 0.0, y: 0.0),],
342        interiors:[[
343            (x: 1.0, y: 1.0),
344            (x: 4.0, y: 1.0),
345            (x: 4.0, y: 4.0),
346            (x: 1.50, y: 4.0),
347            (x: 1.0, y: 1.0),]
348            ]
349        );
350        let wkt_out = poly.to_wkt();
351        let expected = String::from("POLYGON((0 0,6 0,6 6,0 6,0 0),(1 1,1.5 4,4 4,4 1,1 1))");
352        assert_eq!(wkt_out, expected);
353    }
354
355    #[test]
356    fn can_format_multi_line_string() {
357        let line1 = line_string![
358            (x: 1.0, y: 1.0),
359            (x: 4.0, y: 1.0),
360            (x: 4.0, y: 4.0),
361            (x: 1.50, y: 4.0),
362        ];
363        let line2 = line_string![
364            (x: 11.0, y: 21.0),
365            (x: 34.0, y: 21.0),
366            (x: 24.0, y: 54.0),
367            (x: 31.50, y: 34.0),
368        ];
369        let ml = MultiLineString(vec![line1, line2]);
370        let wkt_out = ml.to_wkt();
371        let expected =
372            String::from("MULTILINESTRING((1 1,4 1,4 4,1.5 4),(11 21,34 21,24 54,31.5 34))");
373        assert_eq!(wkt_out, expected);
374    }
375
376    #[test]
377    fn can_format_empty_multi_line_string() {
378        let ml = MultiLineString(vec![] as Vec<LineString<f64>>);
379        let wkt_out = ml.to_wkt();
380        let expected = String::from("MULTILINESTRING EMPTY");
381        assert_eq!(wkt_out, expected);
382    }
383
384    #[test]
385    fn can_format_line_string() {
386        let line = line_string![
387            (x: 1.0, y: 1.0),
388            (x: 4.0, y: 1.0),
389            (x: 4.0, y: 4.0),
390            (x: 1.50, y: 4.0),
391            (x: 1.0, y: 1.0),
392        ];
393        let wkt_out = line.to_wkt();
394        let expected = String::from("LINESTRING(1 1,4 1,4 4,1.5 4,1 1)");
395        assert_eq!(wkt_out, expected);
396    }
397
398    #[test]
399    fn can_format_empty_line_string() {
400        let line = LineString::from(vec![] as Vec<Coordinate<f64>>);
401        let wkt_out = line.to_wkt();
402        let expected = String::from("LINESTRING EMPTY");
403        assert_eq!(wkt_out, expected);
404    }
405
406    #[test]
407    fn can_format_multi_point() {
408        let point1 = point!(x: 22.200, y: 31.0);
409        let point2 = point!(x: 4356.0, y: 1002.345);
410        let mp = MultiPoint(vec![point1, point2]);
411        let wkt_out = mp.to_wkt();
412        let expected = String::from("MULTIPOINT(22.2 31,4356 1002.345)");
413        assert_eq!(wkt_out, expected);
414    }
415
416    #[test]
417    fn can_format_empty_multi_point() {
418        let mp = MultiPoint(vec![] as Vec<Point<f64>>);
419        let wkt_out = mp.to_wkt();
420        let expected = String::from("MULTIPOINT EMPTY");
421        assert_eq!(wkt_out, expected);
422    }
423
424    #[test]
425    fn can_format_point() {
426        let point = point!(x: 22.200, y: 31.0);
427        let wkt_out = point.to_wkt();
428        let expected = String::from("POINT(22.2 31)");
429        assert_eq!(wkt_out, expected);
430    }
431}