11use crate :: area:: { area, contains} ;
22use crate :: error:: { new_error, ErrorKind , Result } ;
3- use geojson:: Value :: MultiPolygon ;
4- use geojson:: { Feature , Geometry } ;
3+ use geo_types:: { LineString , MultiPolygon , Polygon } ;
54use lazy_static:: lazy_static;
65use rustc_hash:: FxHashMap ;
7- use serde_json:: map:: Map ;
8- use serde_json:: to_value;
96use slab:: Slab ;
107
11- pub type Pt = Vec < f64 > ;
8+ pub type Pt = geo_types :: Coordinate ;
129pub type Ring = Vec < Pt > ;
1310
1411lazy_static ! {
@@ -48,7 +45,7 @@ struct Fragment {
4845
4946/// Contours generator, using builder pattern, to
5047/// be used on a rectangular `Slice` of values to
51- /// get a `Vec` of Features of MultiPolygon (use [`contour_rings`] internally).
48+ /// get a `Vec` of [`Contour`] (uses [`contour_rings`] internally).
5249///
5350/// [`contour_rings`]: fn.contour_rings.html
5451pub struct ContourBuilder {
@@ -122,8 +119,8 @@ impl ContourBuilder {
122119
123120 ring. iter_mut ( )
124121 . map ( |point| {
125- let x = point[ 0 ] ;
126- let y = point[ 1 ] ;
122+ let x = point. x ;
123+ let y = point. y ;
127124 let xt = x. trunc ( ) as u32 ;
128125 let yt = y. trunc ( ) as u32 ;
129126 let mut v0;
@@ -132,11 +129,11 @@ impl ContourBuilder {
132129 let v1 = values[ ix] ;
133130 if x > 0.0 && x < ( dx as f64 ) && ( xt as f64 - x) . abs ( ) < std:: f64:: EPSILON {
134131 v0 = values[ ( yt * dx + xt - 1 ) as usize ] ;
135- point[ 0 ] = x + ( value - v0) / ( v1 - v0) - 0.5 ;
132+ point. x = x + ( value - v0) / ( v1 - v0) - 0.5 ;
136133 }
137134 if y > 0.0 && y < ( dy as f64 ) && ( yt as f64 - y) . abs ( ) < std:: f64:: EPSILON {
138135 v0 = values[ ( ( yt - 1 ) * dx + xt) as usize ] ;
139- point[ 1 ] = y + ( value - v0) / ( v1 - v0) - 0.5 ;
136+ point. y = y + ( value - v0) / ( v1 - v0) - 0.5 ;
140137 }
141138 }
142139 } )
@@ -151,23 +148,23 @@ impl ContourBuilder {
151148 ///
152149 /// * `values` - The slice of values to be used.
153150 /// * `thresholds` - The slice of thresholds values to be used.
154- pub fn contours ( & self , values : & [ f64 ] , thresholds : & [ f64 ] ) -> Result < Vec < Feature > > {
151+ pub fn contours ( & self , values : & [ f64 ] , thresholds : & [ f64 ] ) -> Result < Vec < Contour > > {
155152 if values. len ( ) as u32 != self . dx * self . dy {
156153 return Err ( new_error ( ErrorKind :: BadDimension ) ) ;
157154 }
158155 let mut isoring = IsoRingBuilder :: new ( self . dx , self . dy ) ;
159156 thresholds
160157 . iter ( )
161- . map ( |value | self . contour ( values, * value , & mut isoring) )
162- . collect :: < Result < Vec < Feature > > > ( )
158+ . map ( |threshold | self . contour ( values, * threshold , & mut isoring) )
159+ . collect ( )
163160 }
164161
165162 fn contour (
166163 & self ,
167164 values : & [ f64 ] ,
168165 threshold : f64 ,
169166 isoring : & mut IsoRingBuilder ,
170- ) -> Result < Feature > {
167+ ) -> Result < Contour > {
171168 let ( mut polygons, mut holes) = ( Vec :: new ( ) , Vec :: new ( ) ) ;
172169 let mut result = isoring. compute ( values, threshold) ?;
173170
@@ -184,15 +181,15 @@ impl ContourBuilder {
184181 {
185182 ring. iter_mut ( )
186183 . map ( |point| {
187- point[ 0 ] = point[ 0 ] * self . x_step + self . x_origin ;
188- point[ 1 ] = point[ 1 ] * self . y_step + self . y_origin ;
184+ point. x = point. x * self . x_step + self . x_origin ;
185+ point. y = point. y * self . y_step + self . y_origin ;
189186 } )
190187 . for_each ( drop) ;
191188 }
192189 if area ( & ring) > 0.0 {
193- polygons. push ( vec ! [ ring ] ) ;
190+ polygons. push ( Polygon :: new ( LineString :: new ( ring ) , vec ! [ ] ) )
194191 } else {
195- holes. push ( ring) ;
192+ holes. push ( LineString :: new ( ring) ) ;
196193 }
197194 } )
198195 . for_each ( drop) ;
@@ -201,27 +198,84 @@ impl ContourBuilder {
201198 . drain ( ..)
202199 . map ( |hole| {
203200 for polygon in & mut polygons {
204- if contains ( & polygon[ 0 ] , & hole) != -1 {
205- polygon. push ( hole) ;
201+ if contains ( & polygon. exterior ( ) . 0 , & hole. 0 ) != -1 {
202+ polygon. interiors_push ( hole) ;
206203 return ;
207204 }
208205 }
209206 } )
210207 . for_each ( drop) ;
211208
212- let mut properties = Map :: with_capacity ( 1 ) ;
213- properties. insert ( String :: from ( "value" ) , to_value ( threshold) ?) ;
214- Ok ( Feature {
215- geometry : Some ( Geometry {
216- value : MultiPolygon ( polygons) ,
217- bbox : None ,
218- foreign_members : None ,
219- } ) ,
220- properties : Some ( properties) ,
209+ Ok ( Contour {
210+ geometry : MultiPolygon ( polygons) ,
211+ threshold,
212+ } )
213+ }
214+ }
215+
216+ /// A contour has the geometry and threshold of a contour ring, built by [`ContourBuilder`].
217+ #[ derive( Debug , Clone ) ]
218+ pub struct Contour {
219+ geometry : MultiPolygon ,
220+ threshold : f64 ,
221+ }
222+
223+ impl Contour {
224+ /// Borrow the [`MultiPolygon`](geo_types::MultiPolygon) geometry of this contour.
225+ pub fn geometry ( & self ) -> & MultiPolygon {
226+ & self . geometry
227+ }
228+
229+ /// Get the owned polygons and threshold of this countour.
230+ pub fn into_inner ( self ) -> ( MultiPolygon , f64 ) {
231+ ( self . geometry , self . threshold )
232+ }
233+
234+ /// Get the threshold used to construct this contour.
235+ pub fn threshold ( & self ) -> f64 {
236+ self . threshold
237+ }
238+
239+ #[ cfg( feature = "geojson" ) ]
240+ /// Convert the contour to a struct from the `geojson` crate.
241+ ///
242+ /// To get a string reresentation, call to_geojson().to_string().
243+ /// ```
244+ /// use contour::ContourBuilder;
245+ ///
246+ /// let builder = ContourBuilder::new(10, 10, false);
247+ /// # #[rustfmt::skip]
248+ /// let contours = builder.contours(&[
249+ /// // ...ellided for brevity
250+ /// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
251+ /// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
252+ /// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
253+ /// # 0., 0., 0., 2., 1., 2., 0., 0., 0., 0.,
254+ /// # 0., 0., 0., 2., 2., 2., 0., 0., 0., 0.,
255+ /// # 0., 0., 0., 1., 2., 1., 0., 0., 0., 0.,
256+ /// # 0., 0., 0., 2., 2., 2., 0., 0., 0., 0.,
257+ /// # 0., 0., 0., 2., 1., 2., 0., 0., 0., 0.,
258+ /// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
259+ /// # 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.
260+ /// ], &[0.5]).unwrap();
261+ ///
262+ /// let geojson_string = contours[0].to_geojson().to_string();
263+ ///
264+ /// assert_eq!(&geojson_string[0..27], r#"{"geometry":{"coordinates":"#);
265+ /// ```
266+ pub fn to_geojson ( & self ) -> geojson:: Feature {
267+ let mut properties = geojson:: JsonObject :: with_capacity ( 1 ) ;
268+ // REVIEW: This maintains existing behavior, but should we rename
269+ // `value` to something more explicable, like `threshold`?
270+ properties. insert ( "value" . to_string ( ) , self . threshold . into ( ) ) ;
271+
272+ geojson:: Feature {
221273 bbox : None ,
274+ geometry : Some ( geojson:: Geometry :: from ( self . geometry ( ) ) ) ,
222275 id : None ,
276+ properties : Some ( properties) ,
223277 foreign_members : None ,
224- } )
278+ }
225279 }
226280}
227281
@@ -351,14 +405,20 @@ impl IsoRingBuilder {
351405 Ok ( result)
352406 }
353407
354- fn index ( & self , point : & [ f64 ] ) -> usize {
355- ( point[ 0 ] * 2.0 + point[ 1 ] * ( self . dx as f64 + 1. ) * 4. ) as usize
408+ fn index ( & self , point : & Pt ) -> usize {
409+ ( point. x * 2.0 + point. y * ( self . dx as f64 + 1. ) * 4. ) as usize
356410 }
357411
358412 // Stitchs segments to rings.
359413 fn stitch ( & mut self , line : & [ Vec < f64 > ] , x : i32 , y : i32 , result : & mut Vec < Ring > ) -> Result < ( ) > {
360- let start = vec ! [ line[ 0 ] [ 0 ] + x as f64 , line[ 0 ] [ 1 ] + y as f64 ] ;
361- let end = vec ! [ line[ 1 ] [ 0 ] + x as f64 , line[ 1 ] [ 1 ] + y as f64 ] ;
414+ let start = Pt {
415+ x : line[ 0 ] [ 0 ] + x as f64 ,
416+ y : line[ 0 ] [ 1 ] + y as f64 ,
417+ } ;
418+ let end = Pt {
419+ x : line[ 1 ] [ 0 ] + x as f64 ,
420+ y : line[ 1 ] [ 1 ] + y as f64 ,
421+ } ;
362422 let start_index = self . index ( & start) ;
363423 let end_index = self . index ( & end) ;
364424 if self . fragment_by_end . contains_key ( & start_index) {
@@ -458,3 +518,54 @@ impl IsoRingBuilder {
458518 self . is_empty = true ;
459519 }
460520}
521+
522+ #[ cfg( test) ]
523+ mod tests {
524+
525+ #[ cfg( feature = "geojson" ) ]
526+ #[ test]
527+ fn test_simple_polygon_no_smoothing_geojson ( ) {
528+ use super :: * ;
529+ let c = ContourBuilder :: new ( 10 , 10 , false ) ;
530+ #[ rustfmt:: skip]
531+ let res = c. contours ( & [
532+ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ,
533+ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ,
534+ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ,
535+ 0. , 0. , 0. , 2. , 1. , 2. , 0. , 0. , 0. , 0. ,
536+ 0. , 0. , 0. , 2. , 2. , 2. , 0. , 0. , 0. , 0. ,
537+ 0. , 0. , 0. , 1. , 2. , 1. , 0. , 0. , 0. , 0. ,
538+ 0. , 0. , 0. , 2. , 2. , 2. , 0. , 0. , 0. , 0. ,
539+ 0. , 0. , 0. , 2. , 1. , 2. , 0. , 0. , 0. , 0. ,
540+ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ,
541+ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.
542+ ] , & [ 0.5 ] ) . unwrap ( ) ;
543+ match res[ 0 ] . to_geojson ( ) . geometry . unwrap ( ) . value {
544+ geojson:: Value :: MultiPolygon ( p) => {
545+ assert_eq ! (
546+ p,
547+ vec![ vec![ vec![
548+ vec![ 6. , 7.5 ] ,
549+ vec![ 6. , 6.5 ] ,
550+ vec![ 6. , 5.5 ] ,
551+ vec![ 6. , 4.5 ] ,
552+ vec![ 6. , 3.5 ] ,
553+ vec![ 5.5 , 3. ] ,
554+ vec![ 4.5 , 3. ] ,
555+ vec![ 3.5 , 3. ] ,
556+ vec![ 3. , 3.5 ] ,
557+ vec![ 3. , 4.5 ] ,
558+ vec![ 3. , 5.5 ] ,
559+ vec![ 3. , 6.5 ] ,
560+ vec![ 3. , 7.5 ] ,
561+ vec![ 3.5 , 8. ] ,
562+ vec![ 4.5 , 8. ] ,
563+ vec![ 5.5 , 8. ] ,
564+ vec![ 6. , 7.5 ] ,
565+ ] ] ]
566+ ) ;
567+ }
568+ _ => panic ! ( "" ) ,
569+ } ;
570+ }
571+ }
0 commit comments