From 96465c5ef6a4b3e2fa6f36d71f5437f88248d2ea Mon Sep 17 00:00:00 2001 From: netthier Date: Fri, 12 Apr 2024 11:33:40 +0200 Subject: [PATCH] Make ContourBuilder generic over the raster data type Signed-off-by: netthier --- Cargo.toml | 3 ++- src/area.rs | 2 +- src/band.rs | 18 +++++++-------- src/contour.rs | 14 +++++------ src/contourbuilder.rs | 54 +++++++++++++++++++++---------------------- src/isoringbuilder.rs | 17 ++++++-------- src/lib.rs | 10 ++++---- src/line.rs | 14 +++++------ 8 files changed, 66 insertions(+), 66 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 23bfdc4..331740a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,12 @@ license = "MIT OR Apache-2.0" [dependencies] geojson = { version = ">=0.16, <=0.24", optional = true } -geo-types= { version = "0.7" } +geo-types = { version = "0.7" } lazy_static = "1.0" serde_json = { version = "^1.0", optional = true } rustc-hash = "1.0" slab = "0.4" +num-traits = "0.2" [dev-dependencies] serde_json = "^1.0" diff --git a/src/area.rs b/src/area.rs index 774bffd..528c82e 100644 --- a/src/area.rs +++ b/src/area.rs @@ -1,4 +1,4 @@ -use crate::{Float, Pt}; +use crate::{ContourValue, Float, Pt}; #[allow(clippy::unnecessary_cast)] // Note that we need to disable the clippy warning about unnecessary casts diff --git a/src/band.rs b/src/band.rs index b41249b..b748925 100644 --- a/src/band.rs +++ b/src/band.rs @@ -1,32 +1,32 @@ -use crate::Float; -use geo_types::MultiPolygon; +use crate::{ContourValue, Float}; +use geo_types::{CoordFloat, MultiPolygon}; /// An isoband has the geometry and min / max values of a contour ring, built by [`ContourBuilder`]. #[derive(Debug, Clone)] -pub struct Band { +pub struct Band { pub(crate) geometry: MultiPolygon, - pub(crate) min_v: Float, - pub(crate) max_v: Float, + pub(crate) min_v: V, + pub(crate) max_v: V, } -impl Band { +impl Band { /// Borrow the [`MultiPolygon`](geo_types::MultiPolygon) geometry of this contour. pub fn geometry(&self) -> &MultiPolygon { &self.geometry } /// Get the owned polygons and thresholds (min and max) of this band. - pub fn into_inner(self) -> (MultiPolygon, Float, Float) { + pub fn into_inner(self) -> (MultiPolygon, V, V) { (self.geometry, self.min_v, self.max_v) } /// Get the minimum value used to construct this band. - pub fn min_v(&self) -> Float { + pub fn min_v(&self) -> V { self.min_v } /// Get the maximum value used to construct this band. - pub fn max_v(&self) -> Float { + pub fn max_v(&self) -> V { self.max_v } diff --git a/src/contour.rs b/src/contour.rs index 37ed75e..2add550 100644 --- a/src/contour.rs +++ b/src/contour.rs @@ -1,26 +1,26 @@ -use crate::Float; -use geo_types::MultiPolygon; +use crate::{ContourValue, Float}; +use geo_types::{CoordFloat, MultiPolygon}; /// A contour has the geometry and threshold of a contour ring, built by [`ContourBuilder`]. #[derive(Debug, Clone)] -pub struct Contour { +pub struct Contour { pub(crate) geometry: MultiPolygon, - pub(crate) threshold: Float, + pub(crate) threshold: V, } -impl Contour { +impl Contour { /// Borrow the [`MultiPolygon`](geo_types::MultiPolygon) geometry of this contour. pub fn geometry(&self) -> &MultiPolygon { &self.geometry } /// Get the owned polygons and threshold of this contour. - pub fn into_inner(self) -> (MultiPolygon, Float) { + pub fn into_inner(self) -> (MultiPolygon, V) { (self.geometry, self.threshold) } /// Get the threshold used to construct this contour. - pub fn threshold(&self) -> Float { + pub fn threshold(&self) -> V { self.threshold } diff --git a/src/contourbuilder.rs b/src/contourbuilder.rs index 830a548..11600ae 100644 --- a/src/contourbuilder.rs +++ b/src/contourbuilder.rs @@ -1,8 +1,8 @@ use crate::area::{area, contains}; use crate::error::{new_error, ErrorKind, Result}; use crate::isoringbuilder::IsoRingBuilder; -use crate::{Band, Contour, Float, Line, Ring}; -use geo_types::{LineString, MultiLineString, MultiPolygon, Polygon}; +use crate::{Band, Contour, ContourValue, Float, Line, Ring}; +use geo_types::{CoordFloat, LineString, MultiLineString, MultiPolygon, Polygon}; use rustc_hash::FxHashMap; /// Contours generator, using builder pattern, to @@ -43,10 +43,10 @@ impl ContourBuilder { dx, dy, smooth, - x_origin: 0., - y_origin: 0., - x_step: 1., - y_step: 1., + x_origin: 0.0, + y_origin: 0.0, + x_step: 1.0, + y_step: 1.0, } } @@ -74,7 +74,7 @@ impl ContourBuilder { self } - fn smooth_linear(&self, ring: &mut Ring, values: &[Float], value: Float) { + fn smooth_linear(&self, ring: &mut Ring, values: &[V], value: V) { let dx = self.dx; let dy = self.dy; let len_values = values.len(); @@ -91,11 +91,11 @@ impl ContourBuilder { let v1 = values[ix]; if x > 0.0 && x < (dx as Float) && (xt as Float - x).abs() < Float::EPSILON { v0 = values[yt * dx + xt - 1]; - point.x = x + (value - v0) / (v1 - v0) - 0.5; + point.x = x + num_traits::cast::( (value - v0) / (v1 - v0) - num_traits::cast(0.5).unwrap()).unwrap(); } if y > 0.0 && y < (dy as Float) && (yt as Float - y).abs() < Float::EPSILON { v0 = values[(yt - 1) * dx + xt]; - point.y = y + (value - v0) / (v1 - v0) - 0.5; + point.y = y + num_traits::cast::((value - v0) / (v1 - v0) - num_traits::cast(0.5).unwrap()).unwrap(); } } }) @@ -111,7 +111,7 @@ impl ContourBuilder { /// /// * `values` - The slice of values to be used. /// * `thresholds` - The slice of thresholds values to be used. - pub fn lines(&self, values: &[Float], thresholds: &[Float]) -> Result> { + pub fn lines(&self, values: &[V], thresholds: &[V]) -> Result>> { if values.len() != self.dx * self.dy { return Err(new_error(ErrorKind::BadDimension)); } @@ -122,12 +122,12 @@ impl ContourBuilder { .collect() } - fn line( + fn line( &self, - values: &[Float], - threshold: Float, + values: &[V], + threshold: V, isoring: &mut IsoRingBuilder, - ) -> Result { + ) -> Result> { let mut result = isoring.compute(values, threshold)?; let mut linestrings = Vec::new(); @@ -148,7 +148,7 @@ impl ContourBuilder { linestrings.push(LineString(ring)); }); Ok(Line { - geometry: MultiLineString::(linestrings), + geometry: MultiLineString(linestrings), threshold, }) } @@ -162,7 +162,7 @@ impl ContourBuilder { /// /// * `values` - The slice of values to be used. /// * `thresholds` - The slice of thresholds values to be used. - pub fn contours(&self, values: &[Float], thresholds: &[Float]) -> Result> { + pub fn contours(&self, values: &[V], thresholds: &[V]) -> Result>> { if values.len() != self.dx * self.dy { return Err(new_error(ErrorKind::BadDimension)); } @@ -173,12 +173,12 @@ impl ContourBuilder { .collect() } - fn contour( + fn contour( &self, - values: &[Float], - threshold: Float, + values: &[V], + threshold: V, isoring: &mut IsoRingBuilder, - ) -> Result { + ) -> Result> { let (mut polygons, mut holes) = (Vec::new(), Vec::new()); let mut result = isoring.compute(values, threshold)?; @@ -197,7 +197,7 @@ impl ContourBuilder { }); } if area(&ring) > 0.0 { - polygons.push(Polygon::::new(LineString::new(ring), vec![])) + polygons.push(Polygon::new(LineString::new(ring), vec![])) } else { holes.push(LineString::new(ring)); } @@ -213,7 +213,7 @@ impl ContourBuilder { }); Ok(Contour { - geometry: MultiPolygon::(polygons), + geometry: MultiPolygon(polygons), threshold, }) } @@ -228,7 +228,7 @@ impl ContourBuilder { /// * `values` - The slice of values to be used. /// * `thresholds` - The slice of thresholds values to be used /// (have to be equal to or greater than 2). - pub fn isobands(&self, values: &[Float], thresholds: &[Float]) -> Result> { + pub fn isobands(&self, values: &[V], thresholds: &[V]) -> Result>> { // We will compute rings as previously, but we will // iterate over the contours in pairs and use the paths from the lower threshold // and the path from the upper threshold to create the isoband. @@ -268,7 +268,7 @@ impl ContourBuilder { .collect::>(); Ok((rings, *threshold)) }) - .collect::, Float)>>>()?; + .collect::, V)>>>()?; // We now have the rings for each isolines for all the given thresholds, // we can iterate over them in pairs to compute the isobands. @@ -281,7 +281,7 @@ impl ContourBuilder { }) .collect::>(); - let mut bands: Vec = Vec::new(); + let mut bands: Vec> = Vec::new(); // Reconstruction of the polygons b.into_iter().for_each(|(rings, min_v, max_v)| { let mut rings_and_area = rings @@ -314,7 +314,7 @@ impl ContourBuilder { for (i, (ring, _)) in rings_and_area.into_iter().enumerate() { if *enclosed_by_n.get(&i).unwrap() % 2 == 0 { - polygons.push(Polygon::::new(ring.into(), vec![])); + polygons.push(Polygon::new(ring.into(), vec![])); } else { interior_rings.push(ring.into()); } @@ -331,7 +331,7 @@ impl ContourBuilder { polygons.reverse(); bands.push(Band { - geometry: MultiPolygon::(polygons), + geometry: MultiPolygon(polygons), min_v: *min_v, max_v: *max_v, }); diff --git a/src/isoringbuilder.rs b/src/isoringbuilder.rs index 4024028..648e6ae 100644 --- a/src/isoringbuilder.rs +++ b/src/isoringbuilder.rs @@ -1,5 +1,6 @@ +use geo_types::{Coord, CoordFloat}; use crate::error::{new_error, ErrorKind, Result}; -use crate::{Float, Pt, Ring}; +use crate::{ContourValue, Float, Pt, Ring}; use lazy_static::lazy_static; use rustc_hash::FxHashMap; use slab::Slab; @@ -49,12 +50,8 @@ struct Fragment { /// * `threshold` - The threshold value. /// * `dx` - The number of columns in the grid. /// * `dy` - The number of rows in the grid. -pub fn contour_rings( - values: &[Float], - threshold: Float, - dx: usize, - dy: usize, -) -> Result> { + +pub fn contour_rings(values: &[V], threshold: V, dx: usize, dy: usize) -> Result> { let mut isoring = IsoRingBuilder::new(dx, dy); isoring.compute(values, threshold) } @@ -94,7 +91,7 @@ impl IsoRingBuilder { /// /// * `values` - The slice of values to be used. /// * `threshold` - The threshold value to use. - pub fn compute(&mut self, values: &[Float], threshold: Float) -> Result> { + pub fn compute(&mut self, values: &[V], threshold: V) -> Result> { macro_rules! case_stitch { ($ix:expr, $x:ident, $y:ident, $result:expr) => { CASES[$ix] @@ -177,11 +174,11 @@ impl IsoRingBuilder { y: i64, result: &mut Vec, ) -> Result<()> { - let start = Pt { + let start = Coord { x: line[0][0] + x as Float, y: line[0][1] + y as Float, }; - let end = Pt { + let end = Coord { x: line[1][0] + x as Float, y: line[1][1] + y as Float, }; diff --git a/src/lib.rs b/src/lib.rs index 551ef19..c3e17ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,17 +63,19 @@ mod error; mod isoringbuilder; mod line; +pub trait ContourValue: PartialOrd + Copy + Num + NumCast{} +impl ContourValue for T where T: PartialOrd + Copy + Num + NumCast {} + #[cfg(feature = "f32")] pub type Float = f32; #[cfg(not(feature = "f32"))] pub type Float = f64; -#[cfg(feature = "f32")] -pub type Pt = geo_types::Coord; -#[cfg(not(feature = "f32"))] -pub type Pt = geo_types::Coord; +pub type Pt = geo_types::Coord; pub type Ring = Vec; +use geo_types::CoordNum; +use num_traits::{Num, NumCast}; pub use crate::band::Band; pub use crate::contour::Contour; pub use crate::contourbuilder::ContourBuilder; diff --git a/src/line.rs b/src/line.rs index 967534c..b7fc2ce 100644 --- a/src/line.rs +++ b/src/line.rs @@ -1,26 +1,26 @@ -use crate::Float; -use geo_types::MultiLineString; +use crate::{ContourValue, Float}; +use geo_types::{CoordFloat, MultiLineString}; /// A line has the geometry and threshold of a contour ring, built by [`ContourBuilder`]. #[derive(Debug, Clone)] -pub struct Line { +pub struct Line< V: ContourValue> { pub(crate) geometry: MultiLineString, - pub(crate) threshold: Float, + pub(crate) threshold: V, } -impl Line { +impl Line { /// Borrow the [`MultiLineString`](geo_types::MultiLineString) geometry of this contour. pub fn geometry(&self) -> &MultiLineString { &self.geometry } /// Get the owned lines and threshold of this contour. - pub fn into_inner(self) -> (MultiLineString, Float) { + pub fn into_inner(self) -> (MultiLineString, V) { (self.geometry, self.threshold) } /// Get the threshold used to construct this isoline. - pub fn threshold(&self) -> Float { + pub fn threshold(&self) -> V { self.threshold }