• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 6252204497

20 Sep 2023 04:53PM UTC coverage: 89.507% (-0.4%) from 89.876%
6252204497

push

github

web-flow
Merge pull request #879 from geo-engine/api-type-separation

Api-type-separation

2926 of 2926 new or added lines in 60 files covered. (100.0%)

109175 of 121974 relevant lines covered (89.51%)

59539.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.49
/datatypes/src/primitives/multi_line_string.rs
1
use std::convert::TryFrom;
2

3
use arrow::array::BooleanArray;
4
use arrow::error::ArrowError;
5
use fallible_iterator::FallibleIterator;
6
use float_cmp::{ApproxEq, F64Margin};
7
use geo::algorithm::intersects::Intersects;
8
use postgres_types::{FromSql, ToSql};
9
use serde::{Deserialize, Serialize};
10
use snafu::ensure;
11
use wkt::{ToWkt, Wkt};
12

13
use crate::collections::VectorDataType;
14
use crate::error::Error;
15
use crate::primitives::{
16
    error, BoundingBox2D, GeometryRef, MultiPoint, PrimitivesError, TypedGeometry,
17
};
18
use crate::primitives::{Coordinate2D, Geometry};
19
use crate::util::arrow::{downcast_array, padded_buffer_size, ArrowTyped};
20
use crate::util::Result;
21

22
/// A trait that allows a common access to lines of `MultiLineString`s and its references
23
pub trait MultiLineStringAccess {
24
    type L: AsRef<[Coordinate2D]>;
25
    fn lines(&self) -> &[Self::L];
26
}
27

28
/// A representation of a simple feature multi line string
29
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
11✔
30
pub struct MultiLineString {
31
    coordinates: Vec<Vec<Coordinate2D>>,
32
}
33

34
impl MultiLineString {
35
    pub fn new(coordinates: Vec<Vec<Coordinate2D>>) -> Result<Self> {
4,080✔
36
        ensure!(
4,080✔
37
            !coordinates.is_empty() && coordinates.iter().all(|c| c.len() >= 2),
10,834✔
38
            error::UnallowedEmpty
×
39
        );
40

41
        Ok(Self::new_unchecked(coordinates))
4,080✔
42
    }
4,080✔
43

44
    pub(crate) fn new_unchecked(coordinates: Vec<Vec<Coordinate2D>>) -> Self {
4,090✔
45
        Self { coordinates }
4,090✔
46
    }
4,090✔
47
}
48

49
impl MultiLineStringAccess for MultiLineString {
50
    type L = Vec<Coordinate2D>;
51
    fn lines(&self) -> &[Vec<Coordinate2D>] {
4,077✔
52
        &self.coordinates
4,077✔
53
    }
4,077✔
54
}
55

56
impl Geometry for MultiLineString {
57
    const DATA_TYPE: VectorDataType = VectorDataType::MultiLineString;
58

59
    fn intersects_bbox(&self, bbox: &BoundingBox2D) -> bool {
×
60
        let geo::MultiLineString::<f64>(geo_line_strings) = self.into();
×
61
        let geo_rect: geo::Rect<f64> = bbox.into();
×
62

63
        for line_string in geo_line_strings {
×
64
            for line in line_string.lines() {
×
65
                if line.intersects(&geo_rect) {
×
66
                    return true;
×
67
                }
×
68
            }
69
        }
70

71
        false
×
72
    }
×
73
}
74

75
impl From<&MultiLineString> for geo::MultiLineString<f64> {
76
    fn from(geometry: &MultiLineString) -> geo::MultiLineString<f64> {
1✔
77
        let line_strings = geometry
1✔
78
            .coordinates
1✔
79
            .iter()
1✔
80
            .map(|coordinates| {
3✔
81
                let geo_coordinates = coordinates.iter().map(Into::into).collect();
3✔
82
                geo::LineString(geo_coordinates)
3✔
83
            })
3✔
84
            .collect();
1✔
85
        geo::MultiLineString(line_strings)
1✔
86
    }
1✔
87
}
88

89
impl From<geo::MultiLineString<f64>> for MultiLineString {
90
    fn from(geo_geometry: geo::MultiLineString<f64>) -> MultiLineString {
3✔
91
        let coordinates = geo_geometry
3✔
92
            .0
3✔
93
            .into_iter()
3✔
94
            .map(|geo_line_string| {
5✔
95
                geo_line_string
5✔
96
                    .0
5✔
97
                    .into_iter()
5✔
98
                    .map(Into::into)
5✔
99
                    .collect::<Vec<_>>()
5✔
100
            })
5✔
101
            .collect();
3✔
102
        MultiLineString::new_unchecked(coordinates)
3✔
103
    }
3✔
104
}
105

106
impl TryFrom<TypedGeometry> for MultiLineString {
107
    type Error = Error;
108

109
    fn try_from(value: TypedGeometry) -> Result<Self, Self::Error> {
110
        if let TypedGeometry::MultiLineString(geometry) = value {
×
111
            Ok(geometry)
×
112
        } else {
113
            Err(PrimitivesError::InvalidConversion.into())
×
114
        }
115
    }
×
116
}
117

118
impl AsRef<[Vec<Coordinate2D>]> for MultiLineString {
119
    fn as_ref(&self) -> &[Vec<Coordinate2D>] {
19✔
120
        &self.coordinates
19✔
121
    }
19✔
122
}
123

124
impl ApproxEq for &MultiLineString {
125
    type Margin = F64Margin;
126

127
    fn approx_eq<M: Into<Self::Margin>>(self, other: Self, margin: M) -> bool {
6✔
128
        let m = margin.into();
6✔
129
        self.lines().len() == other.lines().len()
6✔
130
            && self
5✔
131
                .lines()
5✔
132
                .iter()
5✔
133
                .zip(other.lines().iter())
5✔
134
                .all(|(line_a, line_b)| line_a.len() == line_b.len() && line_a.approx_eq(line_b, m))
9✔
135
    }
6✔
136
}
137

138
impl ToSql for MultiLineString {
139
    fn to_sql(
2✔
140
        &self,
2✔
141
        ty: &postgres_types::Type,
2✔
142
        w: &mut bytes::BytesMut,
2✔
143
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
2✔
144
        let postgres_types::Kind::Array(member_type) = ty.kind() else {
2✔
145
            panic!("expected array type");
×
146
        };
147

148
        let dimension = postgres_protocol::types::ArrayDimension {
2✔
149
            len: self.coordinates.len() as i32,
2✔
150
            lower_bound: 1, // arrays are one-indexed
2✔
151
        };
2✔
152

2✔
153
        postgres_protocol::types::array_to_sql(
2✔
154
            Some(dimension),
2✔
155
            member_type.oid(),
2✔
156
            self.coordinates.iter(),
2✔
157
            |coordinates, w| {
2✔
158
                postgres_protocol::types::path_to_sql(
4✔
159
                    false,
4✔
160
                    coordinates.iter().map(|p| (p.x, p.y)),
8✔
161
                    w,
4✔
162
                )?;
4✔
163

164
                Ok(postgres_protocol::IsNull::No)
4✔
165
            },
4✔
166
            w,
2✔
167
        )?;
2✔
168

169
        Ok(postgres_types::IsNull::No)
2✔
170
    }
2✔
171

172
    fn accepts(ty: &postgres_types::Type) -> bool {
1✔
173
        let postgres_types::Kind::Array(inner_type) = ty.kind() else {
1✔
174
            return false;
×
175
        };
176

177
        matches!(inner_type, &postgres_types::Type::PATH)
1✔
178
    }
1✔
179

180
    postgres_types::to_sql_checked!();
181
}
182

183
impl<'a> FromSql<'a> for MultiLineString {
184
    fn from_sql(
2✔
185
        _ty: &postgres_types::Type,
2✔
186
        raw: &'a [u8],
2✔
187
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
2✔
188
        let array = postgres_protocol::types::array_from_sql(raw)?;
2✔
189
        if array.dimensions().count()? > 1 {
2✔
190
            return Err("array contains too many dimensions".into());
×
191
        }
2✔
192

193
        let coordinates = array
2✔
194
            .values()
2✔
195
            .map(|raw| {
4✔
196
                let Some(raw) = raw else {
4✔
197
                    return Err("array contains NULL values".into());
×
198
                };
199
                let path = postgres_protocol::types::path_from_sql(raw)?;
4✔
200

201
                let coordinates = path
4✔
202
                    .points()
4✔
203
                    .map(|point| {
8✔
204
                        Ok(Coordinate2D {
8✔
205
                            x: point.x(),
8✔
206
                            y: point.y(),
8✔
207
                        })
8✔
208
                    })
8✔
209
                    .collect()?;
4✔
210
                Ok(coordinates)
4✔
211
            })
4✔
212
            .collect()?;
2✔
213

214
        Ok(Self { coordinates })
2✔
215
    }
2✔
216

217
    fn accepts(ty: &postgres_types::Type) -> bool {
158✔
218
        let postgres_types::Kind::Array(inner_type) = ty.kind() else {
158✔
219
            return false;
×
220
        };
221

222
        matches!(inner_type, &postgres_types::Type::PATH)
158✔
223
    }
158✔
224
}
225

226
impl ArrowTyped for MultiLineString {
227
    type ArrowArray = arrow::array::ListArray;
228
    type ArrowBuilder = arrow::array::ListBuilder<
229
        arrow::array::ListBuilder<<Coordinate2D as ArrowTyped>::ArrowBuilder>,
230
    >;
231

232
    fn arrow_data_type() -> arrow::datatypes::DataType {
78✔
233
        MultiPoint::arrow_list_data_type()
78✔
234
    }
78✔
235

236
    fn estimate_array_memory_size(builder: &mut Self::ArrowBuilder) -> usize {
128✔
237
        let static_size = std::mem::size_of::<Self::ArrowArray>()
128✔
238
            + std::mem::size_of::<<MultiPoint as ArrowTyped>::ArrowArray>();
128✔
239

128✔
240
        let feature_offset_bytes_size = std::mem::size_of_val(builder.offsets_slice());
128✔
241

128✔
242
        let line_builder = builder.values();
128✔
243

128✔
244
        let line_offset_bytes_size = std::mem::size_of_val(line_builder.offsets_slice());
128✔
245

128✔
246
        let coordinates_builder = line_builder.values();
128✔
247

128✔
248
        let coords_size = Coordinate2D::estimate_array_memory_size(coordinates_builder);
128✔
249

128✔
250
        static_size
128✔
251
            + coords_size
128✔
252
            + padded_buffer_size(line_offset_bytes_size, 64)
128✔
253
            + padded_buffer_size(feature_offset_bytes_size, 64)
128✔
254
    }
128✔
255

256
    fn arrow_builder(capacity: usize) -> Self::ArrowBuilder {
149✔
257
        let minimal_number_of_coordinates = 2 * capacity; // at least 2 coordinates per line string
149✔
258
        let coordinate_builder = Coordinate2D::arrow_builder(minimal_number_of_coordinates);
149✔
259
        let line_string_builder = arrow::array::ListBuilder::new(coordinate_builder);
149✔
260
        arrow::array::ListBuilder::new(line_string_builder) // multi line strings = lists of line strings
149✔
261
    }
149✔
262

263
    fn concat(a: &Self::ArrowArray, b: &Self::ArrowArray) -> Result<Self::ArrowArray, ArrowError> {
1✔
264
        use arrow::array::{Array, FixedSizeListArray, Float64Array, ListArray};
1✔
265

1✔
266
        let mut multi_line_builder = Self::arrow_builder(a.len() + b.len());
1✔
267

268
        for multi_lines in &[a, b] {
2✔
269
            for multi_line_index in 0..multi_lines.len() {
2✔
270
                let line_builder = multi_line_builder.values();
2✔
271

2✔
272
                let lines_ref = multi_lines.value(multi_line_index);
2✔
273
                let lines = downcast_array::<ListArray>(&lines_ref);
2✔
274

275
                for line_index in 0..lines.len() {
3✔
276
                    let coordinate_builder = line_builder.values();
3✔
277

3✔
278
                    let coordinates_ref = lines.value(line_index);
3✔
279
                    let coordinates = downcast_array::<FixedSizeListArray>(&coordinates_ref);
3✔
280

281
                    for coordinate_index in 0..coordinates.len() {
8✔
282
                        let floats_ref = coordinates.value(coordinate_index);
8✔
283
                        let floats: &Float64Array = downcast_array(&floats_ref);
8✔
284

8✔
285
                        coordinate_builder.values().append_slice(floats.values());
8✔
286

8✔
287
                        coordinate_builder.append(true);
8✔
288
                    }
8✔
289

290
                    line_builder.append(true);
3✔
291
                }
292

293
                multi_line_builder.append(true);
2✔
294
            }
295
        }
296

297
        Ok(multi_line_builder.finish_cloned())
1✔
298
    }
1✔
299

300
    fn filter(
4✔
301
        multi_lines: &Self::ArrowArray,
4✔
302
        filter_array: &BooleanArray,
4✔
303
    ) -> Result<Self::ArrowArray, ArrowError> {
4✔
304
        use arrow::array::{Array, FixedSizeListArray, Float64Array, ListArray};
4✔
305

4✔
306
        let mut multi_line_builder = Self::arrow_builder(0);
4✔
307

308
        for multi_line_index in 0..multi_lines.len() {
8✔
309
            if !filter_array.value(multi_line_index) {
8✔
310
                continue;
2✔
311
            }
6✔
312

6✔
313
            let line_builder = multi_line_builder.values();
6✔
314

6✔
315
            let lines_ref = multi_lines.value(multi_line_index);
6✔
316
            let lines = downcast_array::<ListArray>(&lines_ref);
6✔
317

318
            for line_index in 0..lines.len() {
7✔
319
                let coordinate_builder = line_builder.values();
7✔
320

7✔
321
                let coordinates_ref = lines.value(line_index);
7✔
322
                let coordinates = downcast_array::<FixedSizeListArray>(&coordinates_ref);
7✔
323

324
                for coordinate_index in 0..coordinates.len() {
19✔
325
                    let floats_ref = coordinates.value(coordinate_index);
19✔
326
                    let floats: &Float64Array = downcast_array(&floats_ref);
19✔
327

19✔
328
                    coordinate_builder.values().append_slice(floats.values());
19✔
329

19✔
330
                    coordinate_builder.append(true);
19✔
331
                }
19✔
332

333
                line_builder.append(true);
7✔
334
            }
335

336
            multi_line_builder.append(true);
6✔
337
        }
338

339
        Ok(multi_line_builder.finish_cloned())
4✔
340
    }
4✔
341

342
    fn from_vec(multi_line_strings: Vec<Self>) -> Result<Self::ArrowArray, ArrowError>
10✔
343
    where
10✔
344
        Self: Sized,
10✔
345
    {
10✔
346
        let mut builder = Self::arrow_builder(multi_line_strings.len());
10✔
347
        for multi_line_string in multi_line_strings {
29✔
348
            let line_string_builder = builder.values();
19✔
349

350
            for line_string in multi_line_string.as_ref() {
25✔
351
                let coordinate_builder = line_string_builder.values();
25✔
352

353
                for coordinate in line_string {
89✔
354
                    let float_builder = coordinate_builder.values();
64✔
355
                    float_builder.append_value(coordinate.x);
64✔
356
                    float_builder.append_value(coordinate.y);
64✔
357
                    coordinate_builder.append(true);
64✔
358
                }
64✔
359

360
                line_string_builder.append(true);
25✔
361
            }
362

363
            builder.append(true);
19✔
364
        }
365

366
        Ok(builder.finish_cloned())
10✔
367
    }
10✔
368
}
369

370
#[derive(Debug, PartialEq)]
×
371
pub struct MultiLineStringRef<'g> {
372
    point_coordinates: Vec<&'g [Coordinate2D]>,
373
}
374

375
impl<'r> GeometryRef for MultiLineStringRef<'r> {
376
    type GeometryType = MultiLineString;
377

378
    fn as_geometry(&self) -> Self::GeometryType {
×
379
        self.into()
×
380
    }
×
381

382
    fn bbox(&self) -> Option<BoundingBox2D> {
×
383
        self.bbox()
×
384
    }
×
385
}
386

387
impl<'g> MultiLineStringRef<'g> {
388
    pub fn new(coordinates: Vec<&'g [Coordinate2D]>) -> Result<Self> {
1✔
389
        ensure!(!coordinates.is_empty(), error::UnallowedEmpty);
1✔
390

391
        Ok(Self::new_unchecked(coordinates))
1✔
392
    }
1✔
393

394
    pub(crate) fn new_unchecked(coordinates: Vec<&'g [Coordinate2D]>) -> Self {
25✔
395
        Self {
25✔
396
            point_coordinates: coordinates,
25✔
397
        }
25✔
398
    }
25✔
399

400
    pub fn bbox(&self) -> Option<BoundingBox2D> {
×
401
        self.lines().iter().fold(None, |bbox, line| {
×
402
            let lbox = BoundingBox2D::from_coord_ref_iter(line.iter());
×
403
            match (bbox, lbox) {
×
404
                (None, Some(lbox)) => Some(lbox),
×
405
                (Some(bbox), Some(lbox)) => Some(bbox.union(&lbox)),
×
406
                (bbox, None) => bbox,
×
407
            }
408
        })
×
409
    }
×
410
}
411

412
impl<'g> MultiLineStringAccess for MultiLineStringRef<'g> {
413
    type L = &'g [Coordinate2D];
414
    fn lines(&self) -> &[&'g [Coordinate2D]] {
13✔
415
        &self.point_coordinates
13✔
416
    }
13✔
417
}
418

419
impl<'r> ToWkt<f64> for MultiLineStringRef<'r> {
420
    fn to_wkt(&self) -> Wkt<f64> {
1✔
421
        let line_strings = self.lines();
1✔
422
        let mut multi_line_string =
1✔
423
            wkt::types::MultiLineString(Vec::with_capacity(line_strings.len()));
1✔
424

425
        for line_string in line_strings {
4✔
426
            let mut line_strings = wkt::types::LineString(Vec::with_capacity(line_string.len()));
3✔
427

428
            for coord in *line_string {
10✔
429
                line_strings.0.push(coord.into());
7✔
430
            }
7✔
431

432
            multi_line_string.0.push(line_strings);
3✔
433
        }
434

435
        Wkt {
1✔
436
            item: wkt::Geometry::MultiLineString(multi_line_string),
1✔
437
        }
1✔
438
    }
1✔
439
}
440

441
impl<'g> From<MultiLineStringRef<'g>> for geojson::Geometry {
442
    fn from(geometry: MultiLineStringRef<'g>) -> geojson::Geometry {
2✔
443
        geojson::Geometry::new(match geometry.point_coordinates.len() {
2✔
444
            1 => {
445
                let coordinates = geometry.point_coordinates[0];
1✔
446
                let positions = coordinates.iter().map(|c| vec![c.x, c.y]).collect();
3✔
447
                geojson::Value::LineString(positions)
1✔
448
            }
449
            _ => geojson::Value::MultiLineString(
1✔
450
                geometry
1✔
451
                    .point_coordinates
1✔
452
                    .iter()
1✔
453
                    .map(|&coordinates| coordinates.iter().map(|c| vec![c.x, c.y]).collect())
5✔
454
                    .collect(),
1✔
455
            ),
1✔
456
        })
457
    }
2✔
458
}
459

460
impl<'g> From<MultiLineStringRef<'g>> for MultiLineString {
461
    fn from(multi_line_string_ref: MultiLineStringRef<'g>) -> Self {
7✔
462
        MultiLineString::from(&multi_line_string_ref)
7✔
463
    }
7✔
464
}
465

466
impl<'g> From<&MultiLineStringRef<'g>> for MultiLineString {
467
    fn from(multi_line_string_ref: &MultiLineStringRef<'g>) -> Self {
7✔
468
        MultiLineString::new_unchecked(
7✔
469
            multi_line_string_ref
7✔
470
                .point_coordinates
7✔
471
                .iter()
7✔
472
                .copied()
7✔
473
                .map(ToOwned::to_owned)
7✔
474
                .collect(),
7✔
475
        )
7✔
476
    }
7✔
477
}
478

479
impl<'g> From<&'g MultiLineString> for MultiLineStringRef<'g> {
480
    fn from(multi_line_string: &'g MultiLineString) -> Self {
1✔
481
        MultiLineStringRef::new_unchecked(
1✔
482
            multi_line_string
1✔
483
                .lines()
1✔
484
                .iter()
1✔
485
                .map(AsRef::as_ref)
1✔
486
                .collect::<Vec<_>>(),
1✔
487
        )
1✔
488
    }
1✔
489
}
490

491
impl<'g> From<&MultiLineStringRef<'g>> for geo::MultiLineString<f64> {
492
    fn from(geometry: &MultiLineStringRef<'g>) -> Self {
2✔
493
        let line_strings = geometry
2✔
494
            .point_coordinates
2✔
495
            .iter()
2✔
496
            .map(|coordinates| {
2✔
497
                let geo_coordinates = coordinates.iter().map(Into::into).collect();
2✔
498
                geo::LineString(geo_coordinates)
2✔
499
            })
2✔
500
            .collect();
2✔
501
        geo::MultiLineString(line_strings)
2✔
502
    }
2✔
503
}
504

505
#[cfg(test)]
506
mod tests {
507
    use super::*;
508
    use arrow::array::{Array, ArrayBuilder};
509
    use float_cmp::approx_eq;
510

511
    #[test]
1✔
512
    fn access() {
1✔
513
        fn aggregate<T: MultiLineStringAccess>(multi_line_string: &T) -> (usize, usize) {
3✔
514
            let number_of_lines = multi_line_string.lines().len();
3✔
515
            let number_of_coordinates = multi_line_string
3✔
516
                .lines()
3✔
517
                .iter()
3✔
518
                .map(AsRef::as_ref)
3✔
519
                .map(<[Coordinate2D]>::len)
3✔
520
                .sum();
3✔
521

3✔
522
            (number_of_lines, number_of_coordinates)
3✔
523
        }
3✔
524

1✔
525
        let coordinates = vec![
1✔
526
            vec![(0.0, 0.1).into(), (1.0, 1.1).into()],
1✔
527
            vec![(3.0, 3.1).into(), (4.0, 4.1).into()],
1✔
528
        ];
1✔
529
        let multi_line_string = MultiLineString::new(coordinates.clone()).unwrap();
1✔
530
        let multi_line_string_ref =
1✔
531
            MultiLineStringRef::new(coordinates.iter().map(AsRef::as_ref).collect()).unwrap();
1✔
532

1✔
533
        assert_eq!(aggregate(&multi_line_string), (2, 4));
1✔
534
        assert_eq!(
1✔
535
            aggregate(&multi_line_string),
1✔
536
            aggregate(&multi_line_string_ref)
1✔
537
        );
1✔
538
    }
1✔
539

540
    #[test]
1✔
541
    fn approx_equal() {
1✔
542
        let a = MultiLineString::new(vec![
1✔
543
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
544
            vec![(0.5, 0.5).into(), (0.6, 0.6).into()],
1✔
545
            vec![(0.6, 0.6).into(), (0.9, 0.9).into()],
1✔
546
        ])
1✔
547
        .unwrap();
1✔
548

1✔
549
        let b = MultiLineString::new(vec![
1✔
550
            vec![(0.099_999_999, 0.1).into(), (0.5, 0.5).into()],
1✔
551
            vec![(0.5, 0.5).into(), (0.6, 0.6).into()],
1✔
552
            vec![(0.6, 0.6).into(), (0.9, 0.9).into()],
1✔
553
        ])
1✔
554
        .unwrap();
1✔
555

1✔
556
        assert!(approx_eq!(&MultiLineString, &a, &b, epsilon = 0.000_001));
1✔
557
    }
1✔
558

559
    #[test]
1✔
560
    fn not_approx_equal_outer_len() {
1✔
561
        let a = MultiLineString::new(vec![
1✔
562
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
563
            vec![(0.5, 0.5).into(), (0.6, 0.6).into()],
1✔
564
            vec![(0.6, 0.6).into(), (0.9, 0.9).into()],
1✔
565
        ])
1✔
566
        .unwrap();
1✔
567

1✔
568
        let b = MultiLineString::new(vec![
1✔
569
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
570
            vec![(0.5, 0.5).into(), (0.6, 0.6).into()],
1✔
571
            vec![(0.6, 0.6).into(), (0.9, 0.9).into()],
1✔
572
            vec![(0.9, 0.9).into(), (123_456_789.9, 123_456_789.9).into()],
1✔
573
        ])
1✔
574
        .unwrap();
1✔
575

1✔
576
        assert!(!approx_eq!(&MultiLineString, &a, &b, F64Margin::default()));
1✔
577
    }
1✔
578

579
    #[test]
1✔
580
    fn not_approx_equal_inner_len() {
1✔
581
        let a = MultiLineString::new(vec![
1✔
582
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
583
            vec![(0.5, 0.5).into(), (0.6, 0.6).into(), (0.7, 0.7).into()],
1✔
584
            vec![(0.7, 0.7).into(), (0.9, 0.9).into()],
1✔
585
        ])
1✔
586
        .unwrap();
1✔
587

1✔
588
        let b = MultiLineString::new(vec![
1✔
589
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
590
            vec![(0.5, 0.5).into(), (0.6, 0.6).into()],
1✔
591
            vec![(0.6, 0.6).into(), (0.7, 0.7).into(), (0.9, 0.9).into()],
1✔
592
        ])
1✔
593
        .unwrap();
1✔
594

1✔
595
        assert!(!approx_eq!(&MultiLineString, &a, &b, F64Margin::default()));
1✔
596
    }
1✔
597

598
    #[test]
1✔
599
    fn test_to_wkt() {
1✔
600
        let a = MultiLineString::new(vec![
1✔
601
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
602
            vec![(0.5, 0.5).into(), (0.6, 0.6).into(), (0.7, 0.7).into()],
1✔
603
            vec![(0.7, 0.7).into(), (0.9, 0.9).into()],
1✔
604
        ])
1✔
605
        .unwrap();
1✔
606

1✔
607
        let a_ref = MultiLineStringRef::from(&a);
1✔
608

1✔
609
        assert_eq!(
1✔
610
            a_ref.wkt_string(),
1✔
611
            "MULTILINESTRING((0.1 0.1,0.5 0.5),(0.5 0.5,0.6 0.6,0.7 0.7),(0.7 0.7,0.9 0.9))"
1✔
612
        );
1✔
613
    }
1✔
614

615
    #[test]
1✔
616
    fn test_to_geo_and_back() {
1✔
617
        let line_string = MultiLineString::new(vec![
1✔
618
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
619
            vec![(0.5, 0.5).into(), (0.6, 0.6).into(), (0.7, 0.7).into()],
1✔
620
            vec![(0.7, 0.7).into(), (0.9, 0.9).into()],
1✔
621
        ])
1✔
622
        .unwrap();
1✔
623

1✔
624
        let geo_line_string = geo::MultiLineString::<f64>::from(&line_string);
1✔
625

1✔
626
        let line_string_back = MultiLineString::from(geo_line_string);
1✔
627

1✔
628
        assert_eq!(line_string, line_string_back);
1✔
629
    }
1✔
630

631
    #[test]
1✔
632
    fn arrow_builder_size() {
1✔
633
        fn push_geometry(
4,032✔
634
            geometries_builder: &mut <MultiLineString as ArrowTyped>::ArrowBuilder,
4,032✔
635
            geometry: &MultiLineString,
4,032✔
636
        ) {
4,032✔
637
            let line_builder = geometries_builder.values();
4,032✔
638

639
            for line in geometry.lines() {
10,752✔
640
                let coordinate_builder = line_builder.values();
10,752✔
641

642
                for coordinate in line {
33,558✔
643
                    coordinate_builder
22,806✔
644
                        .values()
22,806✔
645
                        .append_slice(coordinate.as_ref());
22,806✔
646

22,806✔
647
                    coordinate_builder.append(true);
22,806✔
648
                }
22,806✔
649

650
                line_builder.append(true);
10,752✔
651
            }
652

653
            geometries_builder.append(true);
4,032✔
654
        }
4,032✔
655

656
        for num_multi_lines in 0..64 {
65✔
657
            for capacity in [0, num_multi_lines] {
128✔
658
                let mut builder = MultiLineString::arrow_builder(capacity);
128✔
659

660
                for i in 0..num_multi_lines {
4,032✔
661
                    match i % 3 {
4,032✔
662
                        0 => {
1,386✔
663
                            push_geometry(
1,386✔
664
                                &mut builder,
1,386✔
665
                                &MultiLineString::new(vec![
1,386✔
666
                                    vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1,386✔
667
                                    vec![(0.5, 0.5).into(), (0.6, 0.6).into()],
1,386✔
668
                                    vec![(0.6, 0.6).into(), (0.9, 0.9).into()],
1,386✔
669
                                ])
1,386✔
670
                                .unwrap(),
1,386✔
671
                            );
1,386✔
672
                        }
1,386✔
673
                        1 => {
1,344✔
674
                            push_geometry(
1,344✔
675
                                &mut builder,
1,344✔
676
                                &MultiLineString::new(vec![
1,344✔
677
                                    vec![(0.0, 0.1).into(), (1.0, 1.1).into()],
1,344✔
678
                                    vec![(3.0, 3.1).into(), (4.0, 4.1).into()],
1,344✔
679
                                ])
1,344✔
680
                                .unwrap(),
1,344✔
681
                            );
1,344✔
682
                        }
1,344✔
683
                        2 => {
1,302✔
684
                            push_geometry(
1,302✔
685
                                &mut builder,
1,302✔
686
                                &MultiLineString::new(vec![
1,302✔
687
                                    vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1,302✔
688
                                    vec![(0.5, 0.5).into(), (0.6, 0.6).into(), (0.7, 0.7).into()],
1,302✔
689
                                    vec![(0.7, 0.7).into(), (0.9, 0.9).into()],
1,302✔
690
                                ])
1,302✔
691
                                .unwrap(),
1,302✔
692
                            );
1,302✔
693
                        }
1,302✔
694
                        _ => unreachable!(),
×
695
                    }
696
                }
697

698
                assert_eq!(builder.len(), num_multi_lines);
128✔
699

700
                let builder_byte_size = MultiLineString::estimate_array_memory_size(&mut builder);
128✔
701

128✔
702
                let array = builder.finish_cloned();
128✔
703

128✔
704
                assert_eq!(
128✔
705
                    builder_byte_size,
128✔
706
                    array.get_array_memory_size(),
128✔
707
                    "{num_multi_lines}"
×
708
                );
709
            }
710
        }
711
    }
1✔
712
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc