1111
1212import * as vectorField from "./vectorField.js" ; // vector field
1313
14- // flag to add streamlines to view
15- const ADD_STREAMLINES = true ;
1614
15+ // streamlines
1716const numPaths = 64800 ; // number of streamlines: 64800 -> 1 x 1 degree samples
1817const numPathLength = 6 ; // number of point positions per streamline
1918
@@ -22,24 +21,29 @@ const useRegularLocations = true; // use regular lon/lat start positions instea
2221// streamlines array
2322let streamlines = null ;
2423
24+ // streamlines velocity factor
25+ const STRETCH_FACTOR = 4.0 ; // to make streamlines longer
26+
2527// streamline drawing
26- const streamlineSize = 2 ;
28+ const streamlineSize = 1.0 ;
29+ const ADAPT_STREAMLINESIZE = false ; // adapt line width to zoom factor
30+
31+ // quantized color scale for color buckets
32+ let colorScale = null ;
33+ const NUM_COLORS = 10 ; // number of quantized colors
2734
2835// regular stepping for start locations
29- let currentLon = - 180.0 , currentLat = - 90.0 ;
36+ let StartLon = - 180.0 , StartLat = - 90.0 ;
3037let deltaLon = 1.0 , deltaLat = 1.0 ;
3138let nLonSteps = 0 , nLatSteps = 0 ;
39+ let currentLon , currentLat ;
3240
3341// constants
3442const DEGREE_TO_RADIAN = Math . PI / 180 ;
3543
36- // pre-allocate vector
37- const vector = new Float32Array ( 2 ) ;
3844
3945// Initialize streamlines
4046function initializeStreamlines ( ) {
41- // checks if anything to do
42- if ( ! ADD_STREAMLINES ) return ;
4347
4448 // check if vector field is ready
4549 if ( ! vectorField . isGradientValid ( ) ) return ;
@@ -50,12 +54,26 @@ function initializeStreamlines() {
5054 const numPositions = numPaths * numPathLength ;
5155
5256 // streamlines array
53- streamlines = new Float32Array ( numPositions * 4 ) ; // x, y, vx, vy
57+ if ( streamlines == null ) {
58+ streamlines = new Float32Array ( numPositions * 4 ) ; // x, y, vx, vy
59+ }
5460
5561 // loop over paths
5662 for ( let n = 0 ; n < numPaths ; n ++ ) {
5763 createPath ( n ) ;
5864 }
65+
66+ // Create a quantized color scale with 10 discrete steps
67+ const startColor = "rgba(155, 155, 155, 0.4)" ;
68+ const endColor = "rgba(255, 255, 155, 0.4)" ;
69+
70+ // interpolator
71+ const colorInterpolator = d3 . interpolateRgb ( startColor , endColor ) ;
72+
73+ // scale
74+ colorScale = d3 . scaleQuantize ( )
75+ . domain ( [ 0.0 , 1.0 ] ) // normalized velocity range
76+ . range ( d3 . quantize ( colorInterpolator , NUM_COLORS ) ) ;
5977}
6078
6179
@@ -86,14 +104,17 @@ function createPath(n) {
86104 nLonSteps += 1 ;
87105 }
88106
89- deltaLon = 360.0 / ( nLonSteps + 1 ) ;
90- deltaLat = 180.0 / ( nLatSteps + 1 ) ;
107+ if ( nLonSteps <= 0 ) nLonSteps = 1 ;
108+ if ( nLatSteps <= 0 ) nLatSteps = 1 ;
109+
110+ deltaLon = 360.0 / nLonSteps ;
111+ deltaLat = 180.0 / nLatSteps ;
91112
92113 console . log ( `streamlines: regular paths ${ numPaths } - nsteps ${ nLonSteps } ${ nLatSteps } delta lon/lat = ${ deltaLon } /${ deltaLat } ` ) ;
93114
94115 // shift away from pole and meridian
95- currentLon += deltaLon / 2 ;
96- currentLat += deltaLat / 2 ;
116+ currentLon = StartLon + deltaLon / 2 ;
117+ currentLat = StartLat + deltaLat / 2 ;
97118 }
98119
99120 // set to current start position
@@ -104,7 +125,7 @@ function createPath(n) {
104125 currentLon += deltaLon ;
105126 if ( ( n > 0 ) && ( n % nLonSteps == 0 ) ) {
106127 // reached end of longitude line, increment latitude and reset longitude
107- currentLon = - 180.0 + deltaLon / 2 ; // re-start longitudes
128+ currentLon = StartLon + deltaLon / 2 ; // re-start longitudes
108129 currentLat += deltaLat ;
109130 }
110131
@@ -131,11 +152,18 @@ function createPath(n) {
131152 // moves points according to vector field:
132153 // vx - x direction == lon
133154 // vy - y direction == lat
155+ // pre-allocate vector
156+ const vector = new Float32Array ( 2 ) ;
157+
134158 vectorField . getVectorField ( lon , lat , vector ) ;
135159
136160 // check if valid
137161 if ( ! vector [ 0 ] || ! vector [ 1 ] ) { return ; }
138162
163+ // scale velocities
164+ vector [ 0 ] *= STRETCH_FACTOR ; // vx
165+ vector [ 1 ] *= STRETCH_FACTOR ; // vy
166+
139167 // or simple vector field
140168 //const lonRad = lon * DEGREE_TO_RADIAN;
141169 //const latRad = lat * DEGREE_TO_RADIAN;
@@ -165,28 +193,42 @@ function createPath(n) {
165193
166194 // updates velocity direction
167195 vectorField . getVectorField ( lon , lat , vector ) ;
196+
168197 // check if valid
169198 if ( ! vector [ 0 ] || ! vector [ 1 ] ) { return ; }
199+
200+ // scale velocities
201+ vector [ 0 ] *= STRETCH_FACTOR ; // vx
202+ vector [ 1 ] *= STRETCH_FACTOR ; // vy
170203 }
171204}
172205
206+ // clear streamline arrays
207+ function clearStreamlines ( ) {
208+
209+ console . log ( `streamlines: clearStreamlines` ) ;
210+
211+ // check if streamlines array is valid
212+ if ( ! streamlines ) return ;
213+
214+ // reset reference array
215+ streamlines = null ;
216+ }
217+
173218
174219// update streamline positions
175220function updateStreamlines ( ) {
176- // checks if anything to do
177- if ( ! ADD_STREAMLINES ) return ;
178221
179222 // check if vector field is ready
180223 if ( ! vectorField . isGradientValid ( ) ) return ;
181224
182225 // check if streamlines array is valid
183226 if ( ! streamlines ) return ;
184227
185- console . log ( `updateStreamlines: streamlines ${ streamlines . length / 4 } ` ) ;
186-
187228 // check if regular start locations, then we keep those
188229 if ( useRegularLocations ) return ;
189230
231+ //console.log(`updateStreamlines: streamlines ${streamlines.length/4}`);
190232 console . time ( 'updateStreamlines' ) ;
191233
192234 // loop over paths
@@ -195,13 +237,10 @@ function updateStreamlines() {
195237 }
196238
197239 console . timeEnd ( 'updateStreamlines' ) ;
198-
199240}
200241
201242// draw streamlines
202243function drawStreamlines ( projection , context ) {
203- // checks if anything to do
204- if ( ! ADD_STREAMLINES ) return ;
205244
206245 //console.log(`drawStreamlines: streamlines ${streamlines.length/4}`);
207246
@@ -238,17 +277,29 @@ function drawStreamlines(projection, context) {
238277 const vCenter = [ Math . cos ( latRad ) * Math . cos ( lonRad ) , Math . cos ( latRad ) * Math . sin ( lonRad ) , Math . sin ( latRad ) ] ;
239278
240279 // determine line width based on zoom
241- let k = 1.0 , scaleZoom = 1.0 ;
242- const transform = d3 . zoomTransform ( d3 . select ( "#navigation" ) . node ( ) ) ;
243- if ( transform != null ) {
244- k = transform . k ; // 313 to 6266...
245- const windowSize = Math . min ( context . canvas . width , context . canvas . height ) ;
246- scaleZoom = k / windowSize ; // stronger zoom -> higher k -> thicker lines
280+ let lineWidth = streamlineSize ;
281+ if ( ADAPT_STREAMLINESIZE ) {
282+ let k = 1.0 , scaleZoom = 1.0 ;
283+ const transform = d3 . zoomTransform ( d3 . select ( "#navigation" ) . node ( ) ) ;
284+ if ( transform != null ) {
285+ k = transform . k ; // 313 to 6266...
286+ const windowSize = Math . min ( context . canvas . width , context . canvas . height ) ;
287+ scaleZoom = k / windowSize ; // stronger zoom -> higher k -> thicker lines
288+ }
289+ lineWidth = ( scaleZoom < 1 ) ? streamlineSize : Math . floor ( streamlineSize * scaleZoom ) ;
290+ //console.log(`drawStreamlines: line width ${lineWidth}`);
247291 }
248- const lineWidth = ( scaleZoom < 1 ) ? streamlineSize : Math . floor ( streamlineSize * scaleZoom ) ;
249292
250293 projection . clipAngle ( 90 ) ;
251294
295+ // position vectors
296+ const v0 = [ 0 , 0 , 0 ] ;
297+ const v1 = [ 0 , 0 , 0 ] ;
298+
299+ let p0 = [ 0 , 0 ] ;
300+ let p1 = [ 0 , 0 ] ;
301+
302+ //debug
252303 //let isDone = false;
253304
254305 for ( let i = 0 ; i < streamlines . length / 4 ; i ++ ) {
@@ -267,56 +318,70 @@ function drawStreamlines(projection, context) {
267318 const lon0 = streamlines [ index ] ;
268319 const lat0 = streamlines [ index + 1 ] ;
269320
321+ const vx = streamlines [ index + 2 ] ;
322+ const vy = streamlines [ index + 3 ] ;
323+
324+ // velocity strength
325+ const norm = Math . sqrt ( vx * vx + vy * vy ) / STRETCH_FACTOR ; // in [0,1]
326+
270327 // updated position
271- const lon1 = lon0 + streamlines [ index + 2 ] ;
272- const lat1 = lat0 + streamlines [ index + 3 ] ;
328+ const lon1 = lon0 + vx ;
329+ const lat1 = lat0 + vy ;
273330
274331 // current point location vector
275- lonRad = lon0 * DEGREE_TO_RADIAN ;
276- latRad = lat0 * DEGREE_TO_RADIAN ;
277- const v0 = [ Math . cos ( latRad ) * Math . cos ( lonRad ) , Math . cos ( latRad ) * Math . sin ( lonRad ) , Math . sin ( latRad ) ] ;
278-
279- lonRad = lon1 * DEGREE_TO_RADIAN ;
280- latRad = lat1 * DEGREE_TO_RADIAN ;
281- const v1 = [ Math . cos ( latRad ) * Math . cos ( lonRad ) , Math . cos ( latRad ) * Math . sin ( lonRad ) , Math . sin ( latRad ) ] ;
332+ const lonRad0 = lon0 * DEGREE_TO_RADIAN ;
333+ const latRad0 = lat0 * DEGREE_TO_RADIAN ;
334+ v0 [ 0 ] = Math . cos ( latRad0 ) * Math . cos ( lonRad0 ) ;
335+ v0 [ 1 ] = Math . cos ( latRad0 ) * Math . sin ( lonRad0 ) ;
336+ v0 [ 2 ] = Math . sin ( latRad0 ) ;
337+
338+ const lonRad1 = lon1 * DEGREE_TO_RADIAN ;
339+ const latRad1 = lat1 * DEGREE_TO_RADIAN ;
340+ v1 [ 0 ] = Math . cos ( latRad1 ) * Math . cos ( lonRad1 ) ;
341+ v1 [ 1 ] = Math . cos ( latRad1 ) * Math . sin ( lonRad1 ) ;
342+ v1 [ 2 ] = Math . sin ( latRad1 ) ;
282343
283344 if ( ! isPointVisibleHemisphere ( vCenter , v0 ) ) { continue ; }
284345 if ( ! isPointVisibleHemisphere ( vCenter , v1 ) ) { continue ; }
285346
286347 // converted to x/y
287- const p0 = projection ( [ lon0 , lat0 ] ) ;
288- const p1 = projection ( [ lon1 , lat1 ] ) ;
348+ p0 = projection ( [ lon0 , lat0 ] ) ;
349+ p1 = projection ( [ lon1 , lat1 ] ) ;
289350
290351 // check if valid
291352 if ( ! p0 || isNaN ( p0 [ 0 ] ) || isNaN ( p0 [ 1 ] ) ) { continue ; }
292353 if ( ! p1 || isNaN ( p1 [ 0 ] ) || isNaN ( p1 [ 1 ] ) ) { continue ; }
293354
294- const [ x0 , y0 ] = p0 ;
295- const [ x1 , y1 ] = p1 ;
296-
297355 // check if point is visible area
298356 // doesn't work, returned [x,y] pixel positions are all within globe area...
357+ //const [x0,y0] = p0;
358+ //const [x1,y1] = p1;
299359 //if (! isPointClose([x0,y0])) { continue; }
300360 //if (! isPointClose([x1,y1])) { continue; }
301361
302362 // Draw streamline
363+ // coloring
364+ //let color;
365+ // same as bumpMap coloring
366+ //color = d3.interpolatePuBu(1 - val);
367+ // red
368+ //color = d3.interpolateYlOrBr(1 - val);
369+ // Determine color based on velocity
370+ let color = colorScale ( norm ) ;
371+
303372 // alpha value based on path position index
304373 const n = i % numPathLength ;
305374 const val = ( ( n + 1 ) / numPathLength ) ; // in [0.1,1]
306- const alpha = 0.2 * val ;
307375
308- //if (index == 0) console.log(`drawStreamlines: ${lon0}/${lat0} to ${lon1}/${lat1} alpha ${alpha}`);
376+ let alpha = 0.1 + 0.2 * val ;
377+ if ( alpha > 1.0 ) alpha = 1.0 ;
309378
310- // coloring
311- let color ;
312- // same as bumpMap coloring
313- //color = d3.interpolatePuBu(1 - val);
314- // red
315- color = d3 . interpolateYlOrBr ( 1 - val ) ;
379+ //if (index == 0) console.log(`drawStreamlines: ${lon0}/${lat0} to ${lon1}/${lat1} alpha ${alpha}`);
316380
317381 // add transparency
318382 color = d3 . color ( color ) . copy ( { opacity : alpha } ) ;
319383
384+ //debug
320385 //if (! isDone) {
321386 // console.log(`drawStreamlines: transform ${transform} k ${k} ${scaleZoom} ${context.canvas.width} ${context.canvas.height} lineWidth ${lineWidth}`);
322387 // isDone = true;
@@ -326,13 +391,13 @@ function drawStreamlines(projection, context) {
326391 context . beginPath ( ) ;
327392 context . strokeStyle = color ;
328393 context . lineWidth = lineWidth ;
329- context . moveTo ( x0 , y0 ) ;
330- context . lineTo ( x1 , y1 ) ;
394+ context . moveTo ( p0 [ 0 ] , p0 [ 1 ] ) ;
395+ context . lineTo ( p1 [ 0 ] , p1 [ 1 ] ) ;
331396 context . stroke ( ) ;
332397 }
333398
334399 console . timeEnd ( 'drawStreamlines' ) ;
335400}
336401
337402
338- export { initializeStreamlines , updateStreamlines , drawStreamlines } ;
403+ export { initializeStreamlines , clearStreamlines , updateStreamlines , drawStreamlines } ;
0 commit comments