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::{Error, ShapeType};
12use super::{EsriShape, HasShapeType, WritableShape};
13use super::{Point, PointM, PointZ};
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).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
221pub 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>(); size += num_points as usize * size_of::<f64>(); }
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>(); size += size_of::<i32>(); 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
308pub 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>(); size += num_points as usize * size_of::<f64>(); if is_m_used {
322 size += 2 * size_of::<f64>(); size += num_points as usize * size_of::<f64>(); }
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>(); size += size_of::<i32>(); 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}