1use 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#[derive(Debug, Clone, PartialEq)]
47pub struct GenericPolyline<PointType> {
48 pub(crate) bbox: GenericBBox<PointType>,
49 pub(crate) parts: Vec<Vec<PointType>>,
50}
51
52impl<PointType: ShrinkablePoint + GrowablePoint + Copy> GenericPolyline<PointType> {
54 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 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 #[inline]
121 pub fn bbox(&self) -> &GenericBBox<PointType> {
122 &self.bbox
123 }
124
125 #[inline]
127 pub fn parts(&self) -> &Vec<Vec<PointType>> {
128 &self.parts
129 }
130
131 #[inline]
133 pub fn part(&self, index: usize) -> Option<&Vec<PointType>> {
134 self.parts.get(index)
135 }
136
137 #[inline]
139 pub fn into_inner(self) -> Vec<Vec<PointType>> {
140 self.parts
141 }
142
143 #[inline]
145 pub fn total_point_count(&self) -> usize {
146 self.parts.iter().map(|part| part.len()).sum()
147 }
148}
149
150pub 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>(); size += size_of::<i32>(); size += size_of::<i32>(); 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
223pub 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>(); size += num_points as usize * size_of::<f64>(); }
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>(); size += size_of::<i32>(); 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
312pub 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>(); size += num_points as usize * size_of::<f64>(); if is_m_used {
326 size += 2 * size_of::<f64>(); size += num_points as usize * size_of::<f64>(); }
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>(); size += size_of::<i32>(); 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}