Skip to content

Commit

Permalink
Make ContourBuilder generic over the raster data type
Browse files Browse the repository at this point in the history
Signed-off-by: netthier <[email protected]>
  • Loading branch information
netthier committed Apr 15, 2024
1 parent 2053186 commit f93227c
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 67 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/area.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Float, Pt};
use crate::{ContourValue, Float, Pt};

Check warning on line 1 in src/area.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `ContourValue`

Check warning on line 1 in src/area.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `ContourValue`

Check warning on line 1 in src/area.rs

View workflow job for this annotation

GitHub Actions / build_and_test (beta)

unused import: `ContourValue`

Check warning on line 1 in src/area.rs

View workflow job for this annotation

GitHub Actions / build_and_test (nightly)

unused import: `ContourValue`

#[allow(clippy::unnecessary_cast)]
// Note that we need to disable the clippy warning about unnecessary casts
Expand Down
18 changes: 9 additions & 9 deletions src/band.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
use crate::Float;
use geo_types::MultiPolygon;
use crate::{ContourValue, Float};
use geo_types::{CoordFloat, MultiPolygon};

Check warning on line 2 in src/band.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `CoordFloat`

Check warning on line 2 in src/band.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `CoordFloat`

Check warning on line 2 in src/band.rs

View workflow job for this annotation

GitHub Actions / build_and_test (beta)

unused import: `CoordFloat`

Check warning on line 2 in src/band.rs

View workflow job for this annotation

GitHub Actions / build_and_test (nightly)

unused import: `CoordFloat`

/// 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<V: ContourValue> {
pub(crate) geometry: MultiPolygon<Float>,
pub(crate) min_v: Float,
pub(crate) max_v: Float,
pub(crate) min_v: V,
pub(crate) max_v: V,
}

impl Band {
impl<V: ContourValue> Band<V> {
/// Borrow the [`MultiPolygon`](geo_types::MultiPolygon) geometry of this contour.
pub fn geometry(&self) -> &MultiPolygon<Float> {
&self.geometry
}

/// Get the owned polygons and thresholds (min and max) of this band.
pub fn into_inner(self) -> (MultiPolygon<Float>, Float, Float) {
pub fn into_inner(self) -> (MultiPolygon<Float>, 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
}

Expand Down
14 changes: 7 additions & 7 deletions src/contour.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
use crate::Float;
use geo_types::MultiPolygon;
use crate::{ContourValue, Float};
use geo_types::{CoordFloat, MultiPolygon};

Check warning on line 2 in src/contour.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `CoordFloat`

Check warning on line 2 in src/contour.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `CoordFloat`

Check warning on line 2 in src/contour.rs

View workflow job for this annotation

GitHub Actions / build_and_test (beta)

unused import: `CoordFloat`

Check warning on line 2 in src/contour.rs

View workflow job for this annotation

GitHub Actions / build_and_test (nightly)

unused import: `CoordFloat`

/// A contour has the geometry and threshold of a contour ring, built by [`ContourBuilder`].
#[derive(Debug, Clone)]
pub struct Contour {
pub struct Contour<V: ContourValue> {
pub(crate) geometry: MultiPolygon<Float>,
pub(crate) threshold: Float,
pub(crate) threshold: V,
}

impl Contour {
impl<V: ContourValue> Contour<V> {
/// Borrow the [`MultiPolygon`](geo_types::MultiPolygon) geometry of this contour.
pub fn geometry(&self) -> &MultiPolygon<Float> {
&self.geometry
}

/// Get the owned polygons and threshold of this contour.
pub fn into_inner(self) -> (MultiPolygon<Float>, Float) {
pub fn into_inner(self) -> (MultiPolygon<Float>, 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
}

Expand Down
54 changes: 27 additions & 27 deletions src/contourbuilder.rs
Original file line number Diff line number Diff line change
@@ -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};

Check warning on line 5 in src/contourbuilder.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `CoordFloat`

Check warning on line 5 in src/contourbuilder.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `CoordFloat`

Check warning on line 5 in src/contourbuilder.rs

View workflow job for this annotation

GitHub Actions / build_and_test (beta)

unused import: `CoordFloat`

Check warning on line 5 in src/contourbuilder.rs

View workflow job for this annotation

GitHub Actions / build_and_test (nightly)

unused import: `CoordFloat`
use rustc_hash::FxHashMap;

/// Contours generator, using builder pattern, to
Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -74,7 +74,7 @@ impl ContourBuilder {
self
}

fn smooth_linear(&self, ring: &mut Ring, values: &[Float], value: Float) {
fn smooth_linear<V: ContourValue>(&self, ring: &mut Ring, values: &[V], value: V) {
let dx = self.dx;
let dy = self.dy;
let len_values = values.len();
Expand All @@ -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::<V, Float>( (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::<V, Float>((value - v0) / (v1 - v0) - num_traits::cast(0.5).unwrap()).unwrap();
}
}
})
Expand All @@ -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<Vec<Line>> {
pub fn lines<V: ContourValue>(&self, values: &[V], thresholds: &[V]) -> Result<Vec<Line<V>>> {
if values.len() != self.dx * self.dy {
return Err(new_error(ErrorKind::BadDimension));
}
Expand All @@ -122,12 +122,12 @@ impl ContourBuilder {
.collect()
}

fn line(
fn line<V: ContourValue>(
&self,
values: &[Float],
threshold: Float,
values: &[V],
threshold: V,
isoring: &mut IsoRingBuilder,
) -> Result<Line> {
) -> Result<Line<V>> {
let mut result = isoring.compute(values, threshold)?;
let mut linestrings = Vec::new();

Expand All @@ -148,7 +148,7 @@ impl ContourBuilder {
linestrings.push(LineString(ring));
});
Ok(Line {
geometry: MultiLineString::<Float>(linestrings),
geometry: MultiLineString(linestrings),
threshold,
})
}
Expand All @@ -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<Vec<Contour>> {
pub fn contours<V: ContourValue>(&self, values: &[V], thresholds: &[V]) -> Result<Vec<Contour<V>>> {
if values.len() != self.dx * self.dy {
return Err(new_error(ErrorKind::BadDimension));
}
Expand All @@ -173,12 +173,12 @@ impl ContourBuilder {
.collect()
}

fn contour(
fn contour<V: ContourValue>(
&self,
values: &[Float],
threshold: Float,
values: &[V],
threshold: V,
isoring: &mut IsoRingBuilder,
) -> Result<Contour> {
) -> Result<Contour<V>> {
let (mut polygons, mut holes) = (Vec::new(), Vec::new());
let mut result = isoring.compute(values, threshold)?;

Expand All @@ -197,7 +197,7 @@ impl ContourBuilder {
});
}
if area(&ring) > 0.0 {
polygons.push(Polygon::<Float>::new(LineString::new(ring), vec![]))
polygons.push(Polygon::new(LineString::new(ring), vec![]))
} else {
holes.push(LineString::new(ring));
}
Expand All @@ -213,7 +213,7 @@ impl ContourBuilder {
});

Ok(Contour {
geometry: MultiPolygon::<Float>(polygons),
geometry: MultiPolygon(polygons),
threshold,
})
}
Expand All @@ -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<Vec<Band>> {
pub fn isobands<V: ContourValue>(&self, values: &[V], thresholds: &[V]) -> Result<Vec<Band<V>>> {
// 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.
Expand Down Expand Up @@ -268,7 +268,7 @@ impl ContourBuilder {
.collect::<Vec<Ring>>();
Ok((rings, *threshold))
})
.collect::<Result<Vec<(Vec<Ring>, Float)>>>()?;
.collect::<Result<Vec<(Vec<Ring>, 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.
Expand All @@ -281,7 +281,7 @@ impl ContourBuilder {
})
.collect::<Vec<_>>();

let mut bands: Vec<Band> = Vec::new();
let mut bands: Vec<Band<V>> = Vec::new();
// Reconstruction of the polygons
b.into_iter().for_each(|(rings, min_v, max_v)| {
let mut rings_and_area = rings
Expand Down Expand Up @@ -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::<Float>::new(ring.into(), vec![]));
polygons.push(Polygon::new(ring.into(), vec![]));
} else {
interior_rings.push(ring.into());
}
Expand All @@ -331,7 +331,7 @@ impl ContourBuilder {
polygons.reverse();

bands.push(Band {
geometry: MultiPolygon::<Float>(polygons),
geometry: MultiPolygon(polygons),
min_v: *min_v,
max_v: *max_v,
});
Expand Down
18 changes: 7 additions & 11 deletions src/isoringbuilder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use geo_types::{Coord, CoordFloat};

Check warning on line 1 in src/isoringbuilder.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `CoordFloat`

Check warning on line 1 in src/isoringbuilder.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `CoordFloat`

Check warning on line 1 in src/isoringbuilder.rs

View workflow job for this annotation

GitHub Actions / build_and_test (beta)

unused import: `CoordFloat`

Check warning on line 1 in src/isoringbuilder.rs

View workflow job for this annotation

GitHub Actions / build_and_test (nightly)

unused import: `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;
Expand Down Expand Up @@ -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<Vec<Ring>> {

pub fn contour_rings<V: ContourValue>(values: &[V], threshold: V, dx: usize, dy: usize) -> Result<Vec<Ring>> {
let mut isoring = IsoRingBuilder::new(dx, dy);
isoring.compute(values, threshold)
}
Expand Down Expand Up @@ -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<Vec<Ring>> {
pub fn compute<V: ContourValue>(&mut self, values: &[V], threshold: V) -> Result<Vec<Ring>> {
macro_rules! case_stitch {
($ix:expr, $x:ident, $y:ident, $result:expr) => {
CASES[$ix]
Expand Down Expand Up @@ -177,12 +174,11 @@ impl IsoRingBuilder {
y: i64,
result: &mut Vec<Ring>,
) -> Result<()> {
assert_eq!((x, y), ((x as Float) as i64, (y as Float) as i64));
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,
};
Expand Down
10 changes: 6 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,19 @@ mod error;
mod isoringbuilder;
mod line;

pub trait ContourValue: PartialOrd + Copy + Num + NumCast{}
impl<T> 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<f32>;
#[cfg(not(feature = "f32"))]
pub type Pt = geo_types::Coord;

pub type Pt = geo_types::Coord<Float>;
pub type Ring = Vec<Pt>;

use geo_types::CoordNum;

Check warning on line 77 in src/lib.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `geo_types::CoordNum`

Check warning on line 77 in src/lib.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `geo_types::CoordNum`

Check warning on line 77 in src/lib.rs

View workflow job for this annotation

GitHub Actions / build_and_test (beta)

unused import: `geo_types::CoordNum`

Check warning on line 77 in src/lib.rs

View workflow job for this annotation

GitHub Actions / build_and_test (nightly)

unused import: `geo_types::CoordNum`
use num_traits::{Num, NumCast};
pub use crate::band::Band;
pub use crate::contour::Contour;
pub use crate::contourbuilder::ContourBuilder;
Expand Down
14 changes: 7 additions & 7 deletions src/line.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
use crate::Float;
use geo_types::MultiLineString;
use crate::{ContourValue, Float};
use geo_types::{CoordFloat, MultiLineString};

Check warning on line 2 in src/line.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `CoordFloat`

Check warning on line 2 in src/line.rs

View workflow job for this annotation

GitHub Actions / build_and_test (stable)

unused import: `CoordFloat`

Check warning on line 2 in src/line.rs

View workflow job for this annotation

GitHub Actions / build_and_test (beta)

unused import: `CoordFloat`

Check warning on line 2 in src/line.rs

View workflow job for this annotation

GitHub Actions / build_and_test (nightly)

unused import: `CoordFloat`

/// 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<Float>,
pub(crate) threshold: Float,
pub(crate) threshold: V,
}

impl Line {
impl<V: ContourValue> Line<V> {
/// Borrow the [`MultiLineString`](geo_types::MultiLineString) geometry of this contour.
pub fn geometry(&self) -> &MultiLineString<Float> {
&self.geometry
}

/// Get the owned lines and threshold of this contour.
pub fn into_inner(self) -> (MultiLineString<Float>, Float) {
pub fn into_inner(self) -> (MultiLineString<Float>, 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
}

Expand Down

0 comments on commit f93227c

Please sign in to comment.