@longline/aqua-ui 1.0.299 → 1.0.302

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/aquaui-sprites.svg +1 -1
  2. package/formatters/CountryFormatter/CountryFormatter.d.ts +25 -3
  3. package/formatters/CountryFormatter/CountryFormatter.js +24 -2
  4. package/formatters/DateTimeFormatter/DateTimeFormatter.d.ts +38 -9
  5. package/formatters/DateTimeFormatter/DateTimeFormatter.js +38 -9
  6. package/formatters/DivisionFormatter/DivisionFormatter.d.ts +28 -4
  7. package/formatters/DivisionFormatter/DivisionFormatter.js +25 -3
  8. package/formatters/FilesizeFormatter/FilesizeFormatter.d.ts +24 -8
  9. package/formatters/FilesizeFormatter/FilesizeFormatter.js +50 -31
  10. package/formatters/GIS/CoordinateFormatter.d.ts +58 -0
  11. package/formatters/GIS/CoordinateFormatter.js +51 -0
  12. package/formatters/GIS/LatitudeFormatter.d.ts +5 -4
  13. package/formatters/GIS/LatitudeFormatter.js +7 -11
  14. package/formatters/GIS/LongitudeFormatter.d.ts +9 -6
  15. package/formatters/GIS/LongitudeFormatter.js +11 -13
  16. package/formatters/GIS/index.d.ts +1 -0
  17. package/formatters/GIS/index.js +1 -0
  18. package/formatters/HighlightFormatter/HighlightFormatter.d.ts +26 -5
  19. package/formatters/HighlightFormatter/HighlightFormatter.js +26 -5
  20. package/formatters/HttpStatusFormatter/HttpStatusFormatter.d.ts +34 -14
  21. package/formatters/HttpStatusFormatter/HttpStatusFormatter.js +35 -16
  22. package/formatters/HumanFormatter/HumanFormatter.d.ts +24 -4
  23. package/formatters/HumanFormatter/HumanFormatter.js +37 -12
  24. package/formatters/NumberFormatter/NumberFormatter.d.ts +19 -6
  25. package/formatters/NumberFormatter/NumberFormatter.js +19 -6
  26. package/formatters/StringFormatter/StringFormatter.d.ts +22 -4
  27. package/formatters/StringFormatter/StringFormatter.js +21 -3
  28. package/map/layers/ParticlesLayer/ParticlesLayer.d.ts +97 -1
  29. package/map/layers/ParticlesLayer/ParticlesLayer.js +239 -38
  30. package/map/layers/ParticlesLayer/ParticlesVertexShader.d.ts +1 -1
  31. package/map/layers/ParticlesLayer/ParticlesVertexShader.js +1 -1
  32. package/map/layers/ParticlesLayer/PositionComputeFragmentShader.d.ts +2 -0
  33. package/map/layers/ParticlesLayer/PositionComputeFragmentShader.js +2 -0
  34. package/map/layers/ParticlesLayer/PositionComputeVertexShader.d.ts +2 -0
  35. package/map/layers/ParticlesLayer/PositionComputeVertexShader.js +2 -0
  36. package/package.json +1 -1
  37. package/services/Dialog/AlertDialog.d.ts +1 -1
  38. package/services/Dialog/ConfirmDialog.d.ts +5 -5
  39. package/services/Dialog/ConfirmDialog.js +1 -1
  40. package/services/Dialog/Dialog.d.ts +46 -16
  41. package/services/Dialog/Dialog.js +98 -16
  42. package/services/Dialog/DialogBackground.js +1 -0
  43. package/services/Dialog/DialogContent.d.ts +3 -3
  44. package/services/Dialog/DialogContent.js +1 -1
  45. package/services/Dialog/DialogFooter.d.ts +3 -3
  46. package/services/Dialog/DialogHeader.d.ts +3 -3
  47. package/services/Dialog/DialogWindow.d.ts +3 -4
  48. package/services/Dialog/DialogWindow.js +1 -0
  49. package/services/Dialog/XhrDialog.d.ts +1 -1
  50. package/services/Dialog/index.d.ts +7 -1
  51. package/svg/icons/index.d.ts +1 -0
  52. package/svg/icons/index.js +1 -0
@@ -16,15 +16,28 @@ interface INumberFormatterProps {
16
16
  locale?: string;
17
17
  }
18
18
  /**
19
- * `NumberFormatter` formats a number with thousands separators and optional
20
- * fractional digits. If provided with a string, `NumberFormatter` will
21
- * convert it to a float first. By default, two decimals are printed.
19
+ * Formats numbers with locale-aware thousands separators and configurable
20
+ * decimal precision.
22
21
  *
23
- * If the input is not a valid number, it returns `null`.
22
+ * Use `NumberFormatter` when displaying numeric values that benefit from
23
+ * grouping (e.g., financial figures, statistics, counts). For large numbers
24
+ * that should be abbreviated (e.g., "1.2M"), use `HumanFormatter` instead.
25
+ *
26
+ * ## Output Examples
27
+ *
28
+ * | Input | Props | Output |
29
+ * |-------|-------|--------|
30
+ * | `35100.2` | (default) | `35,100.20` |
31
+ * | `35100.2` | `decimals={0}` | `35,100` |
32
+ * | `1234.567` | `locale="de-DE"` | `1.234,57` |
33
+ * | `"1000"` | (default) | `1,000.00` |
34
+ * | `NaN` | (default) | *(null)* |
35
+ *
36
+ * ## Usage
24
37
  *
25
38
  * ```tsx
26
- * <NumberFormatter value={35100.20}/>
27
- * <NumberFormatter value={35100.20} decimals={0}/>
39
+ * <NumberFormatter value={35100.20} />
40
+ * <NumberFormatter value={35100.20} decimals={0} />
28
41
  * <NumberFormatter value={1234.567} locale="de-DE" />
29
42
  * ```
30
43
  */
@@ -1,14 +1,27 @@
1
1
  import * as React from 'react';
2
2
  /**
3
- * `NumberFormatter` formats a number with thousands separators and optional
4
- * fractional digits. If provided with a string, `NumberFormatter` will
5
- * convert it to a float first. By default, two decimals are printed.
3
+ * Formats numbers with locale-aware thousands separators and configurable
4
+ * decimal precision.
6
5
  *
7
- * If the input is not a valid number, it returns `null`.
6
+ * Use `NumberFormatter` when displaying numeric values that benefit from
7
+ * grouping (e.g., financial figures, statistics, counts). For large numbers
8
+ * that should be abbreviated (e.g., "1.2M"), use `HumanFormatter` instead.
9
+ *
10
+ * ## Output Examples
11
+ *
12
+ * | Input | Props | Output |
13
+ * |-------|-------|--------|
14
+ * | `35100.2` | (default) | `35,100.20` |
15
+ * | `35100.2` | `decimals={0}` | `35,100` |
16
+ * | `1234.567` | `locale="de-DE"` | `1.234,57` |
17
+ * | `"1000"` | (default) | `1,000.00` |
18
+ * | `NaN` | (default) | *(null)* |
19
+ *
20
+ * ## Usage
8
21
  *
9
22
  * ```tsx
10
- * <NumberFormatter value={35100.20}/>
11
- * <NumberFormatter value={35100.20} decimals={0}/>
23
+ * <NumberFormatter value={35100.20} />
24
+ * <NumberFormatter value={35100.20} decimals={0} />
12
25
  * <NumberFormatter value={1234.567} locale="de-DE" />
13
26
  * ```
14
27
  */
@@ -1,3 +1,4 @@
1
+ import * as React from 'react';
1
2
  interface IStringFormatterProps {
2
3
  /** String to format */
3
4
  value: string;
@@ -5,16 +6,33 @@ interface IStringFormatterProps {
5
6
  type: 'capitalize' | 'capitalizeWords' | 'upper' | 'lower' | 'reverse' | 'snake' | 'kebab';
6
7
  }
7
8
  /**
8
- * `StringFormatter` formats a string in different ways.
9
+ * Transforms strings using common text formatting operations.
10
+ *
11
+ * All operations are Unicode-safe, using `toLocaleUpperCase()` and
12
+ * `toLocaleLowerCase()` for proper internationalization.
13
+ *
14
+ * ## Format Types
15
+ *
16
+ * | Type | Input | Output |
17
+ * |------|-------|--------|
18
+ * | `capitalize` | `"hello world"` | `"Hello world"` |
19
+ * | `capitalizeWords` | `"hello world"` | `"Hello World"` |
20
+ * | `upper` | `"Hello World"` | `"HELLO WORLD"` |
21
+ * | `lower` | `"Hello World"` | `"hello world"` |
22
+ * | `reverse` | `"Hello"` | `"olleH"` |
23
+ * | `snake` | `"Hello World"` | `"hello_world"` |
24
+ * | `kebab` | `"Hello World"` | `"hello-world"` |
25
+ *
26
+ * ## Usage
9
27
  *
10
28
  * ```tsx
11
29
  * <StringFormatter value="hello world" type="capitalize" />
12
30
  * <StringFormatter value="hello world" type="capitalizeWords" />
13
- * <StringFormatter value="hello world" type="snake" />
31
+ * <StringFormatter value="my variable name" type="snake" />
14
32
  * ```
15
33
  */
16
34
  declare const StringFormatter: {
17
- ({ value, type }: IStringFormatterProps): string;
35
+ ({ value, type }: IStringFormatterProps): React.JSX.Element;
18
36
  displayName: string;
19
37
  };
20
- export { StringFormatter };
38
+ export { StringFormatter, IStringFormatterProps };
@@ -1,3 +1,4 @@
1
+ import * as React from 'react';
1
2
  /** Unicode-safe capitalize first letter */
2
3
  var capitalize = function (str) {
3
4
  if (!str)
@@ -26,12 +27,29 @@ var formatters = {
26
27
  kebab: function (s) { return s.trim().toLocaleLowerCase().replace(/\s+/g, '-'); },
27
28
  };
28
29
  /**
29
- * `StringFormatter` formats a string in different ways.
30
+ * Transforms strings using common text formatting operations.
31
+ *
32
+ * All operations are Unicode-safe, using `toLocaleUpperCase()` and
33
+ * `toLocaleLowerCase()` for proper internationalization.
34
+ *
35
+ * ## Format Types
36
+ *
37
+ * | Type | Input | Output |
38
+ * |------|-------|--------|
39
+ * | `capitalize` | `"hello world"` | `"Hello world"` |
40
+ * | `capitalizeWords` | `"hello world"` | `"Hello World"` |
41
+ * | `upper` | `"Hello World"` | `"HELLO WORLD"` |
42
+ * | `lower` | `"Hello World"` | `"hello world"` |
43
+ * | `reverse` | `"Hello"` | `"olleH"` |
44
+ * | `snake` | `"Hello World"` | `"hello_world"` |
45
+ * | `kebab` | `"Hello World"` | `"hello-world"` |
46
+ *
47
+ * ## Usage
30
48
  *
31
49
  * ```tsx
32
50
  * <StringFormatter value="hello world" type="capitalize" />
33
51
  * <StringFormatter value="hello world" type="capitalizeWords" />
34
- * <StringFormatter value="hello world" type="snake" />
52
+ * <StringFormatter value="my variable name" type="snake" />
35
53
  * ```
36
54
  */
37
55
  var StringFormatter = function (_a) {
@@ -42,7 +60,7 @@ var StringFormatter = function (_a) {
42
60
  if (typeof value !== 'string')
43
61
  return null;
44
62
  var formatter = (_b = formatters[type]) !== null && _b !== void 0 ? _b : (function (s) { return s; });
45
- return formatter(value);
63
+ return React.createElement(React.Fragment, null, formatter(value));
46
64
  };
47
65
  StringFormatter.displayName = 'StringFormatter';
48
66
  export { StringFormatter };
@@ -46,9 +46,105 @@ interface IParticlesLayerProps {
46
46
  * @default 99
47
47
  */
48
48
  maxZoom?: number;
49
+ /**
50
+ * Minimum visible velocity magnitude for particle trails.
51
+ * Velocities are linearly remapped to [minSpeed, maxSpeed] range.
52
+ * @default 0.02
53
+ */
54
+ minSpeed?: number;
55
+ /**
56
+ * Maximum visible velocity magnitude for particle trails.
57
+ * Velocities are linearly remapped to [minSpeed, maxSpeed] range.
58
+ * @default 0.15
59
+ */
60
+ maxSpeed?: number;
49
61
  }
62
+ /**
63
+ * A WebGL-based Mapbox GL custom layer that visualizes vector flow fields (such as
64
+ * ocean currents or wind patterns) using animated particle trails.
65
+ *
66
+ * ## Overview
67
+ *
68
+ * The component takes an array of data points, each with a geographic position and
69
+ * a velocity vector (U and V components). It renders animated particles that flow
70
+ * along these velocity vectors, creating a visual representation of the flow field.
71
+ *
72
+ * Particle trails are colored using a configurable gradient, where the head of the
73
+ * trail is typically bright and the tail fades out. The animation continuously cycles
74
+ * particles through their trails, creating a flowing effect.
75
+ *
76
+ * ## Data Format
77
+ *
78
+ * Input data should be an array of points with the following structure:
79
+ *
80
+ * ```typescript
81
+ * interface IPoint {
82
+ * latitude: number; // Geographic latitude
83
+ * longitude: number; // Geographic longitude
84
+ * u: number; // Velocity component in X direction
85
+ * v: number; // Velocity component in Y direction
86
+ * }
87
+ * ```
88
+ *
89
+ * The component uses Delaunay triangulation (via Turf.js) to interpolate velocity
90
+ * values between data points, so the data does not need to be on a regular grid.
91
+ *
92
+ * ## Velocity Remapping
93
+ *
94
+ * To ensure all particles have visible trails regardless of the actual velocity
95
+ * magnitudes in the data, velocities are linearly remapped to a visible range:
96
+ *
97
+ * - The `minSpeed` prop sets the minimum trail speed (for the slowest currents)
98
+ * - The `maxSpeed` prop sets the maximum trail speed (for the fastest currents)
99
+ * - All velocities are scaled proportionally within this range
100
+ * - The relative differences between fast and slow currents are preserved
101
+ * - Particles with exactly zero velocity are not rendered
102
+ *
103
+ * ## Performance
104
+ *
105
+ * The component is optimized for performance using several GPU-based techniques:
106
+ *
107
+ * 1. **UV Texture Caching**: The velocity field is rendered to a texture only when
108
+ * the map view changes (pan/zoom), not on every animation frame.
109
+ *
110
+ * 2. **Combined UV Texture**: U and V components are stored in a single texture
111
+ * (U in red channel, V in green channel) to reduce texture fetches.
112
+ *
113
+ * 3. **Position Pre-computation**: Particle positions are pre-computed in a GPU
114
+ * shader pass when the view changes, eliminating expensive per-vertex loops
115
+ * during animation.
116
+ *
117
+ * With default settings (`density=40`, `particles=80`), the component renders
118
+ * 128,000 particles efficiently.
119
+ *
120
+ * ## Example
121
+ *
122
+ * ```tsx
123
+ * <Map>
124
+ * <ParticlesLayer
125
+ * data={oceanCurrentData}
126
+ * density={50}
127
+ * particles={100}
128
+ * gradientStops={[
129
+ * { pos: 0.0, color: '#00ffffff' },
130
+ * { pos: 0.5, color: '#0088ffff' },
131
+ * { pos: 1.0, color: '#0088ff00' },
132
+ * ]}
133
+ * minSpeed={0.01}
134
+ * maxSpeed={0.2}
135
+ * />
136
+ * </Map>
137
+ * ```
138
+ *
139
+ * ## Notes
140
+ *
141
+ * - The component requires a Mapbox GL map context (via `react-map-gl`)
142
+ * - WebGL 1.0 is used for maximum browser compatibility (no extensions required)
143
+ * - The layer renders before the "overlay" layer in the Mapbox style
144
+ * - Changing props triggers a full re-initialization of the WebGL resources
145
+ */
50
146
  declare const ParticlesLayer: {
51
- ({ particles, density, delay, gradientStops, minZoom, maxZoom, ...props }: IParticlesLayerProps): React.JSX.Element;
147
+ ({ particles, density, delay, gradientStops, minZoom, maxZoom, minSpeed, maxSpeed, ...props }: IParticlesLayerProps): React.JSX.Element;
52
148
  displayName: string;
53
149
  };
54
150
  export { ParticlesLayer, IParticlesLayerProps };
@@ -38,6 +38,8 @@ import { UVVertexShader } from './UVVertexShader';
38
38
  import { UVFragmentShader } from './UVFragmentShader';
39
39
  import { ParticlesFragmentShader } from './ParticlesFragmentShader';
40
40
  import { ParticlesVertexShader } from './ParticlesVertexShader';
41
+ import { PositionComputeVertexShader } from './PositionComputeVertexShader';
42
+ import { PositionComputeFragmentShader } from './PositionComputeFragmentShader';
41
43
  import { RgbColor } from '../../../helper/RgbColor';
42
44
  var TEXTURE_WIDTH = 1024;
43
45
  var TEXTURE_HEIGHT = 1024;
@@ -54,14 +56,23 @@ var ParticlesLayerBase = function (props) {
54
56
  var maxU = React.useRef(0);
55
57
  var minV = React.useRef(0);
56
58
  var maxV = React.useRef(0);
57
- var textureU = React.useRef(null);
58
- var textureV = React.useRef(null);
59
- var fboU = React.useRef(null);
60
- var fboV = React.useRef(null);
59
+ // Combined UV texture: U in R channel, V in G channel, alpha indicates valid data
60
+ var textureUV = React.useRef(null);
61
+ var fboUV = React.useRef(null);
62
+ // Position texture: stores pre-computed particle positions (X in RG, Y in BA)
63
+ var positionProgram = React.useRef(null);
64
+ var texturePosition = React.useRef(null);
65
+ var fboPosition = React.useRef(null);
66
+ var bufferQuad = React.useRef(null);
67
+ // Dynamic position texture dimensions (computed based on density * density * particles)
68
+ var posTexWidth = React.useRef(0);
69
+ var posTexHeight = React.useRef(0);
61
70
  var frame = React.useRef(0);
62
71
  var skip = React.useRef(0);
63
72
  // requestAnimationFrame ID:
64
73
  var animation = React.useRef(null);
74
+ // Last matrix used to render UV textures (for caching):
75
+ var lastMatrix = React.useRef(null);
65
76
  // Current time is used to force component rerender when props change:
66
77
  var _a = React.useState(0), time = _a[0], setTime = _a[1];
67
78
  // Force component rerender when props change:
@@ -70,11 +81,12 @@ var ParticlesLayerBase = function (props) {
70
81
  setTime(Date.now());
71
82
  frame.current = 0;
72
83
  skip.current = 0;
84
+ lastMatrix.current = null; // Reset matrix cache so UV textures are re-rendered
73
85
  animation.current = window.requestAnimationFrame(draw);
74
- }, [props.data, props.gradientStops, props.particles, props.density, props.delay, props.pointSize]);
86
+ }, [props.data, props.gradientStops, props.particles, props.density, props.delay, props.pointSize, props.minSpeed, props.maxSpeed]);
75
87
  var draw = function () {
76
88
  skip.current++;
77
- if (skip.current >= props.delay) {
89
+ if (skip.current > props.delay) {
78
90
  skip.current = 0;
79
91
  frame.current = (frame.current + 1) % props.particles;
80
92
  map.current.triggerRepaint();
@@ -169,6 +181,53 @@ var ParticlesLayerBase = function (props) {
169
181
  gl.attachShader(particlesProgram.current, fragmentShader);
170
182
  gl.linkProgram(particlesProgram.current);
171
183
  };
184
+ var createPositionComputeProgram = function (gl) {
185
+ var vertexShader = gl.createShader(gl.VERTEX_SHADER);
186
+ gl.shaderSource(vertexShader, PositionComputeVertexShader);
187
+ gl.compileShader(vertexShader);
188
+ var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
189
+ gl.shaderSource(fragmentShader, PositionComputeFragmentShader);
190
+ gl.compileShader(fragmentShader);
191
+ positionProgram.current = gl.createProgram();
192
+ gl.attachShader(positionProgram.current, vertexShader);
193
+ gl.attachShader(positionProgram.current, fragmentShader);
194
+ gl.linkProgram(positionProgram.current);
195
+ };
196
+ var createPositionTexture = function (gl) {
197
+ // Compute texture dimensions to fit all particles
198
+ // Total particles = density * density * particles
199
+ var totalParticles = props.density * props.density * props.particles;
200
+ // Find suitable texture dimensions (width should be power of 2 for compatibility)
201
+ // Use a width that balances the texture shape
202
+ var width = 256;
203
+ while (width * width < totalParticles) {
204
+ width *= 2;
205
+ }
206
+ var height = Math.ceil(totalParticles / width);
207
+ posTexWidth.current = width;
208
+ posTexHeight.current = height;
209
+ var texture = gl.createTexture();
210
+ gl.bindTexture(gl.TEXTURE_2D, texture);
211
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
212
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
213
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
214
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
215
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
216
+ return texture;
217
+ };
218
+ var createQuadBuffer = function (gl) {
219
+ // Full-screen quad (two triangles covering clip space)
220
+ bufferQuad.current = gl.createBuffer();
221
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufferQuad.current);
222
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
223
+ -1, -1,
224
+ 1, -1,
225
+ -1, 1,
226
+ -1, 1,
227
+ 1, -1,
228
+ 1, 1
229
+ ]), gl.STATIC_DRAW);
230
+ };
172
231
  var createBufferU = function (gl) {
173
232
  var min = getGeometryU[0], max = getGeometryU[1], geometry = getGeometryU[2];
174
233
  minU.current = min;
@@ -240,16 +299,20 @@ var ParticlesLayerBase = function (props) {
240
299
  createBufferU(gl);
241
300
  createBufferV(gl);
242
301
  createBufferScreen(gl);
302
+ createQuadBuffer(gl);
243
303
  createUVProgram(gl);
244
304
  createParticlesProgram(gl);
245
- textureU.current = createTexture(gl);
246
- textureV.current = createTexture(gl);
247
- fboU.current = createFrameBuffer(gl, textureU.current);
248
- fboV.current = createFrameBuffer(gl, textureV.current);
305
+ createPositionComputeProgram(gl);
306
+ // Single combined texture for both U and V (U in R, V in G)
307
+ textureUV.current = createTexture(gl);
308
+ fboUV.current = createFrameBuffer(gl, textureUV.current);
309
+ // Position texture for pre-computed particle positions
310
+ texturePosition.current = createPositionTexture(gl);
311
+ fboPosition.current = createFrameBuffer(gl, texturePosition.current);
249
312
  };
250
313
  var renderSpeedBuffer = function (gl, matrix, buffer, numTriangles) {
251
314
  gl.useProgram(uvProgram.current);
252
- // Pass a matrix uniform in:
315
+ // Pass a matrix uniform in:
253
316
  gl.uniformMatrix4fv(gl.getUniformLocation(uvProgram.current, 'u_matrix'), false, matrix);
254
317
  // Vertices:
255
318
  var aPos = gl.getAttribLocation(uvProgram.current, 'a_pos');
@@ -263,6 +326,43 @@ var ParticlesLayerBase = function (props) {
263
326
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
264
327
  gl.drawArrays(gl.TRIANGLES, 0, numTriangles * 3);
265
328
  };
329
+ //
330
+ // Render particle positions to the position texture.
331
+ // This moves the expensive trace loop from the particle vertex shader
332
+ // to a one-time-per-view-change fragment shader pass.
333
+ //
334
+ var renderPositionTexture = function (gl) {
335
+ gl.useProgram(positionProgram.current);
336
+ // Bind position framebuffer
337
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fboPosition.current);
338
+ gl.viewport(0, 0, posTexWidth.current, posTexHeight.current);
339
+ gl.clearColor(0, 0, 0, 0);
340
+ gl.clear(gl.COLOR_BUFFER_BIT);
341
+ // Disable blending for position computation
342
+ gl.disable(gl.BLEND);
343
+ // UV texture (already rendered)
344
+ gl.activeTexture(gl.TEXTURE0);
345
+ gl.bindTexture(gl.TEXTURE_2D, textureUV.current);
346
+ gl.uniform1i(gl.getUniformLocation(positionProgram.current, 'uvTexture'), 0);
347
+ // UV range uniforms
348
+ gl.uniform1f(gl.getUniformLocation(positionProgram.current, 'minU'), minU.current);
349
+ gl.uniform1f(gl.getUniformLocation(positionProgram.current, 'maxU'), maxU.current);
350
+ gl.uniform1f(gl.getUniformLocation(positionProgram.current, 'minV'), minV.current);
351
+ gl.uniform1f(gl.getUniformLocation(positionProgram.current, 'maxV'), maxV.current);
352
+ // Grid parameters
353
+ gl.uniform1f(gl.getUniformLocation(positionProgram.current, 'u_density'), props.density);
354
+ gl.uniform1f(gl.getUniformLocation(positionProgram.current, 'u_particles'), props.particles);
355
+ gl.uniform1f(gl.getUniformLocation(positionProgram.current, 'u_texWidth'), posTexWidth.current);
356
+ // Velocity magnitude remapping parameters
357
+ gl.uniform1f(gl.getUniformLocation(positionProgram.current, 'u_minVisMag'), props.minSpeed);
358
+ gl.uniform1f(gl.getUniformLocation(positionProgram.current, 'u_maxVisMag'), props.maxSpeed);
359
+ // Render full-screen quad
360
+ var aPos = gl.getAttribLocation(positionProgram.current, 'a_pos');
361
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufferQuad.current);
362
+ gl.enableVertexAttribArray(aPos);
363
+ gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
364
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
365
+ };
266
366
  var renderParticles = function (gl, matrix) {
267
367
  gl.useProgram(particlesProgram.current);
268
368
  // Matrix:
@@ -281,19 +381,14 @@ var ParticlesLayerBase = function (props) {
281
381
  gl.uniform1i(gl.getUniformLocation(particlesProgram.current, "u_num_instances"), props.particles);
282
382
  // Size of points to be drawn (in pixels).
283
383
  gl.uniform1f(gl.getUniformLocation(particlesProgram.current, "u_point_size"), props.pointSize);
284
- // U/V mins and maxes:
285
- gl.uniform1f(gl.getUniformLocation(particlesProgram.current, "minU"), minU.current);
286
- gl.uniform1f(gl.getUniformLocation(particlesProgram.current, "maxU"), maxU.current);
287
- gl.uniform1f(gl.getUniformLocation(particlesProgram.current, "minV"), minV.current);
288
- gl.uniform1f(gl.getUniformLocation(particlesProgram.current, "maxV"), maxV.current);
289
- // u-texture:
384
+ // Position texture (pre-computed particle positions):
290
385
  gl.activeTexture(gl.TEXTURE0);
291
- gl.bindTexture(gl.TEXTURE_2D, textureU.current);
292
- gl.uniform1i(gl.getUniformLocation(particlesProgram.current, "uTexture"), 0);
293
- // v-texture:
294
- gl.activeTexture(gl.TEXTURE1);
295
- gl.bindTexture(gl.TEXTURE_2D, textureV.current);
296
- gl.uniform1i(gl.getUniformLocation(particlesProgram.current, "vTexture"), 1);
386
+ gl.bindTexture(gl.TEXTURE_2D, texturePosition.current);
387
+ gl.uniform1i(gl.getUniformLocation(particlesProgram.current, "positionTexture"), 0);
388
+ // Position texture dimensions and grid parameters for coordinate lookup:
389
+ gl.uniform1f(gl.getUniformLocation(particlesProgram.current, "u_posTexWidth"), posTexWidth.current);
390
+ gl.uniform1f(gl.getUniformLocation(particlesProgram.current, "u_posTexHeight"), posTexHeight.current);
391
+ gl.uniform1f(gl.getUniformLocation(particlesProgram.current, "u_density"), props.density);
297
392
  // Vertices:
298
393
  var aPos = gl.getAttribLocation(particlesProgram.current, 'a_pos'); // on-screen position [-1..1]
299
394
  var aIdx = gl.getAttribLocation(particlesProgram.current, 'a_idx'); // particle index
@@ -309,24 +404,46 @@ var ParticlesLayerBase = function (props) {
309
404
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
310
405
  gl.drawArrays(gl.POINTS, 0, props.density * props.density * props.particles);
311
406
  };
407
+ //
408
+ // Check if two matrices are equal.
409
+ //
410
+ var matricesEqual = function (a, b) {
411
+ if (!a || !b)
412
+ return false;
413
+ for (var i = 0; i < 16; i++) {
414
+ if (a[i] !== b[i])
415
+ return false;
416
+ }
417
+ return true;
418
+ };
312
419
  var onRender = function (gl, matrix, zoom, width, height) {
313
420
  if (zoom < props.minZoom)
314
421
  return;
315
422
  if (zoom > props.maxZoom)
316
423
  return;
317
- gl.useProgram(uvProgram.current);
318
- // Render U-texture:
319
- gl.bindFramebuffer(gl.FRAMEBUFFER, fboU.current);
320
- gl.viewport(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
321
- gl.clearColor(0, 0, 0, 0); // clear to transparent
322
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
323
- renderSpeedBuffer(gl, matrix, bufferU.current, numTrianglesU.current);
324
- // Render V-texture:
325
- gl.bindFramebuffer(gl.FRAMEBUFFER, fboV.current);
326
- gl.viewport(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
327
- gl.clearColor(0, 0, 0, 0); // clear to transparent
328
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
329
- renderSpeedBuffer(gl, matrix, bufferV.current, numTrianglesV.current);
424
+ // Only re-render UV textures when the matrix changes (i.e., when the map
425
+ // view changes due to panning or zooming). This is a significant performance
426
+ // optimization since UV texture rendering involves expensive draw calls.
427
+ var matrixChanged = !matricesEqual(matrix, lastMatrix.current);
428
+ if (matrixChanged) {
429
+ lastMatrix.current = __spreadArray([], matrix, true);
430
+ gl.useProgram(uvProgram.current);
431
+ // Render to combined UV texture (U in R channel, V in G channel)
432
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fboUV.current);
433
+ gl.viewport(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
434
+ gl.clearColor(0, 0, 0, 0); // clear to transparent
435
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
436
+ // Render U to R channel and A channel (alpha indicates valid data)
437
+ gl.colorMask(true, false, false, true);
438
+ renderSpeedBuffer(gl, matrix, bufferU.current, numTrianglesU.current);
439
+ // Render V to G channel only (preserve R and A from U pass)
440
+ gl.colorMask(false, true, false, false);
441
+ renderSpeedBuffer(gl, matrix, bufferV.current, numTrianglesV.current);
442
+ // Reset color mask to default
443
+ gl.colorMask(true, true, true, true);
444
+ // Compute particle positions (moves expensive loop from vertex shader)
445
+ renderPositionTexture(gl);
446
+ }
330
447
  // Unbind framebuffer so we're rendering to the screen:
331
448
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
332
449
  // Reset the viewport size to equal the map size:
@@ -336,14 +453,98 @@ var ParticlesLayerBase = function (props) {
336
453
  };
337
454
  return (React.createElement(Layer, { id: null, type: "custom", beforeId: "overlay", key: time, onAdd: onAddLayer, render: function (gl, matrix) { return onRender(gl, matrix, map.current.getZoom(), map.current.getContainer().clientWidth, map.current.getContainer().clientHeight); } }));
338
455
  };
456
+ /**
457
+ * A WebGL-based Mapbox GL custom layer that visualizes vector flow fields (such as
458
+ * ocean currents or wind patterns) using animated particle trails.
459
+ *
460
+ * ## Overview
461
+ *
462
+ * The component takes an array of data points, each with a geographic position and
463
+ * a velocity vector (U and V components). It renders animated particles that flow
464
+ * along these velocity vectors, creating a visual representation of the flow field.
465
+ *
466
+ * Particle trails are colored using a configurable gradient, where the head of the
467
+ * trail is typically bright and the tail fades out. The animation continuously cycles
468
+ * particles through their trails, creating a flowing effect.
469
+ *
470
+ * ## Data Format
471
+ *
472
+ * Input data should be an array of points with the following structure:
473
+ *
474
+ * ```typescript
475
+ * interface IPoint {
476
+ * latitude: number; // Geographic latitude
477
+ * longitude: number; // Geographic longitude
478
+ * u: number; // Velocity component in X direction
479
+ * v: number; // Velocity component in Y direction
480
+ * }
481
+ * ```
482
+ *
483
+ * The component uses Delaunay triangulation (via Turf.js) to interpolate velocity
484
+ * values between data points, so the data does not need to be on a regular grid.
485
+ *
486
+ * ## Velocity Remapping
487
+ *
488
+ * To ensure all particles have visible trails regardless of the actual velocity
489
+ * magnitudes in the data, velocities are linearly remapped to a visible range:
490
+ *
491
+ * - The `minSpeed` prop sets the minimum trail speed (for the slowest currents)
492
+ * - The `maxSpeed` prop sets the maximum trail speed (for the fastest currents)
493
+ * - All velocities are scaled proportionally within this range
494
+ * - The relative differences between fast and slow currents are preserved
495
+ * - Particles with exactly zero velocity are not rendered
496
+ *
497
+ * ## Performance
498
+ *
499
+ * The component is optimized for performance using several GPU-based techniques:
500
+ *
501
+ * 1. **UV Texture Caching**: The velocity field is rendered to a texture only when
502
+ * the map view changes (pan/zoom), not on every animation frame.
503
+ *
504
+ * 2. **Combined UV Texture**: U and V components are stored in a single texture
505
+ * (U in red channel, V in green channel) to reduce texture fetches.
506
+ *
507
+ * 3. **Position Pre-computation**: Particle positions are pre-computed in a GPU
508
+ * shader pass when the view changes, eliminating expensive per-vertex loops
509
+ * during animation.
510
+ *
511
+ * With default settings (`density=40`, `particles=80`), the component renders
512
+ * 128,000 particles efficiently.
513
+ *
514
+ * ## Example
515
+ *
516
+ * ```tsx
517
+ * <Map>
518
+ * <ParticlesLayer
519
+ * data={oceanCurrentData}
520
+ * density={50}
521
+ * particles={100}
522
+ * gradientStops={[
523
+ * { pos: 0.0, color: '#00ffffff' },
524
+ * { pos: 0.5, color: '#0088ffff' },
525
+ * { pos: 1.0, color: '#0088ff00' },
526
+ * ]}
527
+ * minSpeed={0.01}
528
+ * maxSpeed={0.2}
529
+ * />
530
+ * </Map>
531
+ * ```
532
+ *
533
+ * ## Notes
534
+ *
535
+ * - The component requires a Mapbox GL map context (via `react-map-gl`)
536
+ * - WebGL 1.0 is used for maximum browser compatibility (no extensions required)
537
+ * - The layer renders before the "overlay" layer in the Mapbox style
538
+ * - Changing props triggers a full re-initialization of the WebGL resources
539
+ */
339
540
  var ParticlesLayer = function (_a) {
340
541
  var _b = _a.particles, particles = _b === void 0 ? 80 : _b, _c = _a.density, density = _c === void 0 ? 40 : _c, _d = _a.delay, delay = _d === void 0 ? 0 : _d, _e = _a.gradientStops, gradientStops = _e === void 0 ? [
341
542
  { pos: 0.0, color: '#ffffffff' },
342
543
  { pos: 0.25, color: '#5c5cffff' },
343
544
  { pos: 0.5, color: '#5c5cff00' },
344
545
  { pos: 1.0, color: '#5c5cff00' }, // transparent blue
345
- ] : _e, _f = _a.minZoom, minZoom = _f === void 0 ? 0 : _f, _g = _a.maxZoom, maxZoom = _g === void 0 ? 99 : _g, props = __rest(_a, ["particles", "density", "delay", "gradientStops", "minZoom", "maxZoom"]);
346
- return React.createElement(ParticlesLayerBase, __assign({ particles: particles, density: density, gradientStops: gradientStops, delay: delay, minZoom: minZoom, maxZoom: maxZoom }, props));
546
+ ] : _e, _f = _a.minZoom, minZoom = _f === void 0 ? 0 : _f, _g = _a.maxZoom, maxZoom = _g === void 0 ? 99 : _g, _h = _a.minSpeed, minSpeed = _h === void 0 ? 0.02 : _h, _j = _a.maxSpeed, maxSpeed = _j === void 0 ? 0.15 : _j, props = __rest(_a, ["particles", "density", "delay", "gradientStops", "minZoom", "maxZoom", "minSpeed", "maxSpeed"]);
547
+ return React.createElement(ParticlesLayerBase, __assign({ particles: particles, density: density, gradientStops: gradientStops, delay: delay, minZoom: minZoom, maxZoom: maxZoom, minSpeed: minSpeed, maxSpeed: maxSpeed }, props));
347
548
  };
348
549
  ParticlesLayer.displayName = 'ParticlesLayer';
349
550
  export { ParticlesLayer };
@@ -1,2 +1,2 @@
1
- declare const ParticlesVertexShader = "\n uniform mat4 u_matrix;\n attribute vec2 a_pos;\n attribute float a_idx;\n attribute float a_offset;\n\n uniform float u_point_size;\n uniform int u_num_instances;\n uniform int u_frame;\n uniform sampler2D uTexture;\n uniform sampler2D vTexture; \n uniform float minU;\n uniform float maxU;\n uniform float minV;\n uniform float maxV;\n uniform float u_gradientStops[12];\n uniform vec4 u_gradientColors[12]; \n\n // Output color, calculated by this vertex shader:\n varying vec4 v_color;\n\n //\n // Returns texture color at pixelCoord.\n //\n vec4 texelFetch(sampler2D tex, vec2 normalizedCoord) {\n return texture2D(tex, normalizedCoord);\n }\n\n //\n // Given a value, find its corresponding color in the gradient stops.\n //\n vec4 getGradientColor(float position) {\n // Find left and right stop:\n float left = -1.0;\n float right = 99.0;\n vec4 leftColor;\n vec4 rightColor;\n\n for(int i = 0; i < 12; i++) {\n if (u_gradientStops[i] <= position) {\n left = u_gradientStops[i]; \n leftColor = u_gradientColors[i];\n } else break;\n }\n\n for(int i = 12 - 1; i >= 0; i--) {\n if (u_gradientStops[i] >= position) {\n right = u_gradientStops[i]; \n rightColor = u_gradientColors[i];\n } else break;\n }\n\n // Distance between stops:\n float width = right - left;\n // Distance from left stop:\n float dist = position - left;\n // Right stop weight:\n float weight;\n if(dist == 0.0) {\n weight = dist;\n } else {\n weight = dist / width;\n }\n\n float r = leftColor.x + (rightColor.x - leftColor.x) * weight;\n float g = leftColor.y + (rightColor.y - leftColor.y) * weight;\n float b = leftColor.z + (rightColor.z - leftColor.z) * weight;\n float a = leftColor.w + (rightColor.w - leftColor.w) * weight;\n return vec4(r, g, b, a); \n } \n\n // Vertex shader merely sets vertex position. Fragment shader \n // does all the work.\n void main() {\n int idx = int(a_idx); \n int offset = int(a_offset);\n int frame = u_frame + offset;\n if(frame >= u_num_instances) frame -= u_num_instances;\n\n // Determine color from frame.\n int start = idx - frame;\n if(start < 0) start += u_num_instances;\n v_color = getGradientColor(float(u_num_instances - start) / float(u_num_instances));\n\n // a_pos.x is -1..1, left to right.\n // a_pox.y is -1..1, bottom to top (negative is bottom).\n vec2 pos = a_pos; // Start off at initial position.\n\n // Find initial U. If there isn't one (outside of bounds of texture), \n // then we don't draw anything.\n float w = texelFetch(uTexture, (pos + 1.0) / 2.0).w;\n if(w == 0.0) return;\n\n\n // Use velocity vector [idx] times.\n float uRange = maxU - minU;\n float vRange = maxV - minV;\n\n for(int i = 1; i < 100; i++) {\n if(i > idx) break;\n // Find velocity vector at current position.\n // u and v are 0..1 values.\n vec4 uColor = texelFetch(uTexture, (pos + 1.0) / 2.0);\n vec4 vColor = texelFetch(vTexture, (pos + 1.0) / 2.0);\n if(uColor.w == 0.0) return;\n float u = uRange * uColor.x + minU;\n float v = vRange * vColor.x + minV;\n \n pos.x += u / 10.0;\n pos.y += v / 10.0;\n if(pos.x <= -1.0 || pos.x >= 1.0 || pos.y <= -1.0 || pos.x >= 1.0) {\n return;\n }\n }\n\n gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);\n gl_PointSize = u_point_size;\n }\n";
1
+ declare const ParticlesVertexShader = "\n attribute vec2 a_pos;\n attribute float a_idx;\n attribute float a_offset;\n\n uniform float u_point_size;\n uniform int u_num_instances;\n uniform int u_frame;\n uniform sampler2D positionTexture; // Pre-computed positions (X in RG, Y in BA)\n uniform float u_posTexWidth;\n uniform float u_posTexHeight;\n uniform float u_density;\n uniform float u_gradientStops[12];\n uniform vec4 u_gradientColors[12];\n\n // Output color, calculated by this vertex shader:\n varying vec4 v_color;\n\n //\n // Given a value, find its corresponding color in the gradient stops.\n //\n vec4 getGradientColor(float position) {\n // Find left and right stop:\n float left = -1.0;\n float right = 99.0;\n vec4 leftColor;\n vec4 rightColor;\n\n for(int i = 0; i < 12; i++) {\n if (u_gradientStops[i] <= position) {\n left = u_gradientStops[i];\n leftColor = u_gradientColors[i];\n } else break;\n }\n\n for(int i = 12 - 1; i >= 0; i--) {\n if (u_gradientStops[i] >= position) {\n right = u_gradientStops[i];\n rightColor = u_gradientColors[i];\n } else break;\n }\n\n // Distance between stops:\n float width = right - left;\n // Distance from left stop:\n float dist = position - left;\n // Right stop weight:\n float weight;\n if(dist == 0.0) {\n weight = dist;\n } else {\n weight = dist / width;\n }\n\n float r = leftColor.x + (rightColor.x - leftColor.x) * weight;\n float g = leftColor.y + (rightColor.y - leftColor.y) * weight;\n float b = leftColor.z + (rightColor.z - leftColor.z) * weight;\n float a = leftColor.w + (rightColor.w - leftColor.w) * weight;\n return vec4(r, g, b, a);\n }\n\n // Decode a 16-bit encoded position from two bytes (high, low as 0-1 floats)\n float decodePosition(float hi, float lo) {\n float encoded = hi * 255.0 * 256.0 + lo * 255.0;\n return encoded / 65535.0 * 2.0 - 1.0;\n }\n\n void main() {\n int idx = int(a_idx);\n int offset = int(a_offset);\n int frame = u_frame + offset;\n if(frame >= u_num_instances) frame -= u_num_instances;\n\n // Determine color from frame.\n int start = idx - frame;\n if(start < 0) start += u_num_instances;\n v_color = getGradientColor(float(u_num_instances - start) / float(u_num_instances));\n\n // Compute texture coordinates for this particle's pre-computed position.\n // WebGL 1 doesn't have gl_VertexID, so we compute the particle index from\n // vertex attributes. The vertex buffer layout is:\n // for y in [0, density): for x in [0, density): for i in [0, particles): (pos, idx, offset)\n\n // Convert grid position back to grid index\n float gridX = (a_pos.x + 0.99) * (u_density - 1.0) / 1.98;\n float gridY = (a_pos.y + 0.99) * (u_density - 1.0) / 1.98;\n float gridIndex = floor(gridY + 0.5) * u_density + floor(gridX + 0.5);\n float particleIdx = gridIndex * float(u_num_instances) + a_idx;\n\n // Convert particle index to texture coordinates\n float texX = mod(particleIdx, u_posTexWidth);\n float texY = floor(particleIdx / u_posTexWidth);\n\n // Sample position texture (add 0.5 to sample pixel center)\n vec2 texCoord = vec2((texX + 0.5) / u_posTexWidth, (texY + 0.5) / u_posTexHeight);\n vec4 encodedPos = texture2D(positionTexture, texCoord);\n\n // Decode position (X in RG, Y in BA)\n float posX = decodePosition(encodedPos.r, encodedPos.g);\n float posY = decodePosition(encodedPos.b, encodedPos.a);\n\n // Check for off-screen marker (position > 1.5 means invalid)\n if(posX > 1.5) return;\n\n gl_Position = vec4(posX, posY, 0.0, 1.0);\n gl_PointSize = u_point_size;\n }\n";
2
2
  export { ParticlesVertexShader };