@@ -108,17 +108,226 @@ function createIsoLines(
108108
109109 const properties = { ...commonProperties , ...breaksProperties [ i ] } ;
110110 properties [ zProperty ] = threshold ;
111- // Pass options to marchingsquares lib to reproduce historical turf
112- // behaviour.
113111 const isoline = multiLineString ( isoContours ( matrix , threshold ) , properties ) ;
114112
115113 results . push ( isoline ) ;
116114 }
117115 return results ;
118116}
119117
120- function isoContours ( _matrix : number [ ] [ ] , _threshold : number ) : Position [ ] [ ] {
121- return [ ] ;
118+ function isoContours ( matrix : number [ ] [ ] , threshold : number ) : Position [ ] [ ] {
119+ const segments : [ Position , Position ] [ ] = [ ] ;
120+
121+ for ( let y = 0 ; y < matrix . length - 1 ; y ++ ) {
122+ const rowLen = matrix [ y ] . length - 1 ;
123+ for ( let x = 0 ; x < rowLen ; x ++ ) {
124+ const tr = matrix [ y ] [ x + 1 ] ;
125+ const br = matrix [ y + 1 ] [ x + 1 ] ;
126+ const bl = matrix [ y + 1 ] [ x ] ;
127+ const tl = matrix [ y ] [ x ] ;
128+
129+ const grid =
130+ ( tl >= threshold ? 8 : 0 ) |
131+ ( tr >= threshold ? 4 : 0 ) |
132+ ( br >= threshold ? 2 : 0 ) |
133+ ( bl >= threshold ? 1 : 0 ) ;
134+
135+ // see https://en.wikipedia.org/wiki/Marching_squares
136+ // the two points in the line segments need to be carefully selected to keep things clockwise
137+ // and allow for efficient contour assembly
138+ switch ( grid ) {
139+ case 0 :
140+ continue ;
141+ case 1 :
142+ segments . push ( [
143+ [ x , y + frac ( tl , bl ) ] ,
144+ [ x + frac ( bl , br ) , y + 1 ] ,
145+ ] ) ;
146+ break ;
147+ case 2 :
148+ segments . push ( [
149+ [ x + frac ( bl , br ) , y + 1 ] ,
150+ [ x + 1 , y + frac ( tr , br ) ] ,
151+ ] ) ;
152+ break ;
153+ case 3 :
154+ segments . push ( [
155+ [ x , y + frac ( tl , bl ) ] ,
156+ [ x + 1 , y + frac ( tr , br ) ] ,
157+ ] ) ;
158+ break ;
159+ case 4 :
160+ segments . push ( [
161+ [ x + 1 , y + frac ( tr , br ) ] ,
162+ [ x + frac ( tl , tr ) , y ] ,
163+ ] ) ;
164+ break ;
165+ case 5 : {
166+ // use the average of the 4 corners to differentiate the saddle case and correctly honor the clockwise winding
167+ const avg = ( tl + tr + br + bl ) / 4 ;
168+ const above = avg >= threshold ;
169+
170+ // TODO We could divide our cell into 4 sub-cells to interpolate a 3rd middle point for each of these segments
171+
172+ if ( above ) {
173+ segments . push (
174+ [
175+ [ x , y + frac ( tl , bl ) ] ,
176+ [ x + frac ( tl , tr ) , y ] ,
177+ ] ,
178+ [
179+ [ x + 1 , y + frac ( tr , br ) ] ,
180+ [ x + frac ( bl , br ) , y + 1 ] ,
181+ ]
182+ ) ;
183+ } else {
184+ segments . push (
185+ [
186+ [ x + 1 , y + frac ( tr , br ) ] ,
187+ [ x + frac ( tl , tr ) , y ] ,
188+ ] ,
189+ [
190+ [ x , y + frac ( tl , bl ) ] ,
191+ [ x + frac ( bl , br ) , y + 1 ] ,
192+ ]
193+ ) ;
194+ }
195+ break ;
196+ }
197+ case 6 :
198+ segments . push ( [
199+ [ x + frac ( bl , br ) , y + 1 ] ,
200+ [ x + frac ( tl , tr ) , y ] ,
201+ ] ) ;
202+ break ;
203+ case 7 :
204+ segments . push ( [
205+ [ x , y + frac ( tl , bl ) ] ,
206+ [ x + frac ( tl , tr ) , y ] ,
207+ ] ) ;
208+ break ;
209+ case 8 :
210+ segments . push ( [
211+ [ x + frac ( tl , tr ) , y ] ,
212+ [ x , y + frac ( tl , bl ) ] ,
213+ ] ) ;
214+ break ;
215+ case 9 :
216+ segments . push ( [
217+ [ x + frac ( tl , tr ) , y ] ,
218+ [ x + frac ( bl , br ) , y + 1 ] ,
219+ ] ) ;
220+ break ;
221+ case 10 : {
222+ const avg = ( tl + tr + br + bl ) / 4 ;
223+ const above = avg >= threshold ;
224+
225+ if ( above ) {
226+ segments . push (
227+ [
228+ [ x + frac ( tl , tr ) , y ] ,
229+ [ x + 1 , y + frac ( tr , br ) ] ,
230+ ] ,
231+ [
232+ [ x + frac ( bl , br ) , y + 1 ] ,
233+ [ x , y + frac ( tl , bl ) ] ,
234+ ]
235+ ) ;
236+ } else {
237+ segments . push (
238+ [
239+ [ x + frac ( tl , tr ) , y ] ,
240+ [ x , y + frac ( tl , bl ) ] ,
241+ ] ,
242+ [
243+ [ x + frac ( bl , br ) , y + 1 ] ,
244+ [ x + 1 , y + frac ( tr , br ) ] ,
245+ ]
246+ ) ;
247+ }
248+ break ;
249+ }
250+ case 11 :
251+ segments . push ( [
252+ [ x + frac ( tl , tr ) , y ] ,
253+ [ x + 1 , y + frac ( tr , br ) ] ,
254+ ] ) ;
255+ break ;
256+ case 12 :
257+ segments . push ( [
258+ [ x + 1 , y + frac ( tr , br ) ] ,
259+ [ x , y + frac ( tl , bl ) ] ,
260+ ] ) ;
261+ break ;
262+ case 13 :
263+ segments . push ( [
264+ [ x + 1 , y + frac ( tr , br ) ] ,
265+ [ x + frac ( bl , br ) , y + 1 ] ,
266+ ] ) ;
267+ break ;
268+ case 14 :
269+ segments . push ( [
270+ [ x + frac ( bl , br ) , y + 1 ] ,
271+ [ x , y + frac ( tl , bl ) ] ,
272+ ] ) ;
273+ break ;
274+ case 15 :
275+ // all above
276+ continue ;
277+ }
278+ }
279+ }
280+
281+ // TODO we can keep track of 'active' contours while gridding above and assign line segments to a contour as we go
282+ // but then we'd need another step where we combine contours that might have been split by the saddle cases
283+
284+ const contours : Position [ ] [ ] = [ ] ;
285+
286+ while ( segments . length > 0 ) {
287+ const contour : Position [ ] = [ ...segments . shift ( ) ! ] ;
288+ contours . push ( contour ) ;
289+
290+ let found : boolean ;
291+ do {
292+ found = false ;
293+ for ( let i = 0 ; i < segments . length ; i ++ ) {
294+ const segment = segments [ i ] ;
295+ // add to the end of our contour
296+ if (
297+ segment [ 0 ] [ 0 ] === contour [ contour . length - 1 ] [ 0 ] &&
298+ segment [ 0 ] [ 1 ] === contour [ contour . length - 1 ] [ 1 ]
299+ ) {
300+ found = true ;
301+ contour . push ( segment [ 1 ] ) ;
302+ segments . splice ( i , 1 ) ;
303+ break ;
304+ }
305+ // add to the beginning of our contour
306+ if (
307+ segment [ 1 ] [ 0 ] === contour [ 0 ] [ 0 ] &&
308+ segment [ 1 ] [ 1 ] === contour [ 0 ] [ 1 ]
309+ ) {
310+ found = true ;
311+ contour . unshift ( segment [ 0 ] ) ;
312+ segments . splice ( i , 1 ) ;
313+ break ;
314+ }
315+ }
316+ } while ( found ) ;
317+ }
318+
319+ return contours ;
320+
321+ // get the linear interpolation fraction of how far z is between z0 and z1
322+ // See https://github.com/fschutt/marching-squares/blob/master/src/lib.rs
323+ function frac ( z0 : number , z1 : number ) : number {
324+ if ( z0 === z1 ) {
325+ return 0.5 ;
326+ }
327+
328+ let t = ( threshold - z0 ) / ( z1 - z0 ) ;
329+ return t > 1 ? 1 : t < 0 ? 0 : t ;
330+ }
122331}
123332
124333/**
0 commit comments