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

geo-engine / geoengine / 3929938005

pending completion
3929938005

push

github

GitHub
Merge #713

84930 of 96741 relevant lines covered (87.79%)

79640.1 hits per line

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

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

3
use arrow::array::{ArrayBuilder, BooleanArray};
4
use arrow::error::ArrowError;
5
use float_cmp::{ApproxEq, F64Margin};
6
use geo::algorithm::intersects::Intersects;
7
use serde::{Deserialize, Serialize};
8
use snafu::ensure;
9

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

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

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

31
impl MultiLineString {
32
    pub fn new(coordinates: Vec<Vec<Coordinate2D>>) -> Result<Self> {
26✔
33
        ensure!(
26✔
34
            !coordinates.is_empty() && coordinates.iter().all(|c| c.len() >= 2),
47✔
35
            error::UnallowedEmpty
×
36
        );
37

38
        Ok(Self::new_unchecked(coordinates))
26✔
39
    }
26✔
40

41
    pub(crate) fn new_unchecked(coordinates: Vec<Vec<Coordinate2D>>) -> Self {
29✔
42
        Self { coordinates }
29✔
43
    }
29✔
44
}
45

46
impl MultiLineStringAccess for MultiLineString {
47
    type L = Vec<Coordinate2D>;
48
    fn lines(&self) -> &[Vec<Coordinate2D>] {
44✔
49
        &self.coordinates
44✔
50
    }
44✔
51
}
52

53
impl Geometry for MultiLineString {
54
    const DATA_TYPE: VectorDataType = VectorDataType::MultiLineString;
55

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

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

68
        false
×
69
    }
×
70
}
71

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

86
impl TryFrom<TypedGeometry> for MultiLineString {
87
    type Error = Error;
88

89
    fn try_from(value: TypedGeometry) -> Result<Self, Self::Error> {
90
        if let TypedGeometry::MultiLineString(geometry) = value {
×
91
            Ok(geometry)
×
92
        } else {
93
            Err(PrimitivesError::InvalidConversion.into())
×
94
        }
95
    }
×
96
}
97

98
impl AsRef<[Vec<Coordinate2D>]> for MultiLineString {
99
    fn as_ref(&self) -> &[Vec<Coordinate2D>] {
3✔
100
        &self.coordinates
3✔
101
    }
3✔
102
}
103

104
impl ApproxEq for &MultiLineString {
105
    type Margin = F64Margin;
106

107
    fn approx_eq<M: Into<Self::Margin>>(self, other: Self, margin: M) -> bool {
6✔
108
        let m = margin.into();
6✔
109
        self.lines().len() == other.lines().len()
6✔
110
            && self
5✔
111
                .lines()
5✔
112
                .iter()
5✔
113
                .zip(other.lines().iter())
5✔
114
                .all(|(line_a, line_b)| line_a.len() == line_b.len() && line_a.approx_eq(line_b, m))
9✔
115
    }
6✔
116
}
117

118
impl ArrowTyped for MultiLineString {
119
    type ArrowArray = arrow::array::ListArray;
120
    type ArrowBuilder = arrow::array::ListBuilder<
121
        arrow::array::ListBuilder<<Coordinate2D as ArrowTyped>::ArrowBuilder>,
122
    >;
123

124
    fn arrow_data_type() -> arrow::datatypes::DataType {
64✔
125
        MultiPoint::arrow_list_data_type()
64✔
126
    }
64✔
127

128
    fn builder_byte_size(builder: &mut Self::ArrowBuilder) -> usize {
×
129
        let multi_line_indices_size = builder.len() * std::mem::size_of::<i32>();
×
130

×
131
        let line_builder = builder.values();
×
132
        let line_indices_size = line_builder.len() * std::mem::size_of::<i32>();
×
133

×
134
        let point_builder = line_builder.values();
×
135
        let point_indices_size = point_builder.len() * std::mem::size_of::<i32>();
×
136

×
137
        let coordinates_size = Coordinate2D::builder_byte_size(point_builder);
×
138

×
139
        multi_line_indices_size + line_indices_size + point_indices_size + coordinates_size
×
140
    }
×
141

142
    fn arrow_builder(capacity: usize) -> Self::ArrowBuilder {
12✔
143
        let minimal_number_of_coordinates = 2 * capacity; // at least 2 coordinates per line string
12✔
144
        let coordinate_builder = Coordinate2D::arrow_builder(minimal_number_of_coordinates);
12✔
145
        let line_string_builder = arrow::array::ListBuilder::new(coordinate_builder);
12✔
146
        arrow::array::ListBuilder::new(line_string_builder) // multi line strings = lists of line strings
12✔
147
    }
12✔
148

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

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

154
        for multi_lines in &[a, b] {
2✔
155
            for multi_line_index in 0..multi_lines.len() {
2✔
156
                let line_builder = multi_line_builder.values();
2✔
157

2✔
158
                let lines_ref = multi_lines.value(multi_line_index);
2✔
159
                let lines = downcast_array::<ListArray>(&lines_ref);
2✔
160

161
                for line_index in 0..lines.len() {
3✔
162
                    let coordinate_builder = line_builder.values();
3✔
163

3✔
164
                    let coordinates_ref = lines.value(line_index);
3✔
165
                    let coordinates = downcast_array::<FixedSizeListArray>(&coordinates_ref);
3✔
166

167
                    for coordinate_index in 0..coordinates.len() {
8✔
168
                        let floats_ref = coordinates.value(coordinate_index);
8✔
169
                        let floats: &Float64Array = downcast_array(&floats_ref);
8✔
170

8✔
171
                        coordinate_builder.values().append_slice(floats.values());
8✔
172

8✔
173
                        coordinate_builder.append(true);
8✔
174
                    }
8✔
175

176
                    line_builder.append(true);
3✔
177
                }
178

179
                multi_line_builder.append(true);
2✔
180
            }
181
        }
182

183
        Ok(multi_line_builder.finish())
1✔
184
    }
1✔
185

186
    fn filter(
3✔
187
        multi_lines: &Self::ArrowArray,
3✔
188
        filter_array: &BooleanArray,
3✔
189
    ) -> Result<Self::ArrowArray, ArrowError> {
3✔
190
        use arrow::array::{Array, FixedSizeListArray, Float64Array, ListArray};
3✔
191

3✔
192
        let mut multi_line_builder = Self::arrow_builder(0);
3✔
193

194
        for multi_line_index in 0..multi_lines.len() {
6✔
195
            if !filter_array.value(multi_line_index) {
6✔
196
                continue;
2✔
197
            }
4✔
198

4✔
199
            let line_builder = multi_line_builder.values();
4✔
200

4✔
201
            let lines_ref = multi_lines.value(multi_line_index);
4✔
202
            let lines = downcast_array::<ListArray>(&lines_ref);
4✔
203

204
            for line_index in 0..lines.len() {
5✔
205
                let coordinate_builder = line_builder.values();
5✔
206

5✔
207
                let coordinates_ref = lines.value(line_index);
5✔
208
                let coordinates = downcast_array::<FixedSizeListArray>(&coordinates_ref);
5✔
209

210
                for coordinate_index in 0..coordinates.len() {
12✔
211
                    let floats_ref = coordinates.value(coordinate_index);
12✔
212
                    let floats: &Float64Array = downcast_array(&floats_ref);
12✔
213

12✔
214
                    coordinate_builder.values().append_slice(floats.values());
12✔
215

12✔
216
                    coordinate_builder.append(true);
12✔
217
                }
12✔
218

219
                line_builder.append(true);
5✔
220
            }
221

222
            multi_line_builder.append(true);
4✔
223
        }
224

225
        Ok(multi_line_builder.finish())
3✔
226
    }
3✔
227

228
    fn from_vec(multi_line_strings: Vec<Self>) -> Result<Self::ArrowArray, ArrowError>
2✔
229
    where
2✔
230
        Self: Sized,
2✔
231
    {
2✔
232
        let mut builder = Self::arrow_builder(multi_line_strings.len());
2✔
233
        for multi_line_string in multi_line_strings {
5✔
234
            let line_string_builder = builder.values();
3✔
235

236
            for line_string in multi_line_string.as_ref() {
4✔
237
                let coordinate_builder = line_string_builder.values();
4✔
238

239
                for coordinate in line_string {
14✔
240
                    let float_builder = coordinate_builder.values();
10✔
241
                    float_builder.append_value(coordinate.x);
10✔
242
                    float_builder.append_value(coordinate.y);
10✔
243
                    coordinate_builder.append(true);
10✔
244
                }
10✔
245

246
                line_string_builder.append(true);
4✔
247
            }
248

249
            builder.append(true);
3✔
250
        }
251

252
        Ok(builder.finish())
2✔
253
    }
2✔
254
}
255

256
#[derive(Debug, PartialEq)]
×
257
pub struct MultiLineStringRef<'g> {
258
    point_coordinates: Vec<&'g [Coordinate2D]>,
259
}
260

261
impl<'r> GeometryRef for MultiLineStringRef<'r> {}
262

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

267
        Ok(Self::new_unchecked(coordinates))
1✔
268
    }
1✔
269

270
    pub(crate) fn new_unchecked(coordinates: Vec<&'g [Coordinate2D]>) -> Self {
14✔
271
        Self {
14✔
272
            point_coordinates: coordinates,
14✔
273
        }
14✔
274
    }
14✔
275
}
276

277
impl<'g> MultiLineStringAccess for MultiLineStringRef<'g> {
278
    type L = &'g [Coordinate2D];
279
    fn lines(&self) -> &[&'g [Coordinate2D]] {
10✔
280
        &self.point_coordinates
10✔
281
    }
10✔
282
}
283

284
impl<'g> From<MultiLineStringRef<'g>> for geojson::Geometry {
285
    fn from(geometry: MultiLineStringRef<'g>) -> geojson::Geometry {
2✔
286
        geojson::Geometry::new(match geometry.point_coordinates.len() {
2✔
287
            1 => {
288
                let coordinates = geometry.point_coordinates[0];
1✔
289
                let positions = coordinates.iter().map(|c| vec![c.x, c.y]).collect();
3✔
290
                geojson::Value::LineString(positions)
1✔
291
            }
292
            _ => geojson::Value::MultiLineString(
1✔
293
                geometry
1✔
294
                    .point_coordinates
1✔
295
                    .iter()
1✔
296
                    .map(|&coordinates| coordinates.iter().map(|c| vec![c.x, c.y]).collect())
5✔
297
                    .collect(),
1✔
298
            ),
1✔
299
        })
300
    }
2✔
301
}
302

303
impl<'g> From<MultiLineStringRef<'g>> for MultiLineString {
304
    fn from(multi_line_string_ref: MultiLineStringRef<'g>) -> Self {
3✔
305
        MultiLineString::from(&multi_line_string_ref)
3✔
306
    }
3✔
307
}
308

309
impl<'g> From<&MultiLineStringRef<'g>> for MultiLineString {
310
    fn from(multi_line_string_ref: &MultiLineStringRef<'g>) -> Self {
3✔
311
        MultiLineString::new_unchecked(
3✔
312
            multi_line_string_ref
3✔
313
                .point_coordinates
3✔
314
                .iter()
3✔
315
                .copied()
3✔
316
                .map(ToOwned::to_owned)
3✔
317
                .collect(),
3✔
318
        )
3✔
319
    }
3✔
320
}
321

322
#[cfg(test)]
323
mod tests {
324
    use float_cmp::approx_eq;
325

326
    use super::*;
327

328
    #[test]
1✔
329
    fn access() {
1✔
330
        fn aggregate<T: MultiLineStringAccess>(multi_line_string: &T) -> (usize, usize) {
3✔
331
            let number_of_lines = multi_line_string.lines().len();
3✔
332
            let number_of_coordinates = multi_line_string
3✔
333
                .lines()
3✔
334
                .iter()
3✔
335
                .map(AsRef::as_ref)
3✔
336
                .map(<[Coordinate2D]>::len)
3✔
337
                .sum();
3✔
338

3✔
339
            (number_of_lines, number_of_coordinates)
3✔
340
        }
3✔
341

1✔
342
        let coordinates = vec![
1✔
343
            vec![(0.0, 0.1).into(), (1.0, 1.1).into()],
1✔
344
            vec![(3.0, 3.1).into(), (4.0, 4.1).into()],
1✔
345
        ];
1✔
346
        let multi_line_string = MultiLineString::new(coordinates.clone()).unwrap();
1✔
347
        let multi_line_string_ref =
1✔
348
            MultiLineStringRef::new(coordinates.iter().map(AsRef::as_ref).collect()).unwrap();
1✔
349

1✔
350
        assert_eq!(aggregate(&multi_line_string), (2, 4));
1✔
351
        assert_eq!(
1✔
352
            aggregate(&multi_line_string),
1✔
353
            aggregate(&multi_line_string_ref)
1✔
354
        );
1✔
355
    }
1✔
356

357
    #[test]
1✔
358
    fn approx_equal() {
1✔
359
        let a = MultiLineString::new(vec![
1✔
360
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
361
            vec![(0.5, 0.5).into(), (0.6, 0.6).into()],
1✔
362
            vec![(0.6, 0.6).into(), (0.9, 0.9).into()],
1✔
363
        ])
1✔
364
        .unwrap();
1✔
365

1✔
366
        let b = MultiLineString::new(vec![
1✔
367
            vec![(0.099_999_999, 0.1).into(), (0.5, 0.5).into()],
1✔
368
            vec![(0.5, 0.5).into(), (0.6, 0.6).into()],
1✔
369
            vec![(0.6, 0.6).into(), (0.9, 0.9).into()],
1✔
370
        ])
1✔
371
        .unwrap();
1✔
372

1✔
373
        assert!(approx_eq!(&MultiLineString, &a, &b, epsilon = 0.000_001));
1✔
374
    }
1✔
375

376
    #[test]
1✔
377
    fn not_approx_equal_outer_len() {
1✔
378
        let a = MultiLineString::new(vec![
1✔
379
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
380
            vec![(0.5, 0.5).into(), (0.6, 0.6).into()],
1✔
381
            vec![(0.6, 0.6).into(), (0.9, 0.9).into()],
1✔
382
        ])
1✔
383
        .unwrap();
1✔
384

1✔
385
        let b = MultiLineString::new(vec![
1✔
386
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
387
            vec![(0.5, 0.5).into(), (0.6, 0.6).into()],
1✔
388
            vec![(0.6, 0.6).into(), (0.9, 0.9).into()],
1✔
389
            vec![(0.9, 0.9).into(), (123_456_789.9, 123_456_789.9).into()],
1✔
390
        ])
1✔
391
        .unwrap();
1✔
392

1✔
393
        assert!(!approx_eq!(&MultiLineString, &a, &b, F64Margin::default()));
1✔
394
    }
1✔
395

396
    #[test]
1✔
397
    fn not_approx_equal_inner_len() {
1✔
398
        let a = MultiLineString::new(vec![
1✔
399
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
400
            vec![(0.5, 0.5).into(), (0.6, 0.6).into(), (0.7, 0.7).into()],
1✔
401
            vec![(0.7, 0.7).into(), (0.9, 0.9).into()],
1✔
402
        ])
1✔
403
        .unwrap();
1✔
404

1✔
405
        let b = MultiLineString::new(vec![
1✔
406
            vec![(0.1, 0.1).into(), (0.5, 0.5).into()],
1✔
407
            vec![(0.5, 0.5).into(), (0.6, 0.6).into()],
1✔
408
            vec![(0.6, 0.6).into(), (0.7, 0.7).into(), (0.9, 0.9).into()],
1✔
409
        ])
1✔
410
        .unwrap();
1✔
411

1✔
412
        assert!(!approx_eq!(&MultiLineString, &a, &b, F64Margin::default()));
1✔
413
    }
1✔
414
}
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