diff --git a/geo-types/CHANGES.md b/geo-types/CHANGES.md index 79bb76114..4b4b0cdb2 100644 --- a/geo-types/CHANGES.md +++ b/geo-types/CHANGES.md @@ -2,6 +2,8 @@ ## Unreleased * Add rstar compatibility for MultiPolygon +* Add multi-threading support to Multi* geometries. + * Feature-gated ('multithreading'), disabled by default, enabled by default when geo-types is used by geo ## 0.7.13 diff --git a/geo-types/Cargo.toml b/geo-types/Cargo.toml index 17c95d39d..6fd4eea99 100644 --- a/geo-types/Cargo.toml +++ b/geo-types/Cargo.toml @@ -13,6 +13,7 @@ edition = "2021" [features] default = ["std"] std = ["approx?/std", "num-traits/std", "serde?/std"] +multithreading = ["rayon"] # Prefer `use-rstar` feature rather than enabling rstar directly. # rstar integration relies on the optional approx crate, but implicit features cannot yet enable other features. # See: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#namespaced-features @@ -25,6 +26,7 @@ use-rstar_0_11 = ["rstar_0_11", "approx"] use-rstar_0_12 = ["rstar_0_12", "approx"] [dependencies] +rayon = { version = "1.10.0", optional = true } approx = { version = ">= 0.4.0, < 0.6.0", optional = true, default-features = false } arbitrary = { version = "1.2.0", optional = true } num-traits = { version = "0.2", default-features = false, features = ["libm"] } diff --git a/geo-types/src/geometry/multi_line_string.rs b/geo-types/src/geometry/multi_line_string.rs index 11b0c8ff8..ff19a7c3d 100644 --- a/geo-types/src/geometry/multi_line_string.rs +++ b/geo-types/src/geometry/multi_line_string.rs @@ -5,6 +5,8 @@ use alloc::vec::Vec; #[cfg(any(feature = "approx", test))] use approx::{AbsDiffEq, RelativeEq}; use core::iter::FromIterator; +#[cfg(feature = "multithreading")] +use rayon::prelude::*; /// A collection of /// [`LineString`s](line_string/struct.LineString.html). Can @@ -118,6 +120,36 @@ impl MultiLineString { } } +#[cfg(feature = "multithreading")] +impl IntoParallelIterator for MultiLineString { + type Item = LineString; + type Iter = rayon::vec::IntoIter>; + + fn into_par_iter(self) -> Self::Iter { + self.0.into_par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Sync> IntoParallelIterator for &'a MultiLineString { + type Item = &'a LineString; + type Iter = rayon::slice::Iter<'a, LineString>; + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Send + Sync> IntoParallelIterator for &'a mut MultiLineString { + type Item = &'a mut LineString; + type Iter = rayon::slice::IterMut<'a, LineString>; + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter_mut() + } +} + #[cfg(any(feature = "approx", test))] impl RelativeEq for MultiLineString where @@ -199,6 +231,21 @@ mod test { use super::*; use crate::{line_string, wkt}; + #[cfg(feature = "multithreading")] + #[test] + fn test_multithreading_linestring() { + let multi: MultiLineString = wkt! { + MULTILINESTRING((0 0,2 0,1 2,0 0), (10 10,12 10,11 12,10 10)) + }; + let mut multimut: MultiLineString = wkt! { + MULTILINESTRING((0 0,2 0,1 2,0 0), (10 10,12 10,11 12,10 10)) + }; + multi.par_iter().for_each(|_p| ()); + multimut.par_iter_mut().for_each(|_p| ()); + let _ = &multi.into_par_iter().for_each(|_p| ()); + let _ = &mut multimut.par_iter_mut().for_each(|_p| ()); + } + #[test] fn test_iter() { let multi: MultiLineString = wkt! { diff --git a/geo-types/src/geometry/multi_point.rs b/geo-types/src/geometry/multi_point.rs index ff3c16c6f..c596333da 100644 --- a/geo-types/src/geometry/multi_point.rs +++ b/geo-types/src/geometry/multi_point.rs @@ -6,6 +6,8 @@ use approx::{AbsDiffEq, RelativeEq}; use alloc::vec; use alloc::vec::Vec; use core::iter::FromIterator; +#[cfg(feature = "multithreading")] +use rayon::prelude::*; /// A collection of [`Point`s](struct.Point.html). Can /// be created from a `Vec` of `Point`s, or from an @@ -85,6 +87,36 @@ impl<'a, T: CoordNum> IntoIterator for &'a mut MultiPoint { } } +#[cfg(feature = "multithreading")] +impl IntoParallelIterator for MultiPoint { + type Item = Point; + type Iter = rayon::vec::IntoIter>; + + fn into_par_iter(self) -> Self::Iter { + self.0.into_par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Sync> IntoParallelIterator for &'a MultiPoint { + type Item = &'a Point; + type Iter = rayon::slice::Iter<'a, Point>; + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Send + Sync> IntoParallelIterator for &'a mut MultiPoint { + type Item = &'a mut Point; + type Iter = rayon::slice::IterMut<'a, Point>; + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter_mut() + } +} + impl MultiPoint { pub fn new(value: Vec>) -> Self { Self(value) diff --git a/geo-types/src/geometry/multi_polygon.rs b/geo-types/src/geometry/multi_polygon.rs index fefeaf857..0a68333b1 100644 --- a/geo-types/src/geometry/multi_polygon.rs +++ b/geo-types/src/geometry/multi_polygon.rs @@ -4,7 +4,10 @@ use alloc::vec; use alloc::vec::Vec; #[cfg(any(feature = "approx", test))] use approx::{AbsDiffEq, RelativeEq}; + use core::iter::FromIterator; +#[cfg(feature = "multithreading")] +use rayon::prelude::*; /// A collection of [`Polygon`s](struct.Polygon.html). Can /// be created from a `Vec` of `Polygon`s, or from an @@ -75,6 +78,36 @@ impl<'a, T: CoordNum> IntoIterator for &'a mut MultiPolygon { } } +#[cfg(feature = "multithreading")] +impl IntoParallelIterator for MultiPolygon { + type Item = Polygon; + type Iter = rayon::vec::IntoIter>; + + fn into_par_iter(self) -> Self::Iter { + self.0.into_par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Sync> IntoParallelIterator for &'a MultiPolygon { + type Item = &'a Polygon; + type Iter = rayon::slice::Iter<'a, Polygon>; + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Send + Sync> IntoParallelIterator for &'a mut MultiPolygon { + type Item = &'a mut Polygon; + type Iter = rayon::slice::IterMut<'a, Polygon>; + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter_mut() + } +} + impl MultiPolygon { /// Instantiate Self from the raw content value pub fn new(value: Vec>) -> Self { @@ -250,6 +283,22 @@ mod test { } } + #[cfg(feature = "multithreading")] + #[test] + fn test_par_iter() { + let multi = MultiPolygon::new(vec![ + polygon![(x: 0, y: 0), (x: 2, y: 0), (x: 1, y: 2), (x:0, y:0)], + polygon![(x: 10, y: 10), (x: 12, y: 10), (x: 11, y: 12), (x:10, y:10)], + ]); + let mut multimut = MultiPolygon::new(vec![ + polygon![(x: 0, y: 0), (x: 2, y: 0), (x: 1, y: 2), (x:0, y:0)], + polygon![(x: 10, y: 10), (x: 12, y: 10), (x: 11, y: 12), (x:10, y:10)], + ]); + multi.par_iter().for_each(|_p| ()); + let _ = &multimut.par_iter_mut().for_each(|_p| ()); + let _ = &multi.into_par_iter().for_each(|_p| ()); + let _ = &mut multimut.par_iter_mut().for_each(|_p| ()); + } #[test] fn test_iter_mut() { let mut multi = MultiPolygon::new(vec![ diff --git a/geo-types/src/lib.rs b/geo-types/src/lib.rs index 00e4a6ecc..a4d12ccc3 100644 --- a/geo-types/src/lib.rs +++ b/geo-types/src/lib.rs @@ -62,6 +62,8 @@ //! The following optional [Cargo features] are available: //! //! - `std`: Enables use of the full `std` library. Enabled by default. +//! - `multithreading`: Enables multi-threaded iteration over `Multi*` geometries. **Disabled** +//! by default but **enabled** by `geo`'s default features. //! - `approx`: Allows geometry types to be checked for approximate equality with [approx] //! - `arbitrary`: Allows geometry types to be created from unstructured input with [arbitrary] //! - `serde`: Allows geometry types to be serialized and deserialized with [Serde] diff --git a/geo/Cargo.toml b/geo/Cargo.toml index d48be60f2..330d1c399 100644 --- a/geo/Cargo.toml +++ b/geo/Cargo.toml @@ -17,7 +17,7 @@ default = ["earcutr", "spade", "multithreading"] use-proj = ["proj"] proj-network = ["use-proj", "proj/network"] use-serde = ["serde", "geo-types/serde"] -multithreading = ["i_overlay/allow_multithreading"] +multithreading = ["i_overlay/allow_multithreading", "geo-types/multithreading"] [dependencies] earcutr = { version = "0.4.2", optional = true } diff --git a/geo/src/lib.rs b/geo/src/lib.rs index 88bf61aa8..0d1cde38f 100644 --- a/geo/src/lib.rs +++ b/geo/src/lib.rs @@ -191,7 +191,8 @@ //! - Allows geometry types to be serialized and deserialized with [Serde]. //! - ☐ Disabled by default. //! - `multithreading`: -//! - Enables multithreading support for the `i_overlay` crate. +//! - Enables multithreading support for the `i_overlay` crate (via Rayon), and activates the `multithreading` flag +//! in `geo-types`, enabling multi-threaded iteration over `Multi*` geometries. //! - ☑ Enabled by default. //! //! # Ecosystem