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