Skip to content

Commit

Permalink
Implement conversions from GEOS objects to GeoArrow arrays (#202)
Browse files Browse the repository at this point in the history
* Implement conversion from geos linestrings to geoarrow

* Point and Linestring geos scalar types, wrapped by traits

* Attempt at geosmultipoint

* Implement geos MultiPoint conversion natively

* add point, multipoint, linestring geos round trip tests

* polygon geos round trip

* bumpalo geos polygon

* multipolygon from geos

* add test

* Add quick geos buffer benchmark
  • Loading branch information
kylebarron authored Sep 30, 2023
1 parent 8d4d4b4 commit 527e003
Show file tree
Hide file tree
Showing 40 changed files with 1,516 additions and 399 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ name = "gdal"
test = false
required-features = ["gdal"]

[[bench]]
name = "geos_buffer"
harness = false

[[bench]]
name = "nybb"
harness = false
Expand Down
22 changes: 22 additions & 0 deletions benches/geos_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use criterion::{criterion_group, criterion_main, Criterion};
use geoarrow2::algorithm::geos::buffer::Buffer;
use geoarrow2::array::{CoordBuffer, InterleavedCoordBuffer, PointArray};

fn generate_data() -> PointArray {
let coords = vec![0.0; 100_000];
let coord_buffer = CoordBuffer::Interleaved(InterleavedCoordBuffer::new(coords.into()));
PointArray::new(coord_buffer, None)
}

pub fn criterion_benchmark(c: &mut Criterion) {
let point_array = generate_data();

c.bench_function("buffer", |b| {
b.iter(|| {
let _buffered = point_array.buffer(1.0, 8).unwrap();
})
});
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
4 changes: 2 additions & 2 deletions src/algorithm/geo/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ mod test {
use arrow2::array::Float64Array;

use super::*;
use crate::test::polygon::polygon_arr;
use crate::test::polygon::p_array;

#[test]
fn tmp() {
let arr = polygon_arr();
let arr = p_array();
let area = arr.unsigned_area();
assert_eq!(area, Float64Array::from_vec(vec![28., 18.]));
}
Expand Down
4 changes: 2 additions & 2 deletions src/algorithm/geos/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ mod test {
use arrow2::array::Float64Array;

use super::*;
use crate::test::polygon::polygon_arr;
use crate::test::polygon::p_array;

#[test]
fn tmp() {
let arr = polygon_arr();
let arr = p_array();
let area = arr.area().unwrap();
assert_eq!(area, Float64Array::from_vec(vec![28., 18.]));
}
Expand Down
88 changes: 88 additions & 0 deletions src/algorithm/geos/buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use crate::array::{PointArray, PolygonArray};
use crate::error::Result;
use crate::GeometryArrayTrait;
use geos::Geom;

pub trait Buffer {
type Output;

fn buffer(&self, width: f64, quadsegs: i32) -> Result<Self::Output>;
}

impl Buffer for PointArray {
type Output = PolygonArray<i32>;

fn buffer(&self, width: f64, quadsegs: i32) -> Result<Self::Output> {
// NOTE: the bumpalo allocator didn't appear to make any perf difference with geos :shrug:
// Presumably GEOS is allocating on its own before we can put the geometry in the Bump?
let bump = bumpalo::Bump::new();

let mut geos_geoms = bumpalo::collections::Vec::with_capacity_in(self.len(), &bump);

for maybe_g in self.iter_geos() {
if let Some(g) = maybe_g {
let area = g.buffer(width, quadsegs)?;
geos_geoms.push(Some(area));
} else {
geos_geoms.push(None);
}
}

let polygon_array: PolygonArray<i32> = geos_geoms.try_into()?;
Ok(polygon_array)
}
}

// // Note: this can't (easily) be parameterized in the macro because PointArray is not generic over O
// impl Area for PointArray {
// fn area(&self) -> Result<PrimitiveArray<f64>> {
// Ok(zeroes(self.len(), self.validity()))
// }
// }

// /// Implementation where the result is zero.
// macro_rules! zero_impl {
// ($type:ty) => {
// impl<O: Offset> Area for $type {
// fn area(&self) -> Result<PrimitiveArray<f64>> {
// Ok(zeroes(self.len(), self.validity()))
// }
// }
// };
// }

// zero_impl!(LineStringArray<O>);
// zero_impl!(MultiPointArray<O>);
// zero_impl!(MultiLineStringArray<O>);

// macro_rules! iter_geos_impl {
// ($type:ty) => {
// impl<O: Offset> Area for $type {
// fn area(&self) -> Result<PrimitiveArray<f64>> {
// }
// }
// };
// }

// iter_geos_impl!(PolygonArray<O>);
// iter_geos_impl!(MultiPolygonArray<O>);
// iter_geos_impl!(WKBArray<O>);

// impl<O: Offset> Area for GeometryArray<O> {
// crate::geometry_array_delegate_impl! {
// fn area(&self) -> Result<PrimitiveArray<f64>>;
// }
// }

#[cfg(test)]
mod test {
use super::*;
use crate::test::point::point_array;

#[test]
fn point_buffer() {
let arr = point_array();
let buffered = arr.buffer(1., 8).unwrap();
dbg!(buffered);
}
}
1 change: 1 addition & 0 deletions src/algorithm/geos/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod area;
pub mod buffer;
2 changes: 1 addition & 1 deletion src/array/linestring/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ pub use mutable::MutableLineStringArray;

mod array;
pub mod iterator;
mod mutable;
pub(crate) mod mutable;
31 changes: 4 additions & 27 deletions src/array/linestring/mutable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ impl<O: Offset> From<MutableLineStringArray<O>> for ListArray<O> {
}
}

fn first_pass<'a>(
geoms: impl Iterator<Item = Option<impl LineStringTrait<'a> + 'a>>,
pub(crate) fn first_pass<'a>(
geoms: impl Iterator<Item = Option<impl LineStringTrait<'a>>>,
geoms_length: usize,
) -> (usize, usize) {
let mut coord_capacity = 0;
Expand All @@ -240,8 +240,8 @@ fn first_pass<'a>(
(coord_capacity, geom_capacity)
}

fn second_pass<'a, O: Offset>(
geoms: impl Iterator<Item = Option<impl LineStringTrait<'a, T = f64> + 'a>>,
pub(crate) fn second_pass<'a, O: Offset>(
geoms: impl Iterator<Item = Option<impl LineStringTrait<'a, T = f64>>>,
coord_capacity: usize,
geom_capacity: usize,
) -> MutableLineStringArray<O> {
Expand Down Expand Up @@ -310,29 +310,6 @@ impl<O: Offset> TryFrom<WKBArray<O>> for MutableLineStringArray<O> {
}
}

// #[cfg(feature = "geos")]
// impl<O: Offset> TryFrom<Vec<Option<geos::Geometry<'_>>>> for MutableLineStringArray<O> {
// type Error = GeoArrowError;
// fn try_from(value: Vec<Option<geos::Geometry>>) -> std::result::Result<Self, Self::Error> {
// let length = value.len();
// let geos_linestring_objects: Vec<Option<GEOSLineString>> = value
// .iter()
// .map(|geom| {
// geom.map(|geom| GEOSLineString::new_unchecked(std::borrow::Cow::Owned(geom)))
// })
// .collect();
// let (coord_capacity, geom_capacity) = first_pass(
// geos_linestring_objects.iter().map(|item| item.as_ref()),
// length,
// );
// Ok(second_pass(
// geos_linestring_objects.iter().map(|item| item.as_ref()),
// coord_capacity,
// geom_capacity,
// ))
// }
// }

/// LineString and MultiPoint have the same layout, so enable conversions between the two to change
/// the semantic type
impl<O: Offset> From<MutableLineStringArray<O>> for MutableMultiPointArray<O> {
Expand Down
10 changes: 5 additions & 5 deletions src/array/multilinestring/mutable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ use arrow2::types::Offset;

#[derive(Debug, Clone)]
pub struct MutableMultiLineStringArray<O: Offset> {
coords: MutableCoordBuffer,
pub(crate) coords: MutableCoordBuffer,

/// Offsets into the ring array where each geometry starts
geom_offsets: Offsets<O>,
pub(crate) geom_offsets: Offsets<O>,

/// Offsets into the coordinate array where each ring starts
ring_offsets: Offsets<O>,
pub(crate) ring_offsets: Offsets<O>,

/// Validity is only defined at the geometry level
validity: Option<MutableBitmap>,
pub(crate) validity: Option<MutableBitmap>,
}

pub type MultiLineStringInner<O> = (
Expand Down Expand Up @@ -268,7 +268,7 @@ impl<'a, O: Offset> MutableMultiLineStringArray<O> {
}

#[inline]
fn push_null(&mut self) {
pub(crate) fn push_null(&mut self) {
// NOTE! Only the geom_offsets array needs to get extended, because the next geometry will
// point to the same ring array location
self.geom_offsets.extend_constant(1);
Expand Down
2 changes: 1 addition & 1 deletion src/array/multipoint/mutable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ impl<'a, O: Offset> MutableMultiPointArray<O> {
}

#[inline]
fn push_null(&mut self) {
pub(crate) fn push_null(&mut self) {
self.geom_offsets.extend_constant(1);
match &mut self.validity {
Some(validity) => validity.push(false),
Expand Down
14 changes: 7 additions & 7 deletions src/array/multipolygon/mutable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ pub type MutableMultiPolygonParts<O> = (
/// Converting a [`MutableMultiPolygonArray`] into a [`MultiPolygonArray`] is `O(1)`.
#[derive(Debug, Clone)]
pub struct MutableMultiPolygonArray<O: Offset> {
coords: MutableCoordBuffer,
pub(crate) coords: MutableCoordBuffer,

/// Offsets into the polygon array where each geometry starts
geom_offsets: Offsets<O>,
pub(crate) geom_offsets: Offsets<O>,

/// Offsets into the ring array where each polygon starts
polygon_offsets: Offsets<O>,
pub(crate) polygon_offsets: Offsets<O>,

/// Offsets into the coordinate array where each ring starts
ring_offsets: Offsets<O>,
pub(crate) ring_offsets: Offsets<O>,

/// Validity is only defined at the geometry level
validity: Option<MutableBitmap>,
pub(crate) validity: Option<MutableBitmap>,
}

impl<'a, O: Offset> MutableMultiPolygonArray<O> {
Expand Down Expand Up @@ -319,15 +319,15 @@ impl<'a, O: Offset> MutableMultiPolygonArray<O> {
}

#[inline]
fn push_empty(&mut self) {
pub(crate) fn push_empty(&mut self) {
self.geom_offsets.try_push_usize(0).unwrap();
if let Some(validity) = &mut self.validity {
validity.push(true)
}
}

#[inline]
fn push_null(&mut self) {
pub(crate) fn push_null(&mut self) {
// NOTE! Only the geom_offsets array needs to get extended, because the next geometry will
// point to the same polygon array location
// Note that we don't use self.try_push_geom_offset because that sets validity to true
Expand Down
2 changes: 1 addition & 1 deletion src/array/point/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ pub use mutable::MutablePointArray;

mod array;
pub mod iterator;
mod mutable;
pub(crate) mod mutable;
2 changes: 1 addition & 1 deletion src/array/point/mutable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ fn from_coords(
mutable_array
}

fn from_nullable_coords(
pub(crate) fn from_nullable_coords(
geoms: impl Iterator<Item = Option<impl PointTrait<T = f64>>>,
geoms_length: usize,
) -> MutablePointArray {
Expand Down
14 changes: 7 additions & 7 deletions src/array/polygon/mutable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ pub type MutablePolygonParts<O> = (
/// Converting a [`MutablePolygonArray`] into a [`PolygonArray`] is `O(1)`.
#[derive(Debug, Clone)]
pub struct MutablePolygonArray<O: Offset> {
coords: MutableCoordBuffer,
pub(crate) coords: MutableCoordBuffer,

/// Offsets into the ring array where each geometry starts
geom_offsets: Offsets<O>,
pub(crate) geom_offsets: Offsets<O>,

/// Offsets into the coordinate array where each ring starts
ring_offsets: Offsets<O>,
pub(crate) ring_offsets: Offsets<O>,

/// Validity is only defined at the geometry level
validity: Option<MutableBitmap>,
pub(crate) validity: Option<MutableBitmap>,
}

impl<'a, O: Offset> MutablePolygonArray<O> {
Expand Down Expand Up @@ -184,7 +184,7 @@ impl<'a, O: Offset> MutablePolygonArray<O> {
// - Get ring
// - Add ring's # of coords to self.ring_offsets
// - Push ring's coords to self.coords
for int_ring_idx in 0..polygon.num_interiors() {
for int_ring_idx in 0..num_interiors {
let int_ring = polygon.interior(int_ring_idx).unwrap();
let int_ring_num_coords = int_ring.num_coords();
self.ring_offsets.try_push_usize(int_ring_num_coords)?;
Expand Down Expand Up @@ -241,15 +241,15 @@ impl<'a, O: Offset> MutablePolygonArray<O> {
}

#[inline]
fn push_empty(&mut self) {
pub(crate) fn push_empty(&mut self) {
self.geom_offsets.try_push_usize(0).unwrap();
if let Some(validity) = &mut self.validity {
validity.push(true)
}
}

#[inline]
fn push_null(&mut self) {
pub(crate) fn push_null(&mut self) {
// NOTE! Only the geom_offsets array needs to get extended, because the next geometry will
// point to the same ring array location
self.geom_offsets.extend_constant(1);
Expand Down
Loading

0 comments on commit 527e003

Please sign in to comment.