typed_shapefile/shapefile/record/
polyline.rs

1//! Module with the definition of Polyline, PolylineM, PolylineZ
2
3use std::fmt;
4use std::io::{Read, Write};
5use std::mem::size_of;
6
7use super::io::*;
8use super::traits::{GrowablePoint, ShrinkablePoint};
9use super::ConcreteReadableShape;
10use super::GenericBBox;
11use super::{EsriShape, HasShapeType, WritableShape};
12use super::{Point, PointM, PointZ};
13use crate::shapefile::{Error, ShapeType};
14
15#[cfg(feature = "geo-types")]
16use geo_types;
17
18/// Generic struct to create Polyline; PolylineM, PolylineZ
19///
20/// Polylines can have multiple parts.
21///
22/// Polylines parts must have 2 at least 2 points
23///
24/// To create a polyline with only one part use [`new`],
25/// to create a polyline with multiple parts use [`with_parts`]
26///
27/// # geo-types
28///
29/// shapefile's Polyline can be converted to geo_types's `MultiLineString<f64>`
30///
31/// geo-types's `Line`, `LineString`, `MultiLineString` can be converted to shapefile's Polyline
32/// ```
33/// # #[cfg(feature = "geo-types")]
34/// # fn main() -> Result<(), typed_shapefile::shapefile::Error>{
35/// let mut polylines = typed_shapefile::shapefile::read_shapes_as::<_, typed_shapefile::shapefile::Polyline>("tests/data/line.shp")?;
36/// let geo_polyline: geo_types::MultiLineString<f64> = polylines.pop().unwrap().into();
37/// let polyline = typed_shapefile::shapefile::Polyline::from(geo_polyline);
38/// # Ok(())
39/// # }
40/// # #[cfg(not(feature = "geo-types"))]
41/// # fn main() {}
42/// ```
43///
44/// [`new`]: #method.new
45/// [`with_parts`]: #method.with_parts
46#[derive(Debug, Clone, PartialEq)]
47pub struct GenericPolyline<PointType> {
48    pub(crate) bbox: GenericBBox<PointType>,
49    pub(crate) parts: Vec<Vec<PointType>>,
50}
51
52/// Creating a Polyline
53impl<PointType: ShrinkablePoint + GrowablePoint + Copy> GenericPolyline<PointType> {
54    /// # Examples
55    ///
56    /// Polyline with single part
57    /// ```
58    /// use typed_shapefile::shapefile::{Point, Polyline};
59    /// let points = vec![
60    ///     Point::new(1.0, 1.0),
61    ///     Point::new(2.0, 2.0),
62    /// ];
63    /// let poly = Polyline::new(points);
64    /// ```
65    ///
66    /// # panic
67    ///
68    /// This will panic if the vec has less than 2 points
69    pub fn new(points: Vec<PointType>) -> Self {
70        assert!(
71            points.len() >= 2,
72            "Polylines parts must have at least 2 points"
73        );
74        Self {
75            bbox: GenericBBox::<PointType>::from_points(&points),
76            parts: vec![points],
77        }
78    }
79
80    /// # Examples
81    ///
82    /// Polyline with multiple parts
83    /// ```
84    /// use typed_shapefile::shapefile::{Point, Polyline};
85    /// let first_part = vec![
86    ///     Point::new(1.0, 1.0),
87    ///     Point::new(2.0, 2.0),
88    /// ];
89    ///
90    /// let second_part = vec![
91    ///     Point::new(3.0, 1.0),
92    ///     Point::new(5.0, 6.0),
93    /// ];
94    ///
95    /// let third_part = vec![
96    ///     Point::new(17.0, 15.0),
97    ///     Point::new(18.0, 19.0),
98    ///     Point::new(20.0, 19.0),
99    /// ];
100    /// let poly = Polyline::with_parts(vec![first_part, second_part, third_part]);
101    /// ```
102    ///
103    /// # panic
104    ///
105    /// This will panic if any of the parts are less than 2 points
106    pub fn with_parts(parts: Vec<Vec<PointType>>) -> Self {
107        assert!(
108            parts.iter().all(|p| p.len() >= 2),
109            "Polylines parts must have at least 2 points"
110        );
111        Self {
112            bbox: GenericBBox::<PointType>::from_parts(&parts),
113            parts,
114        }
115    }
116}
117
118impl<PointType> GenericPolyline<PointType> {
119    /// Returns the bounding box associated to the polyline
120    #[inline]
121    pub fn bbox(&self) -> &GenericBBox<PointType> {
122        &self.bbox
123    }
124
125    /// Returns a reference to all the parts
126    #[inline]
127    pub fn parts(&self) -> &Vec<Vec<PointType>> {
128        &self.parts
129    }
130
131    /// Returns a reference to a part
132    #[inline]
133    pub fn part(&self, index: usize) -> Option<&Vec<PointType>> {
134        self.parts.get(index)
135    }
136
137    /// Consumes the polyline and returns the parts
138    #[inline]
139    pub fn into_inner(self) -> Vec<Vec<PointType>> {
140        self.parts
141    }
142
143    /// Returns the number of points contained in all the parts
144    #[inline]
145    pub fn total_point_count(&self) -> usize {
146        self.parts.iter().map(|part| part.len()).sum()
147    }
148}
149
150/// Specialization of the `GenericPolyline` struct to represent a `Polyline` shape
151/// ( collection of [Point](../point/struct.Point.html))
152pub type Polyline = GenericPolyline<Point>;
153
154impl Polyline {
155    pub(crate) fn size_of_record(num_points: i32, num_parts: i32) -> usize {
156        let mut size = 0usize;
157        size += 4 * size_of::<f64>(); // BBOX
158        size += size_of::<i32>(); // num parts
159        size += size_of::<i32>(); // num points
160        size += size_of::<i32>() * num_parts as usize;
161        size += size_of::<Point>() * num_points as usize;
162        size
163    }
164}
165
166impl fmt::Display for Polyline {
167    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
168        write!(f, "Polyline({} parts)", self.parts.len())
169    }
170}
171
172impl HasShapeType for Polyline {
173    fn shapetype() -> ShapeType {
174        ShapeType::Polyline
175    }
176}
177
178impl ConcreteReadableShape for Polyline {
179    fn read_shape_content<T: Read>(source: &mut T, record_size: i32) -> Result<Self, Error> {
180        let rdr = MultiPartShapeReader::<Point, T>::new(source)?;
181        if record_size != Self::size_of_record(rdr.num_points, rdr.num_parts) as i32 {
182            Err(Error::InvalidShapeRecordSize)
183        } else {
184            rdr.read_xy().map_err(Error::IoError).and_then(|rdr| {
185                Ok(Self {
186                    bbox: rdr.bbox,
187                    parts: rdr.parts,
188                })
189            })
190        }
191    }
192}
193
194impl WritableShape for Polyline {
195    fn size_in_bytes(&self) -> usize {
196        let mut size = 0usize;
197        size += 4 * size_of::<f64>();
198        size += size_of::<i32>();
199        size += size_of::<i32>();
200        size += size_of::<i32>() * self.parts.len();
201        size += 2 * size_of::<f64>() * self.total_point_count();
202        size
203    }
204
205    fn write_to<T: Write>(&self, dest: &mut T) -> Result<(), Error> {
206        let parts_iter = self.parts.iter().map(|part| part.as_slice());
207        let writer = MultiPartShapeWriter::new(&self.bbox, parts_iter, dest);
208        writer.write_point_shape()?;
209        Ok(())
210    }
211}
212
213impl EsriShape for Polyline {
214    fn x_range(&self) -> [f64; 2] {
215        self.bbox.x_range()
216    }
217
218    fn y_range(&self) -> [f64; 2] {
219        self.bbox.y_range()
220    }
221}
222
223/*
224 * PolylineM
225 */
226
227/// Specialization of the `GenericPolyline` struct to represent a `PolylineM` shape
228/// ( collection of [PointM](../point/struct.PointM.html))
229pub type PolylineM = GenericPolyline<PointM>;
230
231impl PolylineM {
232    pub(crate) fn size_of_record(num_points: i32, num_parts: i32, is_m_used: bool) -> usize {
233        let mut size = Polyline::size_of_record(num_points, num_parts);
234        if is_m_used {
235            size += 2 * size_of::<f64>(); // MRange
236            size += num_points as usize * size_of::<f64>(); // M
237        }
238        size
239    }
240}
241
242impl fmt::Display for PolylineM {
243    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244        write!(f, "PolylineM({} parts)", self.parts.len())
245    }
246}
247
248impl HasShapeType for PolylineM {
249    fn shapetype() -> ShapeType {
250        ShapeType::PolylineM
251    }
252}
253
254impl ConcreteReadableShape for PolylineM {
255    fn read_shape_content<T: Read>(source: &mut T, record_size: i32) -> Result<Self, Error> {
256        let rdr = MultiPartShapeReader::<PointM, T>::new(source)?;
257
258        let record_size_with_m = Self::size_of_record(rdr.num_points, rdr.num_parts, true) as i32;
259        let record_size_without_m =
260            Self::size_of_record(rdr.num_points, rdr.num_parts, false) as i32;
261
262        if (record_size != record_size_with_m) && (record_size != record_size_without_m) {
263            Err(Error::InvalidShapeRecordSize)
264        } else {
265            rdr.read_xy()
266                .and_then(|rdr| rdr.read_ms_if(record_size == record_size_with_m))
267                .map_err(Error::IoError)
268                .and_then(|rdr| {
269                    Ok(Self {
270                        bbox: rdr.bbox,
271                        parts: rdr.parts,
272                    })
273                })
274        }
275    }
276}
277
278impl WritableShape for PolylineM {
279    fn size_in_bytes(&self) -> usize {
280        let mut size = 0 as usize;
281        size += size_of::<f64>() * 4;
282        size += size_of::<i32>(); // num parts
283        size += size_of::<i32>(); //num points
284        size += size_of::<i32>() * self.parts.len();
285        size += 3 * size_of::<f64>() * self.total_point_count();
286        size += 2 * size_of::<f64>();
287        size
288    }
289
290    fn write_to<T: Write>(&self, dest: &mut T) -> Result<(), Error> {
291        let parts_iter = self.parts.iter().map(|part| part.as_slice());
292        let writer = MultiPartShapeWriter::new(&self.bbox, parts_iter, dest);
293        writer.write_point_m_shape()?;
294        Ok(())
295    }
296}
297
298impl EsriShape for PolylineM {
299    fn x_range(&self) -> [f64; 2] {
300        self.bbox.x_range()
301    }
302
303    fn y_range(&self) -> [f64; 2] {
304        self.bbox.y_range()
305    }
306
307    fn m_range(&self) -> [f64; 2] {
308        self.bbox.m_range()
309    }
310}
311
312/*
313 * PolylineZ
314 */
315
316/// Specialization of the `GenericPolyline` struct to represent a `PolylineZ` shape
317/// ( collection of [PointZ](../point/struct.PointZ.html))
318pub type PolylineZ = GenericPolyline<PointZ>;
319
320impl PolylineZ {
321    pub(crate) fn size_of_record(num_points: i32, num_parts: i32, is_m_used: bool) -> usize {
322        let mut size = Polyline::size_of_record(num_points, num_parts);
323        size += 2 * size_of::<f64>(); // ZRange
324        size += num_points as usize * size_of::<f64>(); // Z
325        if is_m_used {
326            size += 2 * size_of::<f64>(); // MRange
327            size += num_points as usize * size_of::<f64>(); // M
328        }
329        size
330    }
331}
332
333impl fmt::Display for PolylineZ {
334    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
335        write!(f, "PolylineZ({} parts)", self.parts.len())
336    }
337}
338
339impl HasShapeType for PolylineZ {
340    fn shapetype() -> ShapeType {
341        ShapeType::PolylineZ
342    }
343}
344
345impl ConcreteReadableShape for PolylineZ {
346    fn read_shape_content<T: Read>(source: &mut T, record_size: i32) -> Result<Self, Error> {
347        let rdr = MultiPartShapeReader::<PointZ, T>::new(source)?;
348
349        let record_size_with_m = Self::size_of_record(rdr.num_points, rdr.num_parts, true) as i32;
350        let record_size_without_m =
351            Self::size_of_record(rdr.num_points, rdr.num_parts, false) as i32;
352
353        if (record_size != record_size_with_m) && (record_size != record_size_without_m) {
354            Err(Error::InvalidShapeRecordSize)
355        } else {
356            rdr.read_xy()
357                .and_then(|rdr| rdr.read_zs())
358                .and_then(|rdr| rdr.read_ms_if(record_size == record_size_with_m))
359                .map_err(Error::IoError)
360                .and_then(|rdr| {
361                    Ok(Self {
362                        bbox: rdr.bbox,
363                        parts: rdr.parts,
364                    })
365                })
366        }
367    }
368}
369
370impl WritableShape for PolylineZ {
371    fn size_in_bytes(&self) -> usize {
372        let mut size = 0 as usize;
373        size += size_of::<f64>() * 4;
374        size += size_of::<i32>(); // num parts
375        size += size_of::<i32>(); //num points
376        size += size_of::<i32>() * self.parts.len();
377        size += 4 * size_of::<f64>() * self.total_point_count();
378        size += 2 * size_of::<f64>();
379        size += 2 * size_of::<f64>();
380        size
381    }
382
383    fn write_to<T: Write>(&self, dest: &mut T) -> Result<(), Error> {
384        let parts_iter = self.parts.iter().map(|part| part.as_slice());
385        let writer = MultiPartShapeWriter::new(&self.bbox, parts_iter, dest);
386        writer.write_point_z_shape()?;
387        Ok(())
388    }
389}
390
391impl EsriShape for PolylineZ {
392    fn x_range(&self) -> [f64; 2] {
393        self.bbox.x_range()
394    }
395
396    fn y_range(&self) -> [f64; 2] {
397        self.bbox.y_range()
398    }
399
400    fn z_range(&self) -> [f64; 2] {
401        self.bbox.z_range()
402    }
403
404    fn m_range(&self) -> [f64; 2] {
405        self.bbox.m_range()
406    }
407}
408
409#[cfg(feature = "geo-types")]
410impl<PointType> From<GenericPolyline<PointType>> for geo_types::MultiLineString<f64>
411where
412    PointType: Copy,
413    geo_types::Coordinate<f64>: From<PointType>,
414{
415    fn from(polyline: GenericPolyline<PointType>) -> Self {
416        use std::iter::FromIterator;
417        let mut lines = Vec::<geo_types::LineString<f64>>::with_capacity(polyline.parts().len());
418
419        for points in polyline.parts {
420            let line: Vec<geo_types::Coordinate<f64>> = points
421                .into_iter()
422                .map(geo_types::Coordinate::<f64>::from)
423                .collect();
424            lines.push(line.into());
425        }
426        geo_types::MultiLineString::<f64>::from_iter(lines.into_iter())
427    }
428}
429
430#[cfg(feature = "geo-types")]
431impl<PointType> From<geo_types::Line<f64>> for GenericPolyline<PointType>
432where
433    PointType: From<geo_types::Point<f64>> + ShrinkablePoint + GrowablePoint + Copy,
434{
435    fn from(line: geo_types::Line<f64>) -> Self {
436        let (p1, p2) = line.points();
437        Self::new(vec![PointType::from(p1), PointType::from(p2)])
438    }
439}
440
441#[cfg(feature = "geo-types")]
442impl<PointType> From<geo_types::LineString<f64>> for GenericPolyline<PointType>
443where
444    PointType: From<geo_types::Coordinate<f64>> + ShrinkablePoint + GrowablePoint + Copy,
445{
446    fn from(line: geo_types::LineString<f64>) -> Self {
447        let points: Vec<PointType> = line.into_iter().map(PointType::from).collect();
448        Self::new(points)
449    }
450}
451
452#[cfg(feature = "geo-types")]
453impl<PointType> From<geo_types::MultiLineString<f64>> for GenericPolyline<PointType>
454where
455    PointType: From<geo_types::Coordinate<f64>> + ShrinkablePoint + GrowablePoint + Copy,
456{
457    fn from(mls: geo_types::MultiLineString<f64>) -> Self {
458        let mut parts = Vec::<Vec<PointType>>::with_capacity(mls.0.len());
459        for linestring in mls.0.into_iter() {
460            parts.push(linestring.into_iter().map(PointType::from).collect());
461        }
462        Self::with_parts(parts)
463    }
464}
465
466#[cfg(test)]
467mod tests {
468    use super::*;
469
470    #[test]
471    #[should_panic(expected = "Polylines parts must have at least 2 points")]
472    fn test_polyline_new_less_than_2_points() {
473        let _polyline = Polyline::new(vec![Point::new(1.0, 1.0)]);
474    }
475
476    #[test]
477    #[should_panic(expected = "Polylines parts must have at least 2 points")]
478    fn test_polyline_with_parts_less_than_2_points() {
479        let _polyline = Polyline::with_parts(vec![
480            vec![Point::new(1.0, 1.0), Point::new(2.0, 2.0)],
481            vec![Point::new(1.0, 1.0)],
482        ]);
483    }
484}
485
486#[cfg(test)]
487#[cfg(feature = "geo-types")]
488mod test_geo_types_conversions {
489    use super::*;
490    use geo_types::{Coordinate, LineString, MultiLineString};
491    use NO_DATA;
492    use {PointM, PolylineM};
493
494    #[test]
495    fn test_polyline_into_multiline_string() {
496        let polyline_m = PolylineM::with_parts(vec![
497            vec![
498                PointM::new(1.0, 5.0, 0.0),
499                PointM::new(5.0, 5.0, NO_DATA),
500                PointM::new(5.0, 1.0, 3.0),
501            ],
502            vec![PointM::new(1.0, 5.0, 0.0), PointM::new(1.0, 1.0, 0.0)],
503        ]);
504
505        let multiline_string: MultiLineString<f64> = polyline_m.into();
506
507        let expected_multiline = geo_types::MultiLineString(vec![
508            LineString::<f64>(vec![
509                Coordinate { x: 1.0, y: 5.0 },
510                Coordinate { x: 5.0, y: 5.0 },
511                Coordinate { x: 5.0, y: 1.0 },
512            ]),
513            LineString::<f64>(vec![
514                Coordinate { x: 1.0, y: 5.0 },
515                Coordinate { x: 1.0, y: 1.0 },
516            ]),
517        ]);
518        assert_eq!(multiline_string, expected_multiline);
519    }
520
521    #[test]
522    fn test_line_into_polyline() {
523        let line = geo_types::Line::new(
524            Coordinate { x: 2.0, y: 3.0 },
525            Coordinate { x: 6.0, y: -6.0 },
526        );
527        let polyline: PolylineZ = line.into();
528
529        assert_eq!(
530            polyline.parts,
531            vec![vec![
532                PointZ::new(2.0, 3.0, 0.0, NO_DATA),
533                PointZ::new(6.0, -6.0, 0.0, NO_DATA)
534            ]]
535        );
536    }
537
538    #[test]
539    fn test_linestring_into_polyline() {
540        let linestring = LineString::from(vec![
541            Coordinate { x: 1.0, y: 5.0 },
542            Coordinate { x: 5.0, y: 5.0 },
543            Coordinate { x: 5.0, y: 1.0 },
544        ]);
545
546        let polyline: Polyline = linestring.into();
547        assert_eq!(
548            polyline.parts,
549            vec![vec![
550                Point::new(1.0, 5.0),
551                Point::new(5.0, 5.0),
552                Point::new(5.0, 1.0),
553            ]]
554        )
555    }
556
557    #[test]
558    fn test_multi_line_string_into_polyline() {
559        let multiline_string = geo_types::MultiLineString(vec![
560            LineString::<f64>(vec![
561                Coordinate { x: 1.0, y: 5.0 },
562                Coordinate { x: 5.0, y: 5.0 },
563                Coordinate { x: 5.0, y: 1.0 },
564            ]),
565            LineString::<f64>(vec![
566                Coordinate { x: 1.0, y: 5.0 },
567                Coordinate { x: 1.0, y: 1.0 },
568            ]),
569        ]);
570
571        let expected_polyline_z = PolylineZ::with_parts(vec![
572            vec![
573                PointZ::new(1.0, 5.0, 0.0, NO_DATA),
574                PointZ::new(5.0, 5.0, 0.0, NO_DATA),
575                PointZ::new(5.0, 1.0, 0.0, NO_DATA),
576            ],
577            vec![
578                PointZ::new(1.0, 5.0, 0.0, NO_DATA),
579                PointZ::new(1.0, 1.0, 0.0, NO_DATA),
580            ],
581        ]);
582
583        let polyline_z: PolylineZ = multiline_string.into();
584        assert_eq!(polyline_z, expected_polyline_z);
585    }
586}