geoarrow_array/builder/
multilinestring.rs

1use std::sync::Arc;
2
3use arrow_array::OffsetSizeTrait;
4use arrow_buffer::NullBufferBuilder;
5use geo_traits::{CoordTrait, GeometryTrait, GeometryType, LineStringTrait, MultiLineStringTrait};
6use geoarrow_schema::MultiLineStringType;
7use geoarrow_schema::error::{GeoArrowError, GeoArrowResult};
8
9use crate::GeoArrowArray;
10use crate::array::{GenericWkbArray, MultiLineStringArray};
11use crate::builder::{CoordBufferBuilder, OffsetsBuilder};
12use crate::capacity::MultiLineStringCapacity;
13use crate::trait_::{GeoArrowArrayAccessor, GeoArrowArrayBuilder};
14use crate::util::GeometryTypeName;
15
16/// The GeoArrow equivalent to `Vec<Option<MultiLineString>>`: a mutable collection of
17/// MultiLineStrings.
18///
19/// Converting an [`MultiLineStringBuilder`] into a [`MultiLineStringArray`] is `O(1)`.
20#[derive(Debug)]
21pub struct MultiLineStringBuilder {
22    data_type: MultiLineStringType,
23
24    pub(crate) coords: CoordBufferBuilder,
25
26    /// OffsetsBuilder into the ring array where each geometry starts
27    pub(crate) geom_offsets: OffsetsBuilder<i32>,
28
29    /// OffsetsBuilder into the coordinate array where each ring starts
30    pub(crate) ring_offsets: OffsetsBuilder<i32>,
31
32    /// Validity is only defined at the geometry level
33    pub(crate) validity: NullBufferBuilder,
34}
35
36impl MultiLineStringBuilder {
37    /// Creates a new empty [`MultiLineStringBuilder`].
38    pub fn new(typ: MultiLineStringType) -> Self {
39        Self::with_capacity(typ, Default::default())
40    }
41
42    /// Creates a new [`MultiLineStringBuilder`] with a capacity.
43    pub fn with_capacity(typ: MultiLineStringType, capacity: MultiLineStringCapacity) -> Self {
44        let coords = CoordBufferBuilder::with_capacity(
45            capacity.coord_capacity,
46            typ.coord_type(),
47            typ.dimension(),
48        );
49        Self {
50            coords,
51            geom_offsets: OffsetsBuilder::with_capacity(capacity.geom_capacity),
52            ring_offsets: OffsetsBuilder::with_capacity(capacity.ring_capacity),
53            validity: NullBufferBuilder::new(capacity.geom_capacity),
54            data_type: typ,
55        }
56    }
57
58    /// Reserves capacity for at least `additional` more MultiLineStrings.
59    ///
60    /// The collection may reserve more space to speculatively avoid frequent reallocations. After
61    /// calling `reserve`, capacity will be greater than or equal to `self.len() + additional`.
62    /// Does nothing if capacity is already sufficient.
63    pub fn reserve(&mut self, additional: MultiLineStringCapacity) {
64        self.coords.reserve(additional.coord_capacity);
65        self.ring_offsets.reserve(additional.ring_capacity);
66        self.geom_offsets.reserve(additional.geom_capacity);
67    }
68
69    /// Reserves the minimum capacity for at least `additional` more MultiLineStrings.
70    ///
71    /// Unlike [`reserve`], this will not deliberately over-allocate to speculatively avoid
72    /// frequent allocations. After calling `reserve_exact`, capacity will be greater than or equal
73    /// to `self.len() + additional`. Does nothing if the capacity is already sufficient.
74    ///
75    /// Note that the allocator may give the collection more space than it
76    /// requests. Therefore, capacity can not be relied upon to be precisely
77    /// minimal. Prefer [`reserve`] if future insertions are expected.
78    ///
79    /// [`reserve`]: Self::reserve
80    pub fn reserve_exact(&mut self, additional: MultiLineStringCapacity) {
81        self.coords.reserve_exact(additional.coord_capacity);
82        self.ring_offsets.reserve_exact(additional.ring_capacity);
83        self.geom_offsets.reserve_exact(additional.geom_capacity);
84    }
85
86    /// Shrinks the capacity of self to fit.
87    pub fn shrink_to_fit(&mut self) {
88        self.coords.shrink_to_fit();
89        self.ring_offsets.shrink_to_fit();
90        self.geom_offsets.shrink_to_fit();
91        // self.validity.shrink_to_fit();
92    }
93
94    /// The canonical method to create a [`MultiLineStringBuilder`] out of its internal
95    /// components.
96    ///
97    /// # Implementation
98    ///
99    /// This function is `O(1)`.
100    ///
101    /// # Errors
102    ///
103    /// - if the validity is not `None` and its length is different from the number of geometries
104    /// - if the largest ring offset does not match the number of coordinates
105    /// - if the largest geometry offset does not match the size of ring offsets
106    pub fn try_new(
107        coords: CoordBufferBuilder,
108        geom_offsets: OffsetsBuilder<i32>,
109        ring_offsets: OffsetsBuilder<i32>,
110        validity: NullBufferBuilder,
111        data_type: MultiLineStringType,
112    ) -> GeoArrowResult<Self> {
113        // check(
114        //     &coords.clone().into(),
115        //     &geom_offsets.clone().into(),
116        //     &ring_offsets.clone().into(),
117        //     validity.as_ref().map(|x| x.len()),
118        // )?;
119        Ok(Self {
120            coords,
121            geom_offsets,
122            ring_offsets,
123            validity,
124            data_type,
125        })
126    }
127
128    /// Push a raw offset to the underlying geometry offsets buffer.
129    ///
130    /// # Invariants
131    ///
132    /// Care must be taken to ensure that pushing raw offsets
133    /// upholds the necessary invariants of the array.
134    #[inline]
135    #[allow(dead_code)]
136    pub(crate) fn try_push_geom_offset(&mut self, offsets_length: usize) -> GeoArrowResult<()> {
137        self.geom_offsets.try_push_usize(offsets_length)?;
138        self.validity.append(true);
139        Ok(())
140    }
141
142    /// Push a raw offset to the underlying ring offsets buffer.
143    ///
144    /// # Invariants
145    ///
146    /// Care must be taken to ensure that pushing raw offsets
147    /// upholds the necessary invariants of the array.
148    #[inline]
149    #[allow(dead_code)]
150    pub(crate) fn try_push_ring_offset(&mut self, offsets_length: usize) -> GeoArrowResult<()> {
151        self.ring_offsets.try_push_usize(offsets_length)?;
152        Ok(())
153    }
154
155    /// Consume the builder and convert to an immutable [`MultiLineStringArray`]
156    pub fn finish(mut self) -> MultiLineStringArray {
157        let validity = self.validity.finish();
158
159        MultiLineStringArray::new(
160            self.coords.finish(),
161            self.geom_offsets.finish(),
162            self.ring_offsets.finish(),
163            validity,
164            self.data_type.metadata().clone(),
165        )
166    }
167
168    /// Add a new LineString to the end of this array.
169    ///
170    /// # Errors
171    ///
172    /// This function errors iff the new last item is larger than what O supports.
173    #[inline]
174    pub fn push_line_string(
175        &mut self,
176        value: Option<&impl LineStringTrait<T = f64>>,
177    ) -> GeoArrowResult<()> {
178        if let Some(line_string) = value {
179            // Total number of linestrings in this multilinestring
180            let num_line_strings = 1;
181            self.geom_offsets.try_push_usize(num_line_strings)?;
182
183            // For each ring:
184            // - Get ring
185            // - Add ring's # of coords to self.ring_offsets
186            // - Push ring's coords to self.coords
187
188            self.ring_offsets.try_push_usize(line_string.num_coords())?;
189
190            for coord in line_string.coords() {
191                self.coords.push_coord(&coord);
192            }
193
194            self.validity.append(true);
195        } else {
196            self.push_null();
197        }
198        Ok(())
199    }
200
201    /// Add a new MultiLineString to the end of this array.
202    ///
203    /// # Errors
204    ///
205    /// This function errors iff the new last item is larger than what O supports.
206    #[inline]
207    pub fn push_multi_line_string(
208        &mut self,
209        value: Option<&impl MultiLineStringTrait<T = f64>>,
210    ) -> GeoArrowResult<()> {
211        if let Some(multi_line_string) = value {
212            // Total number of linestrings in this multilinestring
213            let num_line_strings = multi_line_string.num_line_strings();
214            self.geom_offsets.try_push_usize(num_line_strings)?;
215
216            // For each ring:
217            // - Get ring
218            // - Add ring's # of coords to self.ring_offsets
219            // - Push ring's coords to self.coords
220
221            // Number of coords for each ring
222            for line_string in multi_line_string.line_strings() {
223                self.ring_offsets.try_push_usize(line_string.num_coords())?;
224
225                for coord in line_string.coords() {
226                    self.coords.push_coord(&coord);
227                }
228            }
229
230            self.validity.append(true);
231        } else {
232            self.push_null();
233        }
234        Ok(())
235    }
236
237    /// Add a new geometry to this builder
238    ///
239    /// This will error if the geometry type is not LineString or MultiLineString.
240    #[inline]
241    pub fn push_geometry(
242        &mut self,
243        value: Option<&impl GeometryTrait<T = f64>>,
244    ) -> GeoArrowResult<()> {
245        if let Some(value) = value {
246            match value.as_type() {
247                GeometryType::LineString(g) => self.push_line_string(Some(g))?,
248                GeometryType::MultiLineString(g) => self.push_multi_line_string(Some(g))?,
249                gt => {
250                    return Err(GeoArrowError::IncorrectGeometryType(format!(
251                        "Expected MultiLineString compatible geometry, got {}",
252                        gt.name()
253                    )));
254                }
255            }
256        } else {
257            self.push_null();
258        };
259        Ok(())
260    }
261
262    /// Extend this builder with the given geometries
263    pub fn extend_from_iter<'a>(
264        &mut self,
265        geoms: impl Iterator<Item = Option<&'a (impl MultiLineStringTrait<T = f64> + 'a)>>,
266    ) {
267        geoms
268            .into_iter()
269            .try_for_each(|maybe_multi_point| self.push_multi_line_string(maybe_multi_point))
270            .unwrap();
271    }
272
273    /// Extend this builder with the given geometries
274    pub fn extend_from_geometry_iter<'a>(
275        &mut self,
276        geoms: impl Iterator<Item = Option<&'a (impl GeometryTrait<T = f64> + 'a)>>,
277    ) -> GeoArrowResult<()> {
278        geoms.into_iter().try_for_each(|g| self.push_geometry(g))?;
279        Ok(())
280    }
281
282    /// Push a raw coordinate to the underlying coordinate array.
283    ///
284    /// # Invariants
285    ///
286    /// Care must be taken to ensure that pushing raw coordinates
287    /// to the array upholds the necessary invariants of the array.
288    #[inline]
289    pub(crate) fn push_coord(&mut self, coord: &impl CoordTrait<T = f64>) -> GeoArrowResult<()> {
290        self.coords.push_coord(coord);
291        Ok(())
292    }
293
294    #[inline]
295    pub(crate) fn push_null(&mut self) {
296        // NOTE! Only the geom_offsets array needs to get extended, because the next geometry will
297        // point to the same ring array location
298        self.geom_offsets.extend_constant(1);
299        self.validity.append(false);
300    }
301
302    /// Construct a new builder, pre-filling it with the provided geometries
303    pub fn from_multi_line_strings(
304        geoms: &[impl MultiLineStringTrait<T = f64>],
305        typ: MultiLineStringType,
306    ) -> Self {
307        let capacity = MultiLineStringCapacity::from_multi_line_strings(geoms.iter().map(Some));
308        let mut array = Self::with_capacity(typ, capacity);
309        array.extend_from_iter(geoms.iter().map(Some));
310        array
311    }
312
313    /// Construct a new builder, pre-filling it with the provided geometries
314    pub fn from_nullable_multi_line_strings(
315        geoms: &[Option<impl MultiLineStringTrait<T = f64>>],
316        typ: MultiLineStringType,
317    ) -> Self {
318        let capacity =
319            MultiLineStringCapacity::from_multi_line_strings(geoms.iter().map(|x| x.as_ref()));
320        let mut array = Self::with_capacity(typ, capacity);
321        array.extend_from_iter(geoms.iter().map(|x| x.as_ref()));
322        array
323    }
324
325    /// Construct a new builder, pre-filling it with the provided geometries
326    pub fn from_nullable_geometries(
327        geoms: &[Option<impl GeometryTrait<T = f64>>],
328        typ: MultiLineStringType,
329    ) -> GeoArrowResult<Self> {
330        let capacity = MultiLineStringCapacity::from_geometries(geoms.iter().map(|x| x.as_ref()))?;
331        let mut array = Self::with_capacity(typ, capacity);
332        array.extend_from_geometry_iter(geoms.iter().map(|x| x.as_ref()))?;
333        Ok(array)
334    }
335}
336
337impl<O: OffsetSizeTrait> TryFrom<(GenericWkbArray<O>, MultiLineStringType)>
338    for MultiLineStringBuilder
339{
340    type Error = GeoArrowError;
341
342    fn try_from((value, typ): (GenericWkbArray<O>, MultiLineStringType)) -> GeoArrowResult<Self> {
343        let wkb_objects = value
344            .iter()
345            .map(|x| x.transpose())
346            .collect::<GeoArrowResult<Vec<_>>>()?;
347        Self::from_nullable_geometries(&wkb_objects, typ)
348    }
349}
350
351impl GeoArrowArrayBuilder for MultiLineStringBuilder {
352    fn len(&self) -> usize {
353        self.geom_offsets.len_proxy()
354    }
355
356    fn push_null(&mut self) {
357        self.push_null();
358    }
359
360    fn push_geometry(
361        &mut self,
362        geometry: Option<&impl GeometryTrait<T = f64>>,
363    ) -> GeoArrowResult<()> {
364        self.push_geometry(geometry)
365    }
366
367    fn finish(self) -> Arc<dyn GeoArrowArray> {
368        Arc::new(self.finish())
369    }
370}