|
1 | 1 | use crate::area::{area, contains}; |
2 | 2 | use crate::error::{new_error, ErrorKind, Result}; |
3 | | -use geo_types::{LineString, MultiPolygon, Polygon}; |
| 3 | +use geo_types::{LineString, MultiLineString, MultiPolygon, Polygon}; |
4 | 4 | use lazy_static::lazy_static; |
5 | 5 | use rustc_hash::FxHashMap; |
6 | 6 | use slab::Slab; |
@@ -140,8 +140,58 @@ impl ContourBuilder { |
140 | 140 | .for_each(drop); |
141 | 141 | } |
142 | 142 |
|
| 143 | + /// Computes isolines according the given input `values` and the given `thresholds`. |
| 144 | + /// Returns a `Vec` of [`Line`] (that can easily be transformed |
| 145 | + /// to GeoJSON Features of MultiLineString). |
| 146 | + /// The threshold value of each Feature is stored in its `value` property. |
| 147 | + /// |
| 148 | + /// # Arguments |
| 149 | + /// |
| 150 | + /// * `values` - The slice of values to be used. |
| 151 | + /// * `thresholds` - The slice of thresholds values to be used. |
| 152 | + pub fn lines(&self, values: &[f64], thresholds: &[f64]) -> Result<Vec<Line>> { |
| 153 | + if values.len() as u32 != self.dx * self.dy { |
| 154 | + return Err(new_error(ErrorKind::BadDimension)); |
| 155 | + } |
| 156 | + let mut isoring = IsoRingBuilder::new(self.dx, self.dy); |
| 157 | + thresholds |
| 158 | + .iter() |
| 159 | + .map(|threshold| self.line(values, *threshold, &mut isoring)) |
| 160 | + .collect() |
| 161 | + } |
| 162 | + |
| 163 | + fn line(&self, values: &[f64], threshold: f64, isoring: &mut IsoRingBuilder) -> Result<Line> { |
| 164 | + let mut result = isoring.compute(values, threshold)?; |
| 165 | + let mut linestrings = Vec::new(); |
| 166 | + |
| 167 | + result |
| 168 | + .drain(..) |
| 169 | + .map(|mut ring| { |
| 170 | + // Smooth the ring if needed |
| 171 | + if self.smooth { |
| 172 | + self.smoooth_linear(&mut ring, values, threshold); |
| 173 | + } |
| 174 | + // Compute the polygon coordinates according to the grid properties if needed |
| 175 | + if (self.x_origin, self.y_origin) != (0f64, 0f64) |
| 176 | + || (self.x_step, self.y_step) != (1f64, 1f64) |
| 177 | + { |
| 178 | + ring.iter_mut() |
| 179 | + .map(|point| { |
| 180 | + point.x = point.x * self.x_step + self.x_origin; |
| 181 | + point.y = point.y * self.y_step + self.y_origin; |
| 182 | + }) |
| 183 | + .for_each(drop); |
| 184 | + } |
| 185 | + linestrings.push(LineString(ring)); |
| 186 | + }).for_each(drop); |
| 187 | + Ok(Line { |
| 188 | + geometry: MultiLineString(linestrings), |
| 189 | + threshold, |
| 190 | + }) |
| 191 | + } |
143 | 192 | /// Computes contours according the given input `values` and the given `thresholds`. |
144 | | - /// Returns a `Vec` of GeoJSON Features of MultiPolygon. |
| 193 | + /// Returns a `Vec` of [`Contour`] (that can easily be transformed |
| 194 | + /// to GeoJSON Features of MultiPolygon). |
145 | 195 | /// The threshold value of each Feature is stored in its `value` property. |
146 | 196 | /// |
147 | 197 | /// # Arguments |
@@ -213,6 +263,71 @@ impl ContourBuilder { |
213 | 263 | } |
214 | 264 | } |
215 | 265 |
|
| 266 | +/// A line has the geometry and threshold of a contour ring, built by [`ContourBuilder`]. |
| 267 | +#[derive(Debug, Clone)] |
| 268 | +pub struct Line { |
| 269 | + geometry: MultiLineString, |
| 270 | + threshold: f64, |
| 271 | +} |
| 272 | + |
| 273 | +impl Line { |
| 274 | + /// Borrow the [`MultiPolygon`](geo_types::MultiPolygon) geometry of this contour. |
| 275 | + pub fn geometry(&self) -> &MultiLineString { |
| 276 | + &self.geometry |
| 277 | + } |
| 278 | + |
| 279 | + /// Get the owned polygons and threshold of this countour. |
| 280 | + pub fn into_inner(self) -> (MultiLineString, f64) { |
| 281 | + (self.geometry, self.threshold) |
| 282 | + } |
| 283 | + |
| 284 | + /// Get the threshold used to construct this contour. |
| 285 | + pub fn threshold(&self) -> f64 { |
| 286 | + self.threshold |
| 287 | + } |
| 288 | + |
| 289 | + #[cfg(feature = "geojson")] |
| 290 | + /// Convert the line to a struct from the `geojson` crate. |
| 291 | + /// |
| 292 | + /// To get a string representation, call to_geojson().to_string(). |
| 293 | + /// ``` |
| 294 | + /// use contour::ContourBuilder; |
| 295 | + /// |
| 296 | + /// let builder = ContourBuilder::new(10, 10, false); |
| 297 | + /// # #[rustfmt::skip] |
| 298 | + /// let contours = builder.lines(&[ |
| 299 | + /// // ...ellided for brevity |
| 300 | + /// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., |
| 301 | + /// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., |
| 302 | + /// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., |
| 303 | + /// # 0., 0., 0., 2., 1., 2., 0., 0., 0., 0., |
| 304 | + /// # 0., 0., 0., 2., 2., 2., 0., 0., 0., 0., |
| 305 | + /// # 0., 0., 0., 1., 2., 1., 0., 0., 0., 0., |
| 306 | + /// # 0., 0., 0., 2., 2., 2., 0., 0., 0., 0., |
| 307 | + /// # 0., 0., 0., 2., 1., 2., 0., 0., 0., 0., |
| 308 | + /// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., |
| 309 | + /// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0. |
| 310 | + /// ], &[0.5]).unwrap(); |
| 311 | + /// |
| 312 | + /// let geojson_string = contours[0].to_geojson().to_string(); |
| 313 | + /// |
| 314 | + /// assert_eq!(&geojson_string[0..27], r#"{"geometry":{"coordinates":"#); |
| 315 | + /// ``` |
| 316 | + pub fn to_geojson(&self) -> geojson::Feature { |
| 317 | + let mut properties = geojson::JsonObject::with_capacity(1); |
| 318 | + properties.insert("threshold".to_string(), self.threshold.into()); |
| 319 | + |
| 320 | + geojson::Feature { |
| 321 | + bbox: None, |
| 322 | + geometry: Some(geojson::Geometry::from(self.geometry())), |
| 323 | + id: None, |
| 324 | + properties: Some(properties), |
| 325 | + foreign_members: None, |
| 326 | + } |
| 327 | + } |
| 328 | + |
| 329 | +} |
| 330 | + |
216 | 331 | /// A contour has the geometry and threshold of a contour ring, built by [`ContourBuilder`]. |
217 | 332 | #[derive(Debug, Clone)] |
218 | 333 | pub struct Contour { |
|
0 commit comments