@@ -108,17 +108,243 @@ 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 (
119+ matrix : ReadonlyArray < ReadonlyArray < number > > ,
120+ threshold : number
121+ ) : Position [ ] [ ] {
122+ const segments : [ Position , Position ] [ ] = [ ] ;
123+
124+ for ( let y = 0 ; y < matrix . length - 1 ; y ++ ) {
125+ const rowLen = matrix [ y ] . length - 1 ;
126+ for ( let x = 0 ; x < rowLen ; x ++ ) {
127+ const tr = matrix [ y ] [ x + 1 ] ;
128+ const br = matrix [ y + 1 ] [ x + 1 ] ;
129+ const bl = matrix [ y + 1 ] [ x ] ;
130+ const tl = matrix [ y ] [ x ] ;
131+
132+ const grid =
133+ ( tl >= threshold ? 8 : 0 ) |
134+ ( tr >= threshold ? 4 : 0 ) |
135+ ( br >= threshold ? 2 : 0 ) |
136+ ( bl >= threshold ? 1 : 0 ) ;
137+
138+ // see https://en.wikipedia.org/wiki/Marching_squares
139+ // the two points in the line segments need to be carefully selected to keep things counter-clockwise
140+ switch ( grid ) {
141+ case 0 :
142+ continue ;
143+ case 1 :
144+ segments . push ( [
145+ [ x + frac ( bl , br ) , y + 1 ] ,
146+ [ x , y + frac ( tl , bl ) ] ,
147+ ] ) ;
148+ break ;
149+ case 2 :
150+ segments . push ( [
151+ [ x + 1 , y + frac ( tr , br ) ] ,
152+ [ x + frac ( bl , br ) , y + 1 ] ,
153+ ] ) ;
154+ break ;
155+ case 3 :
156+ segments . push ( [
157+ [ x + 1 , y + frac ( tr , br ) ] ,
158+ [ x , y + frac ( tl , bl ) ] ,
159+ ] ) ;
160+ break ;
161+ case 4 :
162+ segments . push ( [
163+ [ x + frac ( tl , tr ) , y ] ,
164+ [ x + 1 , y + frac ( tr , br ) ] ,
165+ ] ) ;
166+ break ;
167+ case 5 : {
168+ // use the average of the 4 corners to differentiate the saddle case and correctly honor the counter-clockwise winding
169+ const avg = ( tl + tr + br + bl ) / 4 ;
170+ const above = avg >= threshold ;
171+
172+ if ( above ) {
173+ segments . push (
174+ [
175+ [ x + frac ( tl , tr ) , y ] ,
176+ [ x , y + frac ( tl , bl ) ] ,
177+ ] ,
178+ [
179+ [ x + frac ( bl , br ) , y + 1 ] ,
180+ [ x + 1 , y + frac ( tr , br ) ] ,
181+ ]
182+ ) ;
183+ } else {
184+ segments . push (
185+ [
186+ [ x + frac ( tl , tr ) , y ] ,
187+ [ x + 1 , y + frac ( tr , br ) ] ,
188+ ] ,
189+ [
190+ [ x + frac ( bl , br ) , y + 1 ] ,
191+ [ x , y + frac ( tl , bl ) ] ,
192+ ]
193+ ) ;
194+ }
195+ break ;
196+ }
197+ case 6 :
198+ segments . push ( [
199+ [ x + frac ( tl , tr ) , y ] ,
200+ [ x + frac ( bl , br ) , y + 1 ] ,
201+ ] ) ;
202+ break ;
203+ case 7 :
204+ segments . push ( [
205+ [ x + frac ( tl , tr ) , y ] ,
206+ [ x , y + frac ( tl , bl ) ] ,
207+ ] ) ;
208+ break ;
209+ case 8 :
210+ segments . push ( [
211+ [ x , y + frac ( tl , bl ) ] ,
212+ [ x + frac ( tl , tr ) , y ] ,
213+ ] ) ;
214+ break ;
215+ case 9 :
216+ segments . push ( [
217+ [ x + frac ( bl , br ) , y + 1 ] ,
218+ [ x + frac ( tl , tr ) , y ] ,
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 + 1 , y + frac ( tr , br ) ] ,
229+ [ x + frac ( tl , tr ) , y ] ,
230+ ] ,
231+ [
232+ [ x , y + frac ( tl , bl ) ] ,
233+ [ x + frac ( bl , br ) , y + 1 ] ,
234+ ]
235+ ) ;
236+ } else {
237+ segments . push (
238+ [
239+ [ x , y + frac ( tl , bl ) ] ,
240+ [ x + frac ( tl , tr ) , y ] ,
241+ ] ,
242+ [
243+ [ x + 1 , y + frac ( tr , br ) ] ,
244+ [ x + frac ( bl , br ) , y + 1 ] ,
245+ ]
246+ ) ;
247+ }
248+ break ;
249+ }
250+ case 11 :
251+ segments . push ( [
252+ [ x + 1 , y + frac ( tr , br ) ] ,
253+ [ x + frac ( tl , tr ) , y ] ,
254+ ] ) ;
255+ break ;
256+ case 12 :
257+ segments . push ( [
258+ [ x , y + frac ( tl , bl ) ] ,
259+ [ x + 1 , y + frac ( tr , br ) ] ,
260+ ] ) ;
261+ break ;
262+ case 13 :
263+ segments . push ( [
264+ [ x + frac ( bl , br ) , y + 1 ] ,
265+ [ x + 1 , y + frac ( tr , br ) ] ,
266+ ] ) ;
267+ break ;
268+ case 14 :
269+ segments . push ( [
270+ [ x , y + frac ( tl , bl ) ] ,
271+ [ x + frac ( bl , br ) , y + 1 ] ,
272+ ] ) ;
273+ break ;
274+ case 15 :
275+ // all above
276+ continue ;
277+ }
278+ }
279+ }
280+
281+ const contours : Position [ ] [ ] = [ ] ;
282+
283+ while ( segments . length > 0 ) {
284+ const contour : Position [ ] = [ ...segments . shift ( ) ! ] ;
285+ contours . push ( contour ) ;
286+
287+ let found : boolean ;
288+ do {
289+ found = false ;
290+ for ( let i = 0 ; i < segments . length ; i ++ ) {
291+ const segment = segments [ i ] ;
292+ // add the segment's end point to the end of the contour
293+ if (
294+ segment [ 0 ] [ 0 ] === contour [ contour . length - 1 ] [ 0 ] &&
295+ segment [ 0 ] [ 1 ] === contour [ contour . length - 1 ] [ 1 ]
296+ ) {
297+ found = true ;
298+ contour . push ( segment [ 1 ] ) ;
299+ segments . splice ( i , 1 ) ;
300+ break ;
301+ }
302+ // add the segment's start point to the start of the contour
303+ if (
304+ segment [ 1 ] [ 0 ] === contour [ 0 ] [ 0 ] &&
305+ segment [ 1 ] [ 1 ] === contour [ 0 ] [ 1 ]
306+ ) {
307+ found = true ;
308+ contour . unshift ( segment [ 0 ] ) ;
309+ segments . splice ( i , 1 ) ;
310+ break ;
311+ }
312+ // add the segment's start point to the end of the contour
313+ if (
314+ segment [ 1 ] [ 0 ] === contour [ contour . length - 1 ] [ 0 ] &&
315+ segment [ 1 ] [ 1 ] === contour [ contour . length - 1 ] [ 1 ]
316+ ) {
317+ found = true ;
318+ contour . push ( segment [ 0 ] ) ;
319+ segments . splice ( i , 1 ) ;
320+ break ;
321+ }
322+ // add the segment's end point to the start of the contour
323+ if (
324+ segment [ 0 ] [ 0 ] === contour [ 0 ] [ 0 ] &&
325+ segment [ 0 ] [ 1 ] === contour [ 0 ] [ 1 ]
326+ ) {
327+ found = true ;
328+ contour . unshift ( contour [ 1 ] ) ;
329+ segments . splice ( i , 1 ) ;
330+ break ;
331+ }
332+ }
333+ } while ( found ) ;
334+ }
335+
336+ return contours ;
337+
338+ // get the linear interpolation fraction of how far z is between z0 and z1
339+ // See https://github.com/fschutt/marching-squares/blob/master/src/lib.rs
340+ function frac ( z0 : number , z1 : number ) : number {
341+ if ( z0 === z1 ) {
342+ return 0.5 ;
343+ }
344+
345+ let t = ( threshold - z0 ) / ( z1 - z0 ) ;
346+ return t > 1 ? 1 : t < 0 ? 0 : t ;
347+ }
122348}
123349
124350/**
0 commit comments