shapefile_gbk/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::{Error, ShapeType};
12use super::{EsriShape, HasShapeType, WritableShape};
13use super::{Point, PointM, PointZ};
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<(), shapefile::Error>{
35/// let mut polylines = shapefile::read_shapes_as::<_, shapefile::Polyline>("tests/data/line.shp")?;
36/// let geo_polyline: geo_types::MultiLineString<f64> = polylines.pop().unwrap().into();
37/// let polyline = 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 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 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).map(|rdr| Self {
185                bbox: rdr.bbox,
186                parts: rdr.parts,
187            })
188        }
189    }
190}
191
192impl WritableShape for Polyline {
193    fn size_in_bytes(&self) -> usize {
194        let mut size = 0usize;
195        size += 4 * size_of::<f64>();
196        size += size_of::<i32>();
197        size += size_of::<i32>();
198        size += size_of::<i32>() * self.parts.len();
199        size += 2 * size_of::<f64>() * self.total_point_count();
200        size
201    }
202
203    fn write_to<T: Write>(&self, dest: &mut T) -> Result<(), Error> {
204        let parts_iter = self.parts.iter().map(|part| part.as_slice());
205        let writer = MultiPartShapeWriter::new(&self.bbox, parts_iter, dest);
206        writer.write_point_shape()?;
207        Ok(())
208    }
209}
210
211impl EsriShape for Polyline {
212    fn x_range(&self) -> [f64; 2] {
213        self.bbox.x_range()
214    }
215
216    fn y_range(&self) -> [f64; 2] {
217        self.bbox.y_range()
218    }
219}
220
221/*
222 * PolylineM
223 */
224
225/// Specialization of the `GenericPolyline` struct to represent a `PolylineM` shape
226/// ( collection of [PointM](../point/struct.PointM.html))
227pub type PolylineM = GenericPolyline<PointM>;
228
229impl PolylineM {
230    pub(crate) fn size_of_record(num_points: i32, num_parts: i32, is_m_used: bool) -> usize {
231        let mut size = Polyline::size_of_record(num_points, num_parts);
232        if is_m_used {
233            size += 2 * size_of::<f64>(); // MRange
234            size += num_points as usize * size_of::<f64>(); // M
235        }
236        size
237    }
238}
239
240impl fmt::Display for PolylineM {
241    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
242        write!(f, "PolylineM({} parts)", self.parts.len())
243    }
244}
245
246impl HasShapeType for PolylineM {
247    fn shapetype() -> ShapeType {
248        ShapeType::PolylineM
249    }
250}
251
252impl ConcreteReadableShape for PolylineM {
253    fn read_shape_content<T: Read>(source: &mut T, record_size: i32) -> Result<Self, Error> {
254        let rdr = MultiPartShapeReader::<PointM, T>::new(source)?;
255
256        let record_size_with_m = Self::size_of_record(rdr.num_points, rdr.num_parts, true) as i32;
257        let record_size_without_m =
258            Self::size_of_record(rdr.num_points, rdr.num_parts, false) as i32;
259
260        if (record_size != record_size_with_m) && (record_size != record_size_without_m) {
261            Err(Error::InvalidShapeRecordSize)
262        } else {
263            rdr.read_xy()
264                .and_then(|rdr| rdr.read_ms_if(record_size == record_size_with_m))
265                .map_err(Error::IoError)
266                .map(|rdr| Self {
267                    bbox: rdr.bbox,
268                    parts: rdr.parts,
269                })
270        }
271    }
272}
273
274impl WritableShape for PolylineM {
275    fn size_in_bytes(&self) -> usize {
276        let mut size = 0_usize;
277        size += size_of::<f64>() * 4;
278        size += size_of::<i32>(); // num parts
279        size += size_of::<i32>(); //num points
280        size += size_of::<i32>() * self.parts.len();
281        size += 3 * size_of::<f64>() * self.total_point_count();
282        size += 2 * size_of::<f64>();
283        size
284    }
285
286    fn write_to<T: Write>(&self, dest: &mut T) -> Result<(), Error> {
287        let parts_iter = self.parts.iter().map(|part| part.as_slice());
288        let writer = MultiPartShapeWriter::new(&self.bbox, parts_iter, dest);
289        writer.write_point_m_shape()?;
290        Ok(())
291    }
292}
293
294impl EsriShape for PolylineM {
295    fn x_range(&self) -> [f64; 2] {
296        self.bbox.x_range()
297    }
298
299    fn y_range(&self) -> [f64; 2] {
300        self.bbox.y_range()
301    }
302
303    fn m_range(&self) -> [f64; 2] {
304        self.bbox.m_range()
305    }
306}
307
308/*
309 * PolylineZ
310 */
311
312/// Specialization of the `GenericPolyline` struct to represent a `PolylineZ` shape
313/// ( collection of [PointZ](../point/struct.PointZ.html))
314pub type PolylineZ = GenericPolyline<PointZ>;
315
316impl PolylineZ {
317    pub(crate) fn size_of_record(num_points: i32, num_parts: i32, is_m_used: bool) -> usize {
318        let mut size = Polyline::size_of_record(num_points, num_parts);
319        size += 2 * size_of::<f64>(); // ZRange
320        size += num_points as usize * size_of::<f64>(); // Z
321        if is_m_used {
322            size += 2 * size_of::<f64>(); // MRange
323            size += num_points as usize * size_of::<f64>(); // M
324        }
325        size
326    }
327}
328
329impl fmt::Display for PolylineZ {
330    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
331        write!(f, "PolylineZ({} parts)", self.parts.len())
332    }
333}
334
335impl HasShapeType for PolylineZ {
336    fn shapetype() -> ShapeType {
337        ShapeType::PolylineZ
338    }
339}
340
341impl ConcreteReadableShape for PolylineZ {
342    fn read_shape_content<T: Read>(source: &mut T, record_size: i32) -> Result<Self, Error> {
343        let rdr = MultiPartShapeReader::<PointZ, T>::new(source)?;
344
345        let record_size_with_m = Self::size_of_record(rdr.num_points, rdr.num_parts, true) as i32;
346        let record_size_without_m =
347            Self::size_of_record(rdr.num_points, rdr.num_parts, false) as i32;
348
349        if (record_size != record_size_with_m) && (record_size != record_size_without_m) {
350            Err(Error::InvalidShapeRecordSize)
351        } else {
352            rdr.read_xy()
353                .and_then(|rdr| rdr.read_zs())
354                .and_then(|rdr| rdr.read_ms_if(record_size == record_size_with_m))
355                .map_err(Error::IoError)
356                .map(|rdr| Self {
357                    bbox: rdr.bbox,
358                    parts: rdr.parts,
359                })
360        }
361    }
362}
363
364impl WritableShape for PolylineZ {
365    fn size_in_bytes(&self) -> usize {
366        let mut size = 0_usize;
367        size += size_of::<f64>() * 4;
368        size += size_of::<i32>(); // num parts
369        size += size_of::<i32>(); //num points
370        size += size_of::<i32>() * self.parts.len();
371        size += 4 * size_of::<f64>() * self.total_point_count();
372        size += 2 * size_of::<f64>();
373        size += 2 * size_of::<f64>();
374        size
375    }
376
377    fn write_to<T: Write>(&self, dest: &mut T) -> Result<(), Error> {
378        let parts_iter = self.parts.iter().map(|part| part.as_slice());
379        let writer = MultiPartShapeWriter::new(&self.bbox, parts_iter, dest);
380        writer.write_point_z_shape()?;
381        Ok(())
382    }
383}
384
385impl EsriShape for PolylineZ {
386    fn x_range(&self) -> [f64; 2] {
387        self.bbox.x_range()
388    }
389
390    fn y_range(&self) -> [f64; 2] {
391        self.bbox.y_range()
392    }
393
394    fn z_range(&self) -> [f64; 2] {
395        self.bbox.z_range()
396    }
397
398    fn m_range(&self) -> [f64; 2] {
399        self.bbox.m_range()
400    }
401}
402
403#[cfg(feature = "geo-types")]
404impl<PointType> From<GenericPolyline<PointType>> for geo_types::MultiLineString<f64>
405where
406    PointType: Copy,
407    geo_types::Coordinate<f64>: From<PointType>,
408{
409    fn from(polyline: GenericPolyline<PointType>) -> Self {
410        let mut lines = Vec::<geo_types::LineString<f64>>::with_capacity(polyline.parts().len());
411
412        for points in polyline.parts {
413            let line: Vec<geo_types::Coordinate<f64>> = points
414                .into_iter()
415                .map(geo_types::Coordinate::<f64>::from)
416                .collect();
417            lines.push(line.into());
418        }
419        geo_types::MultiLineString::<f64>::from_iter(lines.into_iter())
420    }
421}
422
423#[cfg(feature = "geo-types")]
424impl<PointType> From<geo_types::Line<f64>> for GenericPolyline<PointType>
425where
426    PointType: From<geo_types::Point<f64>> + ShrinkablePoint + GrowablePoint + Copy,
427{
428    fn from(line: geo_types::Line<f64>) -> Self {
429        let (p1, p2) = line.points();
430        Self::new(vec![PointType::from(p1), PointType::from(p2)])
431    }
432}
433
434#[cfg(feature = "geo-types")]
435impl<PointType> From<geo_types::LineString<f64>> for GenericPolyline<PointType>
436where
437    PointType: From<geo_types::Coordinate<f64>> + ShrinkablePoint + GrowablePoint + Copy,
438{
439    fn from(line: geo_types::LineString<f64>) -> Self {
440        let points: Vec<PointType> = line.into_iter().map(PointType::from).collect();
441        Self::new(points)
442    }
443}
444
445#[cfg(feature = "geo-types")]
446impl<PointType> From<geo_types::MultiLineString<f64>> for GenericPolyline<PointType>
447where
448    PointType: From<geo_types::Coordinate<f64>> + ShrinkablePoint + GrowablePoint + Copy,
449{
450    fn from(mls: geo_types::MultiLineString<f64>) -> Self {
451        let mut parts = Vec::<Vec<PointType>>::with_capacity(mls.0.len());
452        for linestring in mls.0.into_iter() {
453            parts.push(linestring.into_iter().map(PointType::from).collect());
454        }
455        Self::with_parts(parts)
456    }
457}
458
459#[cfg(test)]
460mod tests {
461    use super::*;
462
463    #[test]
464    #[should_panic(expected = "Polylines parts must have at least 2 points")]
465    fn test_polyline_new_less_than_2_points() {
466        let _polyline = Polyline::new(vec![Point::new(1.0, 1.0)]);
467    }
468
469    #[test]
470    #[should_panic(expected = "Polylines parts must have at least 2 points")]
471    fn test_polyline_with_parts_less_than_2_points() {
472        let _polyline = Polyline::with_parts(vec![
473            vec![Point::new(1.0, 1.0), Point::new(2.0, 2.0)],
474            vec![Point::new(1.0, 1.0)],
475        ]);
476    }
477}
478
479#[cfg(test)]
480#[cfg(feature = "geo-types")]
481mod test_geo_types_conversions {
482    use super::*;
483    use crate::NO_DATA;
484    use crate::{PointM, PolylineM};
485    use geo_types::{Coordinate, LineString, MultiLineString};
486
487    #[test]
488    fn test_polyline_into_multiline_string() {
489        let polyline_m = PolylineM::with_parts(vec![
490            vec![
491                PointM::new(1.0, 5.0, 0.0),
492                PointM::new(5.0, 5.0, NO_DATA),
493                PointM::new(5.0, 1.0, 3.0),
494            ],
495            vec![PointM::new(1.0, 5.0, 0.0), PointM::new(1.0, 1.0, 0.0)],
496        ]);
497
498        let multiline_string: MultiLineString<f64> = polyline_m.into();
499
500        let expected_multiline = geo_types::MultiLineString(vec![
501            LineString::<f64>(vec![
502                Coordinate { x: 1.0, y: 5.0 },
503                Coordinate { x: 5.0, y: 5.0 },
504                Coordinate { x: 5.0, y: 1.0 },
505            ]),
506            LineString::<f64>(vec![
507                Coordinate { x: 1.0, y: 5.0 },
508                Coordinate { x: 1.0, y: 1.0 },
509            ]),
510        ]);
511        assert_eq!(multiline_string, expected_multiline);
512    }
513
514    #[test]
515    fn test_line_into_polyline() {
516        let line = geo_types::Line::new(
517            Coordinate { x: 2.0, y: 3.0 },
518            Coordinate { x: 6.0, y: -6.0 },
519        );
520        let polyline: PolylineZ = line.into();
521
522        assert_eq!(
523            polyline.parts,
524            vec![vec![
525                PointZ::new(2.0, 3.0, 0.0, NO_DATA),
526                PointZ::new(6.0, -6.0, 0.0, NO_DATA)
527            ]]
528        );
529    }
530
531    #[test]
532    fn test_linestring_into_polyline() {
533        let linestring = LineString::from(vec![
534            Coordinate { x: 1.0, y: 5.0 },
535            Coordinate { x: 5.0, y: 5.0 },
536            Coordinate { x: 5.0, y: 1.0 },
537        ]);
538
539        let polyline: Polyline = linestring.into();
540        assert_eq!(
541            polyline.parts,
542            vec![vec![
543                Point::new(1.0, 5.0),
544                Point::new(5.0, 5.0),
545                Point::new(5.0, 1.0),
546            ]]
547        )
548    }
549
550    #[test]
551    fn test_multi_line_string_into_polyline() {
552        let multiline_string = geo_types::MultiLineString(vec![
553            LineString::<f64>(vec![
554                Coordinate { x: 1.0, y: 5.0 },
555                Coordinate { x: 5.0, y: 5.0 },
556                Coordinate { x: 5.0, y: 1.0 },
557            ]),
558            LineString::<f64>(vec![
559                Coordinate { x: 1.0, y: 5.0 },
560                Coordinate { x: 1.0, y: 1.0 },
561            ]),
562        ]);
563
564        let expected_polyline_z = PolylineZ::with_parts(vec![
565            vec![
566                PointZ::new(1.0, 5.0, 0.0, NO_DATA),
567                PointZ::new(5.0, 5.0, 0.0, NO_DATA),
568                PointZ::new(5.0, 1.0, 0.0, NO_DATA),
569            ],
570            vec![
571                PointZ::new(1.0, 5.0, 0.0, NO_DATA),
572                PointZ::new(1.0, 1.0, 0.0, NO_DATA),
573            ],
574        ]);
575
576        let polyline_z: PolylineZ = multiline_string.into();
577        assert_eq!(polyline_z, expected_polyline_z);
578    }
579}