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