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

geo-engine / geoengine / 12865901819

20 Jan 2025 10:22AM UTC coverage: 90.07% (-0.6%) from 90.64%
12865901819

Pull #1008

github

web-flow
Merge fec912348 into de81b44f7
Pull Request #1008: user ctx in ge_test

4294 of 4520 new or added lines in 65 files covered. (95.0%)

787 existing lines in 20 files now uncovered.

127380 of 141423 relevant lines covered (90.07%)

56918.3 hits per line

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

80.65
/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)]
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,078✔
36
        ensure!(
4,078✔
37
            !coordinates.is_empty() && coordinates.iter().all(|c| c.len() >= 2),
10,830✔
38
            error::UnallowedEmpty
×
39
        );
40

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

44
    pub(crate) fn new_unchecked(coordinates: Vec<Vec<Coordinate2D>>) -> Self {
4,088✔
45
        Self { coordinates }
4,088✔
46
    }
4,088✔
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 {
UNCOV
139
    fn to_sql(
×
UNCOV
140
        &self,
×
UNCOV
141
        ty: &postgres_types::Type,
×
UNCOV
142
        w: &mut bytes::BytesMut,
×
UNCOV
143
    ) -> Result<postgres_types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
×
UNCOV
144
        let postgres_types::Kind::Array(member_type) = ty.kind() else {
×
145
            panic!("expected array type");
×
146
        };
147

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

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

UNCOV
164
                Ok(postgres_protocol::IsNull::No)
×
UNCOV
165
            },
×
UNCOV
166
            w,
×
UNCOV
167
        )?;
×
168

UNCOV
169
        Ok(postgres_types::IsNull::No)
×
UNCOV
170
    }
×
171

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

UNCOV
177
        matches!(inner_type, &postgres_types::Type::PATH)
×
UNCOV
178
    }
×
179

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

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

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

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

UNCOV
214
        Ok(Self { coordinates })
×
UNCOV
215
    }
×
216

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

222
        matches!(inner_type, &postgres_types::Type::PATH)
145✔
223
    }
145✔
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 {
91✔
233
        MultiPoint::arrow_list_data_type()
91✔
234
    }
91✔
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};
265

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};
305

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 GeometryRef for MultiLineStringRef<'_> {
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 ToWkt<f64> for MultiLineStringRef<'_> {
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::MultiLineString(multi_line_string)
1✔
436
    }
1✔
437
}
438

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

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

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

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

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

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

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

3✔
520
            (number_of_lines, number_of_coordinates)
3✔
521
        }
3✔
522

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

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

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

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

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

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

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

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

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

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

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

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

1✔
605
        let a_ref = MultiLineStringRef::from(&a);
1✔
606

1✔
607
        assert_eq!(
1✔
608
            a_ref.wkt_string(),
1✔
609
            "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✔
610
        );
1✔
611
    }
1✔
612

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

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

1✔
624
        let line_string_back = MultiLineString::from(geo_line_string);
1✔
625

1✔
626
        assert_eq!(line_string, line_string_back);
1✔
627
    }
1✔
628

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

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

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

22,806✔
645
                    coordinate_builder.append(true);
22,806✔
646
                }
22,806✔
647

648
                line_builder.append(true);
10,752✔
649
            }
650

651
            geometries_builder.append(true);
4,032✔
652
        }
4,032✔
653

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

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

696
                assert_eq!(builder.len(), num_multi_lines);
128✔
697

698
                let builder_byte_size = MultiLineString::estimate_array_memory_size(&mut builder);
128✔
699

128✔
700
                let array = builder.finish_cloned();
128✔
701

128✔
702
                assert_eq!(
128✔
703
                    builder_byte_size,
128✔
704
                    array.get_array_memory_size(),
128✔
705
                    "{num_multi_lines}"
×
706
                );
707
            }
708
        }
709
    }
1✔
710
}
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