diff --git a/Cargo.toml b/Cargo.toml index 53bccd9..9cd05cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ serde_json = "^1.0" [features] geojson = ["dep:geojson", "dep:serde_json"] +f32 = [] [package.metadata.docs.rs] all-features = true diff --git a/examples/ex.rs b/examples/ex.rs index 8fdba5b..aae9e80 100644 --- a/examples/ex.rs +++ b/examples/ex.rs @@ -1,4 +1,4 @@ -use contour::ContourBuilder; +use contour::{ContourBuilder, Float}; use geojson::{FeatureCollection, GeoJson}; use std::fs::File; use std::io::{BufWriter, Write}; @@ -6,11 +6,20 @@ use std::io::{BufWriter, Write}; fn main() { let pot_pop_fr = include_str!("../tests/fixtures/pot_pop_fr.json"); let raw_data: serde_json::Value = serde_json::from_str(pot_pop_fr).unwrap(); - let matrix: Vec = raw_data["data"] + let matrix: Vec = raw_data["data"] .as_array() .unwrap() .iter() - .map(|x| x.as_f64().unwrap()) + .map(|x| { + #[cfg(not(feature = "f32"))] + { + x.as_f64().unwrap() + } + #[cfg(feature = "f32")] + { + x.as_f64().unwrap() as f32 + } + }) .collect(); let h = raw_data["height"].as_u64().unwrap() as u32; let w = raw_data["width"].as_u64().unwrap() as u32; @@ -51,11 +60,20 @@ fn main() { let volcano = include_str!("../tests/fixtures/volcano.json"); let raw_data: serde_json::Value = serde_json::from_str(volcano).unwrap(); - let matrix: Vec = raw_data["data"] + let matrix: Vec = raw_data["data"] .as_array() .unwrap() .iter() - .map(|x| x.as_f64().unwrap()) + .map(|x| { + #[cfg(not(feature = "f32"))] + { + x.as_f64().unwrap() + } + #[cfg(feature = "f32")] + { + x.as_f64().unwrap() as f32 + } + }) .collect(); let h = raw_data["height"].as_u64().unwrap() as u32; let w = raw_data["width"].as_u64().unwrap() as u32; diff --git a/src/area.rs b/src/area.rs index 7bb64e6..2ed4f21 100644 --- a/src/area.rs +++ b/src/area.rs @@ -1,6 +1,6 @@ -use crate::Pt; +use crate::{Float, Pt}; -pub fn area(ring: &[Pt]) -> f64 { +pub fn area(ring: &[Pt]) -> Float { let mut i = 0; let n = ring.len() - 1; let mut area = ring[n - 1].y * ring[0].x - ring[n - 1].x * ring[0].y; @@ -51,7 +51,7 @@ fn ring_contains(ring: &[Pt], point: &Pt) -> i32 { fn segment_contains(a: &Pt, b: &Pt, c: &Pt) -> bool { if collinear(a, b, c) { - if (a.x - b.x).abs() < std::f64::EPSILON { + if (a.x - b.x).abs() < Float::EPSILON { within(a.y, c.y, b.y) } else { within(a.x, c.x, b.x) @@ -62,9 +62,9 @@ fn segment_contains(a: &Pt, b: &Pt, c: &Pt) -> bool { } fn collinear(a: &Pt, b: &Pt, c: &Pt) -> bool { - ((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y)).abs() < std::f64::EPSILON + ((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y)).abs() < Float::EPSILON } -fn within(p: f64, q: f64, r: f64) -> bool { +fn within(p: Float, q: Float, r: Float) -> bool { p <= q && q <= r || r <= q && q <= p } diff --git a/src/band.rs b/src/band.rs index d537944..b41249b 100644 --- a/src/band.rs +++ b/src/band.rs @@ -1,31 +1,32 @@ +use crate::Float; use geo_types::MultiPolygon; /// An isoband has the geometry and min / max values of a contour ring, built by [`ContourBuilder`]. #[derive(Debug, Clone)] pub struct Band { - pub(crate) geometry: MultiPolygon, - pub(crate) min_v: f64, - pub(crate) max_v: f64, + pub(crate) geometry: MultiPolygon, + pub(crate) min_v: Float, + pub(crate) max_v: Float, } impl Band { /// Borrow the [`MultiPolygon`](geo_types::MultiPolygon) geometry of this contour. - pub fn geometry(&self) -> &MultiPolygon { + 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, f64, f64) { + pub fn into_inner(self) -> (MultiPolygon, Float, Float) { (self.geometry, self.min_v, self.max_v) } /// Get the minimum value used to construct this band. - pub fn min_v(&self) -> f64 { + pub fn min_v(&self) -> Float { self.min_v } /// Get the maximum value used to construct this band. - pub fn max_v(&self) -> f64 { + pub fn max_v(&self) -> Float { self.max_v } diff --git a/src/contour.rs b/src/contour.rs index 0ed92b6..37ed75e 100644 --- a/src/contour.rs +++ b/src/contour.rs @@ -1,25 +1,26 @@ +use crate::Float; use geo_types::MultiPolygon; /// A contour has the geometry and threshold of a contour ring, built by [`ContourBuilder`]. #[derive(Debug, Clone)] pub struct Contour { - pub(crate) geometry: MultiPolygon, - pub(crate) threshold: f64, + pub(crate) geometry: MultiPolygon, + pub(crate) threshold: Float, } impl Contour { /// Borrow the [`MultiPolygon`](geo_types::MultiPolygon) geometry of this contour. - pub fn geometry(&self) -> &MultiPolygon { + pub fn geometry(&self) -> &MultiPolygon { &self.geometry } /// Get the owned polygons and threshold of this contour. - pub fn into_inner(self) -> (MultiPolygon, f64) { + pub fn into_inner(self) -> (MultiPolygon, Float) { (self.geometry, self.threshold) } /// Get the threshold used to construct this contour. - pub fn threshold(&self) -> f64 { + pub fn threshold(&self) -> Float { self.threshold } diff --git a/src/contourbuilder.rs b/src/contourbuilder.rs index 813171c..d85f466 100644 --- a/src/contourbuilder.rs +++ b/src/contourbuilder.rs @@ -1,7 +1,7 @@ use crate::area::{area, contains}; use crate::error::{new_error, ErrorKind, Result}; use crate::isoringbuilder::IsoRingBuilder; -use crate::{Band, Contour, Line, Ring}; +use crate::{Band, Contour, Float, Line, Ring}; use geo_types::{LineString, MultiLineString, MultiPolygon, Polygon}; use rustc_hash::FxHashMap; @@ -18,13 +18,13 @@ pub struct ContourBuilder { /// Whether to smooth the contours smooth: bool, /// The horizontal coordinate for the origin of the grid. - x_origin: f64, + x_origin: Float, /// The vertical coordinate for the origin of the grid. - y_origin: f64, + y_origin: Float, /// The horizontal step for the grid - x_step: f64, + x_step: Float, /// The vertical step for the grid - y_step: f64, + y_step: Float, } impl ContourBuilder { @@ -43,38 +43,38 @@ impl ContourBuilder { dx, dy, smooth, - x_origin: 0f64, - y_origin: 0f64, - x_step: 1f64, - y_step: 1f64, + x_origin: 0., + y_origin: 0., + x_step: 1., + y_step: 1., } } /// Sets the x origin of the grid. - pub fn x_origin(mut self, x_origin: impl Into) -> Self { + pub fn x_origin(mut self, x_origin: impl Into) -> Self { self.x_origin = x_origin.into(); self } /// Sets the y origin of the grid. - pub fn y_origin(mut self, y_origin: impl Into) -> Self { + pub fn y_origin(mut self, y_origin: impl Into) -> Self { self.y_origin = y_origin.into(); self } /// Sets the x step of the grid. - pub fn x_step(mut self, x_step: impl Into) -> Self { + pub fn x_step(mut self, x_step: impl Into) -> Self { self.x_step = x_step.into(); self } /// Sets the y step of the grid. - pub fn y_step(mut self, y_step: impl Into) -> Self { + pub fn y_step(mut self, y_step: impl Into) -> Self { self.y_step = y_step.into(); self } - fn smoooth_linear(&self, ring: &mut Ring, values: &[f64], value: f64) { + fn smoooth_linear(&self, ring: &mut Ring, values: &[Float], value: Float) { let dx = self.dx; let dy = self.dy; let len_values = values.len(); @@ -89,11 +89,11 @@ impl ContourBuilder { let ix = (yt * dx + xt) as usize; if ix < len_values { let v1 = values[ix]; - if x > 0.0 && x < (dx as f64) && (xt as f64 - x).abs() < std::f64::EPSILON { + if x > 0.0 && x < (dx as Float) && (xt as Float - x).abs() < Float::EPSILON { v0 = values[(yt * dx + xt - 1) as usize]; point.x = x + (value - v0) / (v1 - v0) - 0.5; } - if y > 0.0 && y < (dy as f64) && (yt as f64 - y).abs() < std::f64::EPSILON { + if y > 0.0 && y < (dy as Float) && (yt as Float - y).abs() < Float::EPSILON { v0 = values[((yt - 1) * dx + xt) as usize]; point.y = y + (value - v0) / (v1 - v0) - 0.5; } @@ -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: &[f64], thresholds: &[f64]) -> Result> { + pub fn lines(&self, values: &[Float], thresholds: &[Float]) -> Result> { if values.len() as u32 != self.dx * self.dy { return Err(new_error(ErrorKind::BadDimension)); } @@ -122,7 +122,12 @@ impl ContourBuilder { .collect() } - fn line(&self, values: &[f64], threshold: f64, isoring: &mut IsoRingBuilder) -> Result { + fn line( + &self, + values: &[Float], + threshold: Float, + isoring: &mut IsoRingBuilder, + ) -> Result { let mut result = isoring.compute(values, threshold)?; let mut linestrings = Vec::new(); @@ -132,8 +137,8 @@ impl ContourBuilder { self.smoooth_linear(&mut ring, values, threshold); } // Compute the polygon coordinates according to the grid properties if needed - if (self.x_origin, self.y_origin) != (0f64, 0f64) - || (self.x_step, self.y_step) != (1f64, 1f64) + if (self.x_origin, self.y_origin) != (0.0, 0.0) + || (self.x_step, self.y_step) != (1.0, 1.0) { ring.iter_mut().for_each(|point| { point.x = point.x * self.x_step + self.x_origin; @@ -143,7 +148,7 @@ impl ContourBuilder { linestrings.push(LineString(ring)); }); Ok(Line { - geometry: MultiLineString(linestrings), + geometry: MultiLineString::(linestrings), threshold, }) } @@ -157,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: &[f64], thresholds: &[f64]) -> Result> { + pub fn contours(&self, values: &[Float], thresholds: &[Float]) -> Result> { if values.len() as u32 != self.dx * self.dy { return Err(new_error(ErrorKind::BadDimension)); } @@ -170,8 +175,8 @@ impl ContourBuilder { fn contour( &self, - values: &[f64], - threshold: f64, + values: &[Float], + threshold: Float, isoring: &mut IsoRingBuilder, ) -> Result { let (mut polygons, mut holes) = (Vec::new(), Vec::new()); @@ -183,8 +188,8 @@ impl ContourBuilder { self.smoooth_linear(&mut ring, values, threshold); } // Compute the polygon coordinates according to the grid properties if needed - if (self.x_origin, self.y_origin) != (0f64, 0f64) - || (self.x_step, self.y_step) != (1f64, 1f64) + if (self.x_origin, self.y_origin) != (0.0, 0.0) + || (self.x_step, self.y_step) != (1.0, 1.0) { ring.iter_mut().for_each(|point| { point.x = point.x * self.x_step + self.x_origin; @@ -192,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)); } @@ -208,7 +213,7 @@ impl ContourBuilder { }); Ok(Contour { - geometry: MultiPolygon(polygons), + geometry: MultiPolygon::(polygons), threshold, }) } @@ -223,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: &[f64], thresholds: &[f64]) -> Result> { + pub fn isobands(&self, values: &[Float], thresholds: &[Float]) -> 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. @@ -249,8 +254,8 @@ impl ContourBuilder { } ring.dedup(); // Compute the polygon coordinates according to the grid properties if needed - if (self.x_origin, self.y_origin) != (0f64, 0f64) - || (self.x_step, self.y_step) != (1f64, 1f64) + if (self.x_origin, self.y_origin) != (0.0, 0.0) + || (self.x_step, self.y_step) != (1.0, 1.0) { ring.iter_mut().for_each(|point| { point.x = point.x * self.x_step + self.x_origin; @@ -263,7 +268,7 @@ impl ContourBuilder { .collect::>(); Ok((rings, *threshold)) }) - .collect::, f64)>>>()?; + .collect::, Float)>>>()?; // We now have the rings for each isolines for all the given thresholds, // we can iterate over them in pairs to compute the isobands. @@ -304,12 +309,12 @@ impl ContourBuilder { enclosed_by_n.insert(i, enclosed_by_j); } - let mut polygons: Vec> = Vec::new(); - let mut interior_rings: Vec> = Vec::new(); + let mut polygons: Vec> = Vec::new(); + let mut interior_rings: Vec> = Vec::new(); 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()); } @@ -326,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 e115c63..f53dd30 100644 --- a/src/isoringbuilder.rs +++ b/src/isoringbuilder.rs @@ -1,12 +1,12 @@ use crate::error::{new_error, ErrorKind, Result}; -use crate::{Pt, Ring}; +use crate::{Float, Pt, Ring}; use lazy_static::lazy_static; use rustc_hash::FxHashMap; use slab::Slab; lazy_static! { #[rustfmt::skip] - static ref CASES: Vec>>> = vec![ + static ref CASES: Vec>>> = vec![ vec![], vec![vec![vec![1.0, 1.5], vec![0.5, 1.0]]], vec![vec![vec![1.5, 1.0], vec![1.0, 1.5]]], @@ -49,7 +49,7 @@ 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: &[f64], threshold: f64, dx: u32, dy: u32) -> Result> { +pub fn contour_rings(values: &[Float], threshold: Float, dx: u32, dy: u32) -> Result> { let mut isoring = IsoRingBuilder::new(dx, dy); isoring.compute(values, threshold) } @@ -89,7 +89,7 @@ impl IsoRingBuilder { /// /// * `values` - The slice of values to be used. /// * `threshold` - The threshold value to use. - pub fn compute(&mut self, values: &[f64], threshold: f64) -> Result> { + pub fn compute(&mut self, values: &[Float], threshold: Float) -> Result> { macro_rules! case_stitch { ($ix:expr, $x:ident, $y:ident, $result:expr) => { CASES[$ix] @@ -166,18 +166,24 @@ impl IsoRingBuilder { } fn index(&self, point: &Pt) -> usize { - (point.x * 2.0 + point.y * (self.dx as f64 + 1.) * 4.) as usize + (point.x * 2.0 + point.y * (self.dx as Float + 1.) * 4.) as usize } // Stitchs segments to rings. - fn stitch(&mut self, line: &[Vec], x: i32, y: i32, result: &mut Vec) -> Result<()> { + fn stitch( + &mut self, + line: &[Vec], + x: i32, + y: i32, + result: &mut Vec, + ) -> Result<()> { let start = Pt { - x: line[0][0] + x as f64, - y: line[0][1] + y as f64, + x: line[0][0] + x as Float, + y: line[0][1] + y as Float, }; let end = Pt { - x: line[1][0] + x as f64, - y: line[1][1] + y as f64, + x: line[1][0] + x as Float, + y: line[1][1] + y as Float, }; let start_index = self.index(&start); let end_index = self.index(&end); @@ -212,7 +218,7 @@ impl IsoRingBuilder { .fragment_by_end .remove(&start_index) .ok_or_else(|| new_error(ErrorKind::Unexpected))?; - let mut f = self + let f = self .f .get_mut(f_ix) .ok_or_else(|| new_error(ErrorKind::Unexpected))?; @@ -251,7 +257,7 @@ impl IsoRingBuilder { .fragment_by_start .remove(&end_index) .ok_or_else(|| new_error(ErrorKind::Unexpected))?; - let mut f = self + let f = self .f .get_mut(f_ix) .ok_or_else(|| new_error(ErrorKind::Unexpected))?; diff --git a/src/lib.rs b/src/lib.rs index a7431c1..39fa9cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,15 @@ mod error; mod isoringbuilder; mod line; +#[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 Ring = Vec; pub use crate::band::Band; @@ -74,7 +82,7 @@ pub use crate::line::Line; #[cfg(test)] mod tests { - use crate::ContourBuilder; + use crate::{ContourBuilder, Float}; use geo_types::{line_string, polygon, MultiLineString, MultiPolygon}; #[test] @@ -133,7 +141,7 @@ mod tests { ], &[0.5]).unwrap(); assert_eq!( res[0].geometry(), - &MultiPolygon(vec![polygon![ + &MultiPolygon::(vec![polygon![ (x: 6., y: 7.5), (x: 6., y: 6.5), (x: 6., y: 5.5), @@ -173,7 +181,7 @@ mod tests { ], &[0.5]).unwrap(); assert_eq!( res[0].geometry(), - &MultiLineString(vec![line_string![ + &MultiLineString::(vec![line_string![ (x: 6., y: 7.5), (x: 6., y: 6.5), (x: 6., y: 5.5), @@ -213,7 +221,7 @@ mod tests { ], &[0.5]).unwrap(); assert_eq!( res[0].geometry(), - &MultiPolygon(vec![polygon! { + &MultiPolygon::(vec![polygon! { exterior: [ (x: 6., y: 7.5), (x: 6., y: 6.5), @@ -266,7 +274,7 @@ mod tests { ], &[0.5]).unwrap(); assert_eq!( res[0].geometry(), - &MultiPolygon(vec![ + &MultiPolygon::(vec![ polygon![ (x: 5., y: 7.5), (x: 5., y: 6.5), @@ -321,7 +329,7 @@ mod tests { ], &[0.5]).unwrap(); assert_eq!( res[0].geometry(), - &MultiPolygon(vec![ + &MultiPolygon::(vec![ polygon! { exterior: [ (x: 4., y: 5.5), @@ -392,7 +400,7 @@ mod tests { ], &[0.5]).unwrap(); assert_eq!( res[0].geometry(), - &MultiPolygon(vec![polygon![ + &MultiPolygon::(vec![polygon![ (x: 6., y: 7.5), (x: 6., y: 6.5), (x: 6., y: 5.5), @@ -433,7 +441,7 @@ mod tests { ], &[0.5, 1.5]).unwrap(); assert_eq!( res[0].geometry(), - &MultiPolygon(vec![polygon![ + &MultiPolygon::(vec![polygon![ (x: 7., y: 8.5), (x: 7., y: 7.5), (x: 7., y: 6.5), @@ -459,7 +467,7 @@ mod tests { ); assert_eq!( res[1].geometry(), - &MultiPolygon(vec![polygon![ + &MultiPolygon::(vec![polygon![ (x: 6., y: 6.5), (x: 6., y: 5.5), (x: 5.5, y: 5.), @@ -476,10 +484,10 @@ mod tests { #[test] fn test_multipolygon_with_x_y_steps() { let c = ContourBuilder::new(10, 10, true) - .x_step(2) - .y_step(2) - .x_origin(100) - .y_origin(200); + .x_step(2.0) + .y_step(2.0) + .x_origin(100.0) + .y_origin(200.0); #[rustfmt::skip] let res = c.contours(&[ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., @@ -496,7 +504,7 @@ mod tests { assert_eq!( res[0].geometry(), - &MultiPolygon(vec![ + &MultiPolygon::(vec![ polygon![ (x: 110.0, y: 215.0), (x: 110.0, y: 213.0), diff --git a/src/line.rs b/src/line.rs index abef5ba..967534c 100644 --- a/src/line.rs +++ b/src/line.rs @@ -1,25 +1,26 @@ +use crate::Float; use geo_types::MultiLineString; /// A line has the geometry and threshold of a contour ring, built by [`ContourBuilder`]. #[derive(Debug, Clone)] pub struct Line { - pub(crate) geometry: MultiLineString, - pub(crate) threshold: f64, + pub(crate) geometry: MultiLineString, + pub(crate) threshold: Float, } impl Line { /// Borrow the [`MultiLineString`](geo_types::MultiLineString) geometry of this contour. - pub fn geometry(&self) -> &MultiLineString { + pub fn geometry(&self) -> &MultiLineString { &self.geometry } /// Get the owned lines and threshold of this contour. - pub fn into_inner(self) -> (MultiLineString, f64) { + pub fn into_inner(self) -> (MultiLineString, Float) { (self.geometry, self.threshold) } /// Get the threshold used to construct this isoline. - pub fn threshold(&self) -> f64 { + pub fn threshold(&self) -> Float { self.threshold }