Skip to content

Commit 84bb863

Browse files
committed
Refactor various stuffs
1 parent 4a56da7 commit 84bb863

File tree

14 files changed

+1277
-597
lines changed

14 files changed

+1277
-597
lines changed

Cargo.toml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,20 @@ license = "MIT/Apache-2.0"
1616
geojson = { version = ">=0.16, <=0.24", optional = true }
1717
geo-types= { version = "0.7" }
1818
lazy_static = "1.0"
19-
serde_json = "^1.0"
19+
serde_json = { version = "^1.0", optional = true }
2020
rustc-hash = "1.0"
2121
slab = "0.4"
2222

23+
[dev-dependencies]
24+
serde_json = "^1.0"
25+
26+
[features]
27+
geojson = ["dep:geojson", "dep:serde_json"]
28+
2329
[package.metadata.docs.rs]
2430
all-features = true
2531
rustdoc-args = ["--cfg", "docsrs"]
32+
33+
[[example]]
34+
name = "ex"
35+
required-features = ["geojson"]

README.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44
[![Build status Appveyor](https://ci.appveyor.com/api/projects/status/uemh49tq7vy4uke6?svg=true)](https://ci.appveyor.com/project/mthh/contour-rs)
55
[![Docs.rs version](https://docs.rs/contour/badge.svg)](https://docs.rs/contour/)
66

7-
Computes *isorings* and __*contour polygons*__ by applying [marching squares](https://en.wikipedia.org/wiki/Marching_squares) to a rectangular array of numeric values.
8-
Outputs ring coordinates or polygons contours (represented using geo-types [MultiPolygon](https://docs.rs/geo-types/latest/geo_types/geometry/struct.MultiPolygon.html)s).
9-
For each threshold value, the contour polygon are representing the area where the input values are greater than or equal to the threshold value.
7+
Computes *isolines*, *contour polygons* and *isobands* by applying [marching squares](https://en.wikipedia.org/wiki/Marching_squares) to a rectangular array of numeric values.
8+
Outputs ring coordinates or [geo-types](https://docs.rs/geo-types/) geometries.
109

11-
The generated contours can also easily be serialised to GeoJSON.
10+
The results can also easily be serialised to GeoJSON.
1211

13-
*Note : This is a port of [d3-contour](https://github.com/d3/d3-contour).*
12+
*Note : The core of the algorithm is ported from [d3-contour](https://github.com/d3/d3-contour).*
1413

1514
<div style="text-align:center"><a href="https://mthh.github.io/wasm_demo_contour/"><img src ="https://raw.githubusercontent.com/mthh/contour-rs/master/illustration.png" /></a></div><br>
1615

@@ -31,14 +30,16 @@ extern crate contour;
3130

3231
The API exposes:
3332
- a `ContourBuilder` struct, which computes isorings coordinates for a `Vec` of threshold values and transform them either :
34-
- in `Contour`s (a type containing the threshold value and the geometry as a `MultiPolygon`, easily serializable to GeoJSON), or,
35-
- in `Line`s (a type containing the threshold value and the geometry as a `MultiLineString`, easily serializable to GeoJSON).
36-
33+
- in `Contour`s (a type containing the threshold value and the geometry as a `MultiPolygon`), or,
34+
- in `Line`s (a type containing the threshold value and the geometry as a `MultiLineString`).
35+
- in `Band`s (a type containing a minimum value, a maximum value and the geometry as a `MultiPolygon`).
3736

3837
- a `contour_rings` function, which computes isorings coordinates for a single threshold value (*returns a `Vec` of rings coordinates* - this is what is used internally by the `ContourBuilder`).
3938

4039
`ContourBuilder` is the recommended way to use this crate, as it is more flexible and easier to use (it enables to specify the origin and the step of the grid, and to smooth the contours, while `contour_rings` only speak in grid coordinates and doesn't smooth the resulting rings).
4140

41+
`Line`, `Contour` and `Band` can be serialised to GeoJSON using the `geojson` feature.
42+
4243
## Example:
4344

4445
**Without defining origin and step:**
@@ -147,12 +148,13 @@ Feature {
147148
Demo of this crate compiled to WebAssembly and used from JavaScript : [wasm_demo_contour](https://mthh.github.io/wasm_demo_contour/).
148149

149150
## Difference with the [contour-isobands](https://crates.io/crates/contour-isobands) crate (from [mthh/contour-isobands-rs](https://github.com/mthh/contour-isobands-rs) repository)
150-
While this crate computes isolines (cf. [wikipedia:Marching_squares](https://en.wikipedia.org/wiki/Marching_squares))
151-
and their corresponding polygons (i.e. polygons that contain all points above the threshold defined for a given isoline),
152-
[contour-isobands-rs](https://github.com/mthh/contour-isobands-rs) computes isobands (cf. [wikipedia:Marching_squares#Isobands](https://en.wikipedia.org/wiki/Marching_squares#Isobands))
153-
and their corresponding polygons (i.e. contour polygons that contain all points between a minimum and a maximum bound).
154151

155-
Depending on the desired use of the result, the [`contour-isobands`](https://crates.io/crates/contour-isobands) crate may be more suitable than this [`contour`](https://crates.io/crates/contour) crate.
152+
While this crate computes isolines (cf. [wikipedia:Marching_squares](https://en.wikipedia.org/wiki/Marching_squares)) from which it derives
153+
contour polygons *(i.e. polygons that contain all points above the threshold defined for a given isoline)*
154+
and isobands *(i.e. polygons that contain all points between a minimum and a maximum bound)*,
155+
[contour-isobands-rs](https://github.com/mthh/contour-isobands-rs) is only dedicated to compute isobands and use a slightly different
156+
implementation of the marching squares algorithm for the disambiguation of saddle points (cf. [wikipedia:Marching_squares](https://en.wikipedia.org/wiki/Marching_squares)).
157+
156158

157159
## License
158160

benches/bench.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ static VALUES2: [f64; 238] = [
4343
];
4444

4545
#[bench]
46-
fn bench_build_geojson_contours_multiple_thresholds(b: &mut Bencher) {
46+
fn bench_build_contours_multiple_thresholds(b: &mut Bencher) {
4747
let c = ContourBuilder::new(14, 17, true);
4848
b.iter(|| black_box(c.contours(&VALUES2, &[0.5, 1.5, 2.5])));
4949
}
5050

5151
#[bench]
52-
fn bench_build_geojson_contours_multiple_thresholds_and_x_y_steps_and_origins(b: &mut Bencher) {
52+
fn bench_build_contours_multiple_thresholds_and_x_y_steps_and_origins(b: &mut Bencher) {
5353
let c = ContourBuilder::new(14, 17, true)
5454
.x_step(0.5)
5555
.y_step(0.5)
@@ -79,3 +79,59 @@ fn bench_build_isoring(b: &mut Bencher) {
7979
fn bench_build_isoring_values2(b: &mut Bencher) {
8080
b.iter(|| black_box(contour_rings(&VALUES2, 1.5, 14, 17)));
8181
}
82+
83+
#[bench]
84+
fn bench_contourbuilder_isobands_volcano_without_xy_step_xy_origin(b: &mut Bencher) {
85+
let data_str = include_str!("../tests/fixtures/volcano.json");
86+
let raw_data: serde_json::Value = serde_json::from_str(data_str).unwrap();
87+
let matrix: Vec<f64> = raw_data["data"]
88+
.as_array()
89+
.unwrap()
90+
.iter()
91+
.map(|x| x.as_f64().unwrap())
92+
.collect();
93+
let h = raw_data["height"].as_u64().unwrap() as u32;
94+
let w = raw_data["width"].as_u64().unwrap() as u32;
95+
96+
b.iter(|| {
97+
black_box(
98+
ContourBuilder::new(w, h, true)
99+
.isobands(
100+
&matrix,
101+
&[
102+
90., 95., 100., 105., 110., 115., 120., 125., 130., 135., 140., 145., 150.,
103+
155., 160., 165., 170., 175., 180., 185., 190., 195., 200.,
104+
],
105+
)
106+
.unwrap(),
107+
)
108+
});
109+
}
110+
111+
#[bench]
112+
fn bench_contourbuilder_isobands_pot_pop_fr_without_xy_step_xy_origin(b: &mut Bencher) {
113+
let data_str = include_str!("../tests/fixtures/pot_pop_fr.json");
114+
let raw_data: serde_json::Value = serde_json::from_str(data_str).unwrap();
115+
let matrix: Vec<f64> = raw_data["data"]
116+
.as_array()
117+
.unwrap()
118+
.iter()
119+
.map(|x| x.as_f64().unwrap())
120+
.collect();
121+
let h = raw_data["height"].as_u64().unwrap() as u32;
122+
let w = raw_data["width"].as_u64().unwrap() as u32;
123+
124+
b.iter(|| {
125+
black_box(
126+
ContourBuilder::new(w, h, true)
127+
.isobands(
128+
&matrix,
129+
&[
130+
0.001, 105483.25, 527416.25, 1054832.5, 2109665., 3164497.5, 4219330.,
131+
5274162.5, 6328995., 7383827.5, 8438660., 9704459., 10548326.,
132+
],
133+
)
134+
.unwrap(),
135+
)
136+
});
137+
}

examples/ex.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use contour::ContourBuilder;
2+
use geojson::{FeatureCollection, GeoJson};
3+
use std::fs::File;
4+
use std::io::{BufWriter, Write};
5+
6+
fn main() {
7+
let pot_pop_fr = include_str!("../tests/fixtures/pot_pop_fr.json");
8+
let raw_data: serde_json::Value = serde_json::from_str(pot_pop_fr).unwrap();
9+
let matrix: Vec<f64> = raw_data["data"]
10+
.as_array()
11+
.unwrap()
12+
.iter()
13+
.map(|x| x.as_f64().unwrap())
14+
.collect();
15+
let h = raw_data["height"].as_u64().unwrap() as u32;
16+
let w = raw_data["width"].as_u64().unwrap() as u32;
17+
18+
let x_origin = -6.144721171428571;
19+
let y_origin = 51.78171334283718;
20+
let x_step = 0.11875873095057177;
21+
let y_step = -0.08993203637245273;
22+
23+
let contours = ContourBuilder::new(w, h, true)
24+
.x_step(x_step)
25+
.y_step(y_step)
26+
.x_origin(x_origin)
27+
.y_origin(y_origin)
28+
.isobands(
29+
&matrix,
30+
&[
31+
0., 105483.25, 310000., 527416.25, 850000., 1054832.5, 2109665., 3164497.5,
32+
4219330., 5274162.5, 6328995., 7383827.5, 8438660., 9704459., 10548326.,
33+
],
34+
)
35+
.unwrap();
36+
37+
let features = contours
38+
.iter()
39+
.map(|contour| contour.to_geojson())
40+
.collect::<Vec<geojson::Feature>>();
41+
42+
let geojson_str = GeoJson::from(FeatureCollection {
43+
bbox: None,
44+
features,
45+
foreign_members: None,
46+
})
47+
.to_string();
48+
49+
let mut file_writer = BufWriter::new(File::create("/tmp/example-output.geojson").unwrap());
50+
file_writer.write(&geojson_str.as_bytes()).unwrap();
51+
52+
let volcano = include_str!("../tests/fixtures/volcano.json");
53+
let raw_data: serde_json::Value = serde_json::from_str(volcano).unwrap();
54+
let matrix: Vec<f64> = raw_data["data"]
55+
.as_array()
56+
.unwrap()
57+
.iter()
58+
.map(|x| x.as_f64().unwrap())
59+
.collect();
60+
let h = raw_data["height"].as_u64().unwrap() as u32;
61+
let w = raw_data["width"].as_u64().unwrap() as u32;
62+
63+
let contours = ContourBuilder::new(w, h, true)
64+
.isobands(
65+
&matrix,
66+
&[
67+
90., 95., 100., 105., 110., 115., 120., 125., 130., 135., 140., 145., 150., 155.,
68+
160., 165., 170., 175., 180., 185., 190., 195., 200.,
69+
],
70+
)
71+
.unwrap();
72+
73+
let features = contours
74+
.iter()
75+
.map(|contour| contour.to_geojson())
76+
.collect::<Vec<geojson::Feature>>();
77+
78+
let geojson_str = GeoJson::from(FeatureCollection {
79+
bbox: None,
80+
features,
81+
foreign_members: None,
82+
})
83+
.to_string();
84+
85+
let mut file_writer = BufWriter::new(File::create("/tmp/example-output2.geojson").unwrap());
86+
file_writer.write(&geojson_str.as_bytes()).unwrap();
87+
}

src/area.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::contour::Pt;
1+
use crate::Pt;
22

33
pub fn area(ring: &[Pt]) -> f64 {
44
let mut i = 0;

src/band.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use geo_types::MultiPolygon;
2+
3+
/// An isoband has the geometry and min / max values of a contour ring, built by [`ContourBuilder`].
4+
#[derive(Debug, Clone)]
5+
pub struct Band {
6+
pub(crate) geometry: MultiPolygon,
7+
pub(crate) min_v: f64,
8+
pub(crate) max_v: f64,
9+
}
10+
11+
impl Band {
12+
/// Borrow the [`MultiPolygon`](geo_types::MultiPolygon) geometry of this contour.
13+
pub fn geometry(&self) -> &MultiPolygon {
14+
&self.geometry
15+
}
16+
17+
/// Get the owned polygons and thresholds (min and max) of this band.
18+
pub fn into_inner(self) -> (MultiPolygon, f64, f64) {
19+
(self.geometry, self.min_v, self.max_v)
20+
}
21+
22+
/// Get the minimum value used to construct this band.
23+
pub fn min_v(&self) -> f64 {
24+
self.min_v
25+
}
26+
27+
/// Get the maximum value used to construct this band.
28+
pub fn max_v(&self) -> f64 {
29+
self.max_v
30+
}
31+
32+
#[cfg(feature = "geojson")]
33+
/// Convert the band to a struct from the `geojson` crate.
34+
///
35+
/// To get a string representation, call to_geojson().to_string().
36+
/// ```
37+
/// use contour::ContourBuilder;
38+
///
39+
/// let builder = ContourBuilder::new(10, 10, false);
40+
/// # #[rustfmt::skip]
41+
/// let contours = builder.isobands(&[
42+
/// // ...ellided for brevity
43+
/// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
44+
/// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
45+
/// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
46+
/// # 0., 0., 0., 1., 1., 1., 0., 0., 0., 0.,
47+
/// # 0., 0., 0., 1., 2., 1., 0., 0., 0., 0.,
48+
/// # 0., 0., 0., 1., 2., 1., 0., 0., 0., 0.,
49+
/// # 0., 0., 0., 1., 2., 1., 0., 0., 0., 0.,
50+
/// # 0., 0., 0., 1., 1., 1., 0., 0., 0., 0.,
51+
/// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
52+
/// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.
53+
/// ], &[0.5, 1.5, 2.5]).unwrap();
54+
///
55+
/// let geojson_string = contours[0].to_geojson().to_string();
56+
///
57+
/// assert_eq!(&geojson_string[0..27], r#"{"geometry":{"coordinates":"#);
58+
/// ```
59+
pub fn to_geojson(&self) -> geojson::Feature {
60+
let mut properties = geojson::JsonObject::with_capacity(2);
61+
properties.insert("min_v".to_string(), self.min_v.into());
62+
properties.insert("max_v".to_string(), self.max_v.into());
63+
64+
geojson::Feature {
65+
bbox: None,
66+
geometry: Some(geojson::Geometry::from(self.geometry())),
67+
id: None,
68+
properties: Some(properties),
69+
foreign_members: None,
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)