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    pub(crate) fn try_push_geom_offset(&mut self, offsets_length: usize) -> GeoArrowResult<()> {
136        self.geom_offsets.try_push_usize(offsets_length)?;
137        self.validity.append(true);
138        Ok(())
139    }
140
141    /// Push a raw offset to the underlying ring offsets buffer.
142    ///
143    /// # Invariants
144    ///
145    /// Care must be taken to ensure that pushing raw offsets
146    /// upholds the necessary invariants of the array.
147    #[inline]
148    pub(crate) fn try_push_ring_offset(&mut self, offsets_length: usize) -> GeoArrowResult<()> {
149        self.ring_offsets.try_push_usize(offsets_length)?;
150        Ok(())
151    }
152
153    /// Consume the builder and convert to an immutable [`MultiLineStringArray`]
154    pub fn finish(mut self) -> MultiLineStringArray {
155        let validity = self.validity.finish();
156
157        MultiLineStringArray::new(
158            self.coords.finish(),
159            self.geom_offsets.finish(),
160            self.ring_offsets.finish(),
161            validity,
162            self.data_type.metadata().clone(),
163        )
164    }
165
166    /// Add a new LineString to the end of this array.
167    ///
168    /// # Errors
169    ///
170    /// This function errors iff the new last item is larger than what O supports.
171    #[inline]
172    pub fn push_line_string(
173        &mut self,
174        value: Option<&impl LineStringTrait<T = f64>>,
175    ) -> GeoArrowResult<()> {
176        if let Some(line_string) = value {
177            // Total number of linestrings in this multilinestring
178            let num_line_strings = 1;
179            self.geom_offsets.try_push_usize(num_line_strings)?;
180
181            // For each ring:
182            // - Get ring
183            // - Add ring's # of coords to self.ring_offsets
184            // - Push ring's coords to self.coords
185
186            self.ring_offsets.try_push_usize(line_string.num_coords())?;
187
188            for coord in line_string.coords() {
189                self.coords.push_coord(&coord);
190            }
191
192            self.validity.append(true);
193        } else {
194            self.push_null();
195        }
196        Ok(())
197    }
198
199    /// Add a new MultiLineString to the end of this array.
200    ///
201    /// # Errors
202    ///
203    /// This function errors iff the new last item is larger than what O supports.
204    #[inline]
205    pub fn push_multi_line_string(
206        &mut self,
207        value: Option<&impl MultiLineStringTrait<T = f64>>,
208    ) -> GeoArrowResult<()> {
209        if let Some(multi_line_string) = value {
210            // Total number of linestrings in this multilinestring
211            let num_line_strings = multi_line_string.num_line_strings();
212            self.geom_offsets.try_push_usize(num_line_strings)?;
213
214            // For each ring:
215            // - Get ring
216            // - Add ring's # of coords to self.ring_offsets
217            // - Push ring's coords to self.coords
218
219            // Number of coords for each ring
220            for line_string in multi_line_string.line_strings() {
221                self.ring_offsets.try_push_usize(line_string.num_coords())?;
222
223                for coord in line_string.coords() {
224                    self.coords.push_coord(&coord);
225                }
226            }
227
228            self.validity.append(true);
229        } else {
230            self.push_null();
231        }
232        Ok(())
233    }
234
235    /// Add a new geometry to this builder
236    ///
237    /// This will error if the geometry type is not LineString or MultiLineString.
238    #[inline]
239    pub fn push_geometry(
240        &mut self,
241        value: Option<&impl GeometryTrait<T = f64>>,
242    ) -> GeoArrowResult<()> {
243        if let Some(value) = value {
244            match value.as_type() {
245                GeometryType::LineString(g) => self.push_line_string(Some(g))?,
246                GeometryType::MultiLineString(g) => self.push_multi_line_string(Some(g))?,
247                gt => {
248                    return Err(GeoArrowError::IncorrectGeometryType(format!(
249                        "Expected MultiLineString compatible geometry, got {}",
250                        gt.name()
251                    )));
252                }
253            }
254        } else {
255            self.push_null();
256        };
257        Ok(())
258    }
259
260    /// Extend this builder with the given geometries
261    pub fn extend_from_iter<'a>(
262        &mut self,
263        geoms: impl Iterator<Item = Option<&'a (impl MultiLineStringTrait<T = f64> + 'a)>>,
264    ) {
265        geoms
266            .into_iter()
267            .try_for_each(|maybe_multi_point| self.push_multi_line_string(maybe_multi_point))
268            .unwrap();
269    }
270
271    /// Extend this builder with the given geometries
272    pub fn extend_from_geometry_iter<'a>(
273        &mut self,
274        geoms: impl Iterator<Item = Option<&'a (impl GeometryTrait<T = f64> + 'a)>>,
275    ) -> GeoArrowResult<()> {
276        geoms.into_iter().try_for_each(|g| self.push_geometry(g))?;
277        Ok(())
278    }
279
280    /// Push a raw coordinate to the underlying coordinate array.
281    ///
282    /// # Invariants
283    ///
284    /// Care must be taken to ensure that pushing raw coordinates
285    /// to the array upholds the necessary invariants of the array.
286    #[inline]
287    pub(crate) fn push_coord(&mut self, coord: &impl CoordTrait<T = f64>) -> GeoArrowResult<()> {
288        self.coords.push_coord(coord);
289        Ok(())
290    }
291
292    #[inline]
293    pub(crate) fn push_null(&mut self) {
294        // NOTE! Only the geom_offsets array needs to get extended, because the next geometry will
295        // point to the same ring array location
296        self.geom_offsets.extend_constant(1);
297        self.validity.append(false);
298    }
299
300    /// Construct a new builder, pre-filling it with the provided geometries
301    pub fn from_multi_line_strings(
302        geoms: &[impl MultiLineStringTrait<T = f64>],
303        typ: MultiLineStringType,
304    ) -> Self {
305        let capacity = MultiLineStringCapacity::from_multi_line_strings(geoms.iter().map(Some));
306        let mut array = Self::with_capacity(typ, capacity);
307        array.extend_from_iter(geoms.iter().map(Some));
308        array
309    }
310
311    /// Construct a new builder, pre-filling it with the provided geometries
312    pub fn from_nullable_multi_line_strings(
313        geoms: &[Option<impl MultiLineStringTrait<T = f64>>],
314        typ: MultiLineStringType,
315    ) -> Self {
316        let capacity =
317            MultiLineStringCapacity::from_multi_line_strings(geoms.iter().map(|x| x.as_ref()));
318        let mut array = Self::with_capacity(typ, capacity);
319        array.extend_from_iter(geoms.iter().map(|x| x.as_ref()));
320        array
321    }
322
323    /// Construct a new builder, pre-filling it with the provided geometries
324    pub fn from_nullable_geometries(
325        geoms: &[Option<impl GeometryTrait<T = f64>>],
326        typ: MultiLineStringType,
327    ) -> GeoArrowResult<Self> {
328        let capacity = MultiLineStringCapacity::from_geometries(geoms.iter().map(|x| x.as_ref()))?;
329        let mut array = Self::with_capacity(typ, capacity);
330        array.extend_from_geometry_iter(geoms.iter().map(|x| x.as_ref()))?;
331        Ok(array)
332    }
333}
334
335impl<O: OffsetSizeTrait> TryFrom<(GenericWkbArray<O>, MultiLineStringType)>
336    for MultiLineStringBuilder
337{
338    type Error = GeoArrowError;
339
340    fn try_from((value, typ): (GenericWkbArray<O>, MultiLineStringType)) -> GeoArrowResult<Self> {
341        let wkb_objects = value
342            .iter()
343            .map(|x| x.transpose())
344            .collect::<GeoArrowResult<Vec<_>>>()?;
345        Self::from_nullable_geometries(&wkb_objects, typ)
346    }
347}
348
349impl GeoArrowArrayBuilder for MultiLineStringBuilder {
350    fn len(&self) -> usize {
351        self.geom_offsets.len_proxy()
352    }
353
354    fn push_null(&mut self) {
355        self.push_null();
356    }
357
358    fn push_geometry(
359        &mut self,
360        geometry: Option<&impl GeometryTrait<T = f64>>,
361    ) -> GeoArrowResult<()> {
362        self.push_geometry(geometry)
363    }
364
365    fn finish(self) -> Arc<dyn GeoArrowArray> {
366        Arc::new(self.finish())
367    }
368}